JavaFX: HBox.setHgrow refuses to do its job - javafx

Taking this code, shouldn't the word "Label" be on the right side of the application window?
To be more specific about my problem: Why doesn't the HBox.setHgrow give the label all the horizontal space available?
package com.example.javafxwindow
import javafx.application.Application
import javafx.geometry.Pos
import javafx.scene.Scene
import javafx.scene.control.Label
import javafx.scene.layout.HBox
import javafx.scene.layout.Priority
import javafx.stage.Stage
class HelloApplication : Application()
{
override fun start(stage: Stage)
{
val label = Label("Label")
label.alignment = Pos.CENTER_RIGHT
HBox.setHgrow(label, Priority.ALWAYS)
val hbox = HBox(label)
val scene = Scene(hbox, 400.0, 400.0)
stage.scene = scene
stage.show()
}
}
fun main()
{
Application.launch(HelloApplication::class.java)
}
This is, slowly but surely, driving me nuts.
I don't know what to do else. Maybe I'm blind by now ... where's the error?
I would really appreciate any help.

Allowing a label to grow beyond its preferred size
The label is bounded by its maxWidth which defaults to its prefWidth.
You want to make the maxWidth unbounded, so call:
label.setMaxWidth(Integer.MAX_VALUE)
Different controls have different defaults for maxWidth. For buttons and labels the maxWidth default to their prefWidth. Unless you reset the maxWidth these controls won't grow; even if you provide hints that you might think would make them do so.
Quoting the Oracle JavaFX layout sizing tutorial:
UI controls also provide default minimum and maximum sizes that are based on the typical usage of the control. For example, the maximum size of a Button object defaults to its preferred size because you don't usually want buttons to grow arbitrarily large. However, the maximum size of a ScrollPane object is unbounded because typically you do want them to grow to fill their spaces.
It would be nice if this detail of information was also in java doc of each control, so you would know which controls default their max size to the pref size. There is some information on this in the HBox java doc.
Spring alternative
An alternative is to insert a spring pane into the hbox before the label and set the hgrow on the spring pane instead. That will push the label all the way to the right. For info on that approach see:
How to create toolbar with left, center and right sections in javaFX?
Alternative layout
As noted by DaveB in the comments:
Perhaps, if you simply want the Label over to the right, using AnchorPane instead of HBox would be better. Just anchor the Label to the right side. Choosing the correct layout container can often be the key to retaining your sanity.
Example FXML
I don't write Kotlin, so I will provide a layout example with FXML instead and you can translate it to Kotlin if you wish.
The example sets the maxWidth of a label to a large number so that the label can grow to fill the available area, with the text in the label right aligned.
<HBox prefHeight="50.0" prefWidth="150.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label alignment="CENTER_RIGHT" maxWidth="1.7976931348623157E308" text="Label" HBox.hgrow="ALWAYS" />
</children>
</HBox>
FAQ
That worked. But I can see that's going to be hard to remember next time.
Just my opinion, others have different opinions:
It will probably be easier to remember than you may think. Once something like this bites you, it is difficult to forget.
If something is not resizing how you expect, just try setting the max width (and/or max height).
Use scene builder for prototyping such things, removing any default sizing it sets for layouts and providing sizing hints only where needed.
Scene Builder is quicker and easier for layout than writing things in code (for many, but not all tasks), plus the result is easier to maintain.
Do you happen to know why label.prefWidth=Double.MAX_VALUE did not work?
It only "produced" a "..." text instead of "Label" and it remained on the right side of the application window.
It doesn't make a lot of sense to prefer something to be an infinite size, you could never see it at the preferred size.
It probably confused the layout algorithm. I'm guessing it sized the label to the preferred size, then needed to round it up to snap it for pixel alignment or add to the margin. This would cause a double overflow, making the preferred size negative, which doesn't make sense. So it would try to size to the minimum size instead. When there is not enough space to display a value, a label will elide the text (replacing characters with ...), which is what it did here. The default minimum size will be just enough to display the elided text.
If you also set minSize to 100 and the prefSize to Double.MAX_VALUE, the label will show all of the text aligned within the minSize, which indicates that it is the minimum size that is being respected in such a case.
Anyway, don't set the prefWidth to Double.MAX_VALUE. Instead, set the maxWidth to some large number so that the field can grow to fit it (e.g. Integer.MAX_VALUE).

Related

Grid Layout Widget Sizing Problems

I use a Grid Layout inside my app. The grid layout I set to some fixed sizes.
myBootGridLayout->setContentsMargins(3,0,0,0);
myBootGridLayout->setRowMinimumHeight(0,25);
myBootGridLayout->setRowMinimumHeight(1,25);
myBootGridLayout->setRowMinimumHeight(2,25);
wdgBootFeatues->setFixedHeight(80);
For the QPushButton I use a size rule:
btnSelBootImagePath->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
But as you can see on the image, the ComboBox and Buttons have the same size but the LineEdit field is smaller. What I do wrong? Is there a trick to bring them on the common same size (Height) like in the QT documentation?
In case your question is to understand how to make sure that elements will have the same height, we should consider the following:
Layout might not necessarily ensure that some elements will have same height you would like, since it also relies on the size (horizontal and vertical) policy of the element in layout itself. In case you want to have QLineEdit and QPushButton instances to have the same height, you should set minimum height for each of them. Probably, it would even make sense to make the height fixed (i.e. set both minimum and maximum height to be the same values) for such elements to fit your needs, since both of these elements by default have fixed vertical size policy. This is for the reason, since most apps treat buttons and one line text fields in the same way.
In most cases, combining QVBoxLayout, QHBoxLayout and then QGridLayout is not necessary at all, since QGridLayout is much more flexible, and combines QVBoxLayout and QHBoxLayout features in a single layout under the hood at the first place, this it will probably satisfy all your needs (i.e. to represent you elements in a grid manner). Also, construction of UI elements will be slightly faster if less elements will be used.
Qt documentation might have such an effect because of the following reason - elements were tested on a different device. Qt does not try to make identical style sheets for widgets' elements across all platforms, thus visual differences will be everywhere. On some operating systems, button height is smaller than text field height by default, and this is completely normal.
One approach to make sure that size will get bigger than by default is to change size policy (vertical in your case). Code snippet changing size policy is correct basically. However, size policy is different thing than fixed height across elements. However, if your button and line edit would be in the same row, and both would have minimum expanding vertical policy, probably these elements would have the same height in that row.
Thus, probably to make sure the height of your elements remains the same is to set some minimum (and maximum as well in case vertical size policy is fixed) height through code or Qt Creator. This would be the easiest and least painful from thinking perspective approach. I am not sure if I have answered the question (it looks like that you answered yourself in your own way), but I am sure that I have introduced some thoughts that might come in handy when understanding Qt layouts.

Resizing Layout equal to MainWindow

When I run my program it will display all content properly, and when I resizing the main window, the layout along with all associated widgets remain fixed, rather than resizing with the main window. I used to increase my all widget and listWidget respect to window computer resolution size but still this not one work properly.
I used this one code finding the system height and width.
QWidget widget;
widget.resize(widget.width(), widget.minimumHeight());
QRect rec = QApplication::desktop()->screenGeometry();
int h = rec.height();
int w = rec.width();
// Increasing the listwidget size
ui->listWidget->setFixedHeight(h);
ui->listWidget->setFixedWidth(w);
//increasing the button size
ui->pushButton->setFixedHeight(h0.2);
ui->pushButton->setFixedWidth(w0.2);
At this link you will find two screenshots that illustrate my problem.
Please resolve to solve my problem. Thanks very much in advance.
When defining the layout of your windows and forms in Qt Designer you have to define each element of your form in advance, in order to have a working layout.
This solution is based on the screenshots provided in the comments to the question. Follow these steps:
Add an empty widget to the central area of your form, if there is nothing there. It will be used as a placeholder for the controls you will add later, and of course you can replace it with whatever widget you want. But you need it there to define a proper layout.
In the property panel, set the horizontal QSizePolicy of this widget to MinimumExpanding.
Add an horizontal spacer to the left side of your progress bar.
Define a minimum/maximum width for the white widget on the left (I guess it's a text area). As an example set the maximum width to 200
pixels.
Make the same for the QTabWidget on the right.
Give a minimum height to the Groupbox on top.
Then give the grid layout to the MainWindow.
You should get something similar in the designer view (I use a dark theme, yours will have different colors of course):
If you complete all steps you should have a nicely resizing window.
For the future: remember to integrally define your layouts, also using placeholder widgets when needed, read carefully the documentation about the widgets size policies (there are several, you need to play with them to fully understand each one) and keep in mind that Qt uses a container based approach which is different, as an example from those used by the .Net framework that relies on the concept of anchors.
EDIT : to answer questions in the comments
You will need to add a layout to any widget that contains other widgets, e.g. adding controls to your groupbox will require to give it a grid, horizontal or vertical layout in order to scale nicely on resize. Again use spacers and size policies to make it look the way you want. If you need to add or remove controls, or change their positions, you may need to brake the layout, rearrange and then set it again.
You can also select groups of widgets and give them a layout e.g. vertical, than another group and set them horizontal and so on... then give a grid layout to the container widget to build a compound layout.
There are endless possibilities, you just need to practice and go through trial and error as for everything else...
You can also do it all programmatically, check the Qt widgets documentation for this. But for complex layouts I would not go that way: it's a lot of code... and you have to compile and run to test every modification.
Using the QtCreator, within the designer you can simply right-click on the parent-widget and add a Grid-Layout.
This one resizes it's children to it's dimensions.

JavaFX : Adapt component height to its children height?

I'm trying to develop a desktop app using javaFX (2.2), and I'm experiencing some difficulties to really get it to behave like I would like it to. Being a new user of the framework, I might not be using it as it should be...
What I want is a kind of dashbord, with several JavaFX or custom controls inside a ScrollPane. I currently have several views that look like this :
<AnchorPane id="userContent" xmlns:fx="http://javafx.com/fxml" fx:controller="squalp.view.OrgViewController">
<children>
<ScrollPane fx:id="scrollPane" styleClass="transparent" hbarPolicy="NEVER" vbarPolicy="AS_NEEDED" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<content>
<VBox fx:id="vBox" styleClass="transparent" alignment="TOP_LEFT" focusTraversable="true">
<children>
<Label text="Label" />
<Button fx:id="bTest" text="Test" onAction="#test"/>
<TreeView fx:id="orgTree" focusTraversable="true" />
</children>
</VBox>
</content>
</ScrollPane>
</children>
</AnchorPane>
As long as I keep using simple controls, no problem, items are correctly placed one after another in th VBox.
But when using TreeViews or TableViews, I can only get my controls to have fixed height, meaning extra space on the bottom, or new scroll bars, in addition to the main one. What I would like to achieve is to adapt my controls to their current content height.
Qt's QML, for exemple, has a childrenHeight property, and it's kind of what I'm looking for. I figure this content height must be calculated somehow, since components scrollbars do adapt to the content.
I had to use a workaround for tableviews :
table.setPrefHeight(list.size() * rowHeight + headerHeight);
but I couldn't find a way to track displayed items on a treeview.
Has anyone an idea of what I could be doing to handle this issue ? Or I am really not using the framework as I should ?
Thanks in advance.
EDIT : Visual description of the problem :
Case 1
Here my treeview's height is set to a default value. There are not enough items to fill the treeview : if I want to put another component below, there'll be a gap; if my scene is not big enough, the ScrollPane's scrollbar appears, even though all the displayable content is visible.
Case 2
If if expand my treeview's items, there are now too many of them. They are not all visible at once, and the treeview's scrollbar appears, even though I already have a scrollable view with the scrollpane.
What I would like is my TreeView to adapt to its content height (reduced height if items are collapsed, bigger height if they are expanded), in order to be able to put several components one after another and to scroll through them all using the ScrollPane's scrollbar.
Since the TreeView component has itself a scrollbar, its visible content's height must be calculated at some point (since scrollbars basically use ratios based on contentHeight/componentHeight). This information doesn't seem to be available in the public API, but I wonder if, developping a custom class that expands TreeView, there could be a way to bind my component to its "visible height".
There maybe a proper way to achieve your goal, but I can suggest the approach similar to tableview's.
orgTree.prefHeightProperty().bind(orgTree.impl_treeItemCountProperty()
.multiply(treeRowHeight)); // treeRowHeight maybe 24.
Note that this is only a workaround where the deprecated and internal use-only method is used, which maybe removed in future releases.
I was looking for way to make TreeView auto-resize to fit its content, e.g. when a node is expanded or collapsed. Here's what I eventually do. Note the Platform.runLater() is required, thus simple property binding would not work here. Also note the cellHeight is the height of a tree node. It's beyond the scope of the question here how to get that.
treeView.expandedItemCountProperty().addListener((obs, oldV, newV) -> {
Platform.runLater(() -> {
treeView.setPrefHeight(newV.intValue() * cellHeight);
});
});
You can use the binding property of the view to the childs height property. By this, whenever the height of the Parent changes, childs height will automatically change ! This is not automatic height adaption, like you want, but a forced adaption, which will help you !
Width property is also available
Basically this is a snippet that will help you :
pane.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0,
Number oldValue, Number newValue) {
table.setPrefHeight(newValue - 200);/*The difference of height between
parent and child to be maintained*/
}
});

heightForWidth label

4-5 years I needed a widget with the following properties
Display text incl. HTML
Text should be wrapped on several lines
When the widget is put into a layout, the height of the widget should be adjusted in such a way that the text exactly fits the widget geometry
This subwidget should be used in a layout to provide some detail on how the other GUI elements in the layout work but only consume a minimum space to display its content.
I thought this was an easy one - but each time I return to the challenge I always end by giving up.
The main problem is that the layout breaks down when heightForWidth() is implemented and a QSizePolicy with setHeightForWidth(True) is used. It can shrink to infinitely small. Apparently this is Qt bug.
Another approach is to call updateGeometry() when a resizeEvent() occurs and call setFixedHeight(h) using a width dependent height. But this also gives rise to some weird layout behavior.
If anybody has any good suggestions on how to approach this, please let me know.
Below I include a snippet that reproduces the layout resizing behavior.
Best regards,
Mads
import sys
from PyQt4 import QtCore, QtGui
class Square(QtGui.QLabel):
def __init__(self, parent=None):
QtGui.QLabel.__init__(self, parent)
self.setAutoFillBackground(True)
palette = QtGui.QPalette()
palette.setColor(QtGui.QPalette.Window, QtGui.QColor('red'))
self.setPalette(palette)
policy = self.sizePolicy()
policy.setHeightForWidth(True)
self.setSizePolicy(policy)
def sizeHint(self):
return QtCore.QSize(128, 128)
def heightForWidth(self, width):
return width
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
# Call base class constructor
QtGui.QWidget.__init__(self, parent)
# Add a layout
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
# Add Square
label = Square()
layout.addWidget(label)
spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
layout.addItem(spacerItem)
# Some dummy button
self._push_button = QtGui.QPushButton('Press me')
layout.addWidget(self._push_button)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())
I found this extremely problematic. I think the core of the problem is as follows:
Qt layouts are typically assigned to a widget that we can call the parent widget (with parentwidget->setLayout(layout)).
Layouts ask their child widgets (or child layouts) for their size preferences (minimum size, size hint [preferred size], and maximum size). This is done in a slightly complex way via a QLayoutItem-derived class (e.g. QWidgetItem).
Height-for-width (HFW) widgets -- those having hasHeightForWidth() == true and providing int heightForWidth(int width) -- have limited ability to describe their constraints to layouts, in that they can offer a minimumSizeHint(), a sizeHint(), and a heightForWidth(width). They can, if necessary, also call functions like setMinimumSize() and setMaximumSize(). But most Qt layouts, like QVBoxLayout, QHBoxLayout, and QGridLayout, don't pay particular attention to the heightForWidth(width) results when offering their own min/preferred/max size to their parent, because they do so via QLayout::minimumSize(), QLayout::sizeHint(), and QLayout::maximumSize() -- none of which are called with information about the layout's target size (in a Catch-22-like situation), so they can't easily provide the width value to their children.
So, the layout asks its children how big they want to be without much HFW thought, and thus sets its own minimum/preferred/maximum size (determining, potentially in conjunction with other constraints, the size of the parent widget).
After the layout has told its parent (and its parent, etc.) how much space it needs, Qt works out how big it thinks everything should be. The layout is called via its setGeometry(const QRect& layout_rect). Now the layout knows how big it is. It assigns space to its children with child->setGeometry().
But only at this point has the layout discovered its final width. So up to this point, it cannot offer a final width to its children, and thus HFW widgets can't know their final width until they're being laid out finally. By this time, the layout and its parent may already have been set to the wrong height (can be too big; can be too small).
An excellent description of the widget/layout interaction is at http://kdemonkey.blogspot.co.uk/2013/11/understanding-qwidget-layout-flow.html; beyond this, you're best off looking at the Qt source code itself.
So you see two categories of solution, as you've outlined above, where size needs to be "properly" constrained to that required.
The first:
HFW widgets, like QLabel-derived classes using word wrap, or images wanting to fix their aspect ratio, can provide sensible values for their minimum and maximum size, and a sensible sizeHint() (being the size they'd like to be).
Then, when they're laid out, they (1) intercept QWidget::resizeEvent(QResizeEvent* event) to find out their new width (e.g. from event->size()); (2) calculate their preferred height via their own heightForWidth() function; and (3) force their height via, for example, setFixedHeight(height) followed by updateGeometry().
This tends to work reasonably, except that any parent widget that wants to match its size to such an HFW widget has to do the same thing, e.g. intercepting resizeEvent, and if the parent widget has a layout with hasHeightForWidth() == true, doing something like setFixedHeight(layout->heightForWidth(width())); updateGeometry();.
That leads to faff as you have to modify potentially arbitrary widgets in a long line of parenthood.
It can also lead to quite a lot of redrawing, which can be slow and cause visual flickering.
The second:
Rewrite the layouts.
The best approach I've found is to have layouts calculate their children's geometry assuming some sort of standard rectangle (Qt itself often starts with a 640x480 one when drafting a layout); offering height information based on this draft; then, when setGeometry() is called, if the resulting height (based on a potentially new width) doesn't match what the layout previously advertised, re-constraining by calling parent->setFixedHeight().
This allows you to use arbitrary widgets, and HFW widgets only need to support hasHeightForWidth() and heightForWidth() to have the new layouts (and their parent widgets, and any ancestor layouts using this mechanism) adjust their height.
It can lead to some redrawing, but often not too much, as it happens on a per-layout not per-widget basis.
I've put C++ code at http://egret.psychol.cam.ac.uk/code/2017_01_16_qt_height_for_width/ for the following layouts:
BoxLayoutHfw, VBoxLayoutHfw, HBoxLayoutHfw -- replacements for QBoxLayout etc.
GridLayoutHfw -- replacements for QGridLayout.
FlowLayoutHfw -- replacement for Qt's FlowLayout (http://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html).
and the following widgets:
AspectRatioPixmapLabel -- image maintaining its aspect ratio;
LabelWordWrapWide -- word-wrapping label that tries to use as much horizontal space before it word-wraps.
VerticalScrollArea -- as its name suggests, a vertical scroll area, but one that supports height-for-width cleanly.
... plus some infrastructure code (#defines etc.) that should make the layouts revert to their Qt equivalent's behaviour, and some support files (including gui_defines.h and layouts.h that make the choice of layout and base widget conditional on your preference in this regard).
One residual problem that I've not successfully addressed is that I think QLabel's heightForWidth() seems to return slightly wrong values (slightly overestimating its space requirements) with stylesheets under some circumstances. I suspect the problem is in QLabelPrivate::sizeForWidth but I've just worked around it imperfectly by calculating some stylesheet borders; it's still not quite right, but overestimating (leading to whitespace) is better than underestimating (leading to clipping).
Size hint will not dictate child widget's size except Fixed policy. If autoresize-like policies will surplus by parent.

Qt QHboxLayout cell size ssues

For those of you who haven't been reading my Qt questoins, I am learning Qt for a project. I have only limited experience with GUI design at all, and not in Qt.
I've got a horizontal layout that I want to populate with some buttons. I can feed these buttons in just fine, but my formerly-square buttons are stretched horizontally to take up more space.
I want to let the layout manager determine the best way to size these buttons, but I also want their original proportions to remain intact. For instance, if I start would with 32X32 buttons that need to shrink to fit all of them in the layout, I want them to shrink proportionally so that the width to height scale is maintained. 20X20, 16X16, 12X12 would all be just fine, but 24X16 would be an example of dimensions that are unacceptable.
I've tinkered with size policies on the buttons and stretch options. I'm not seeing, even after reading the QPushButton and QHboxLayout classes how to do this. How is it accomplished?
Thanks.
As long as I understand the question correctly, I think what you want is QBoxLayout::addStretch(). This will add a spacer object that fills the unused space. So the buttons will have their ideal size and the spacer will fill the rest. You can try experimenting with this in Designer, it's easier than the write/compile/run cycle.
You should take a look at the answers to this question. This is a recap of my answer there.
You need to create a custom derivative of QLayoutItem, which overrides bool hasHeightForWidth() and int heightForWidth( int width ) to preserve the aspect ratio. You could either pass the button in and query it, or you could just set the ratio directly. You'll also need to make sure the widget() function returns a pointer to the proper button.
Once that is done, you can add a layout item to a layout in the same manner you would a widget. So when your button gets added, change it to use your custom layout item class.
I haven't actually tested any of this, so it is a theoretical solution at this point. I don't know of any way to do this solution through designer, if that was desired.

Resources