QML dialog broken?

I have this code:

import QtQuick 2.3 import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.2 Dialog { standardButtons: StandardButton.Ok | StandardButton.Cancel width: layout.implicitWidth height: layout.implicitHeight RowLayout { id: layout anchors.fill: parent Item { width: 10 height: 1 } GridLayout { columns: 2 rowSpacing: 10 Layout.fillHeight: true Layout.fillWidth: true Text { text: "Hello world? " } Text { text: "Hello world!" } Text { text: "Goodbye world? " } Text { text: "Goodbye world!" } } Item { width: 10 height: 1 } } } 

At startup, it looks like this, and the dialog can be resized to any size. In addition, RowLayout does not actually populate the parent, as you can see.

damnpls

How can I do this so that the dialog box cannot be resized below the minimum layout size, and so that the layout fills the dialog?

+5
source share
2 answers

Unfortunately, this is a bug in Qt. The documentation is currently misleading, and Dialog does not match the correct size of the content. Consider this working example, which I based on DefaultFontDialog :

 AbstractDialog { title: "Hello" id: root // standardButtons: StandardButton.Ok | StandardButton.Cancel modality: Qt.NonModal Rectangle { id: content implicitWidth: mainLayout.implicitWidth + outerSpacing * 2 implicitHeight: mainLayout.implicitHeight + outerSpacing * 2 property real spacing: 6 property real outerSpacing: 12 color: "white" GridLayout { id: mainLayout anchors { fill: parent; margins: content.outerSpacing } rowSpacing: content.spacing columnSpacing: content.spacing columns: 5 Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } Text { text: "Hello" } } } } 

This works exactly as expected, although of course you don't get the buttons.

If you just change it to Dialog and uncomment standardButtons , then it will stop working - you can resize the dialog box to copy its contents (at least in width) and the content will not expand to the size of the dialog box.

The reason that the minimum width does not work becomes clear when we look at the source code for Dialog (in qtquickcontrols/src/dialogs/DefaultDialogWrapper.qml ):

 AbstractDialog { id: root default property alias data: defaultContentItem.data onVisibilityChanged: if (visible && contentItem) contentItem.forceActiveFocus() Rectangle { id: content property real spacing: 6 property real outerSpacing: 12 property real buttonsRowImplicitWidth: minimumWidth property bool buttonsInSingleRow: defaultContentItem.width >= buttonsRowImplicitWidth property real minimumHeight: implicitHeight property real minimumWidth: Screen.pixelDensity * 50 implicitHeight: defaultContentItem.implicitHeight + spacing + outerSpacing * 2 + buttonsRight.implicitHeight implicitWidth: Math.min(root.__maximumDimension, Math.max( defaultContentItem.implicitWidth, buttonsRowImplicitWidth, Screen.pixelDensity * 50) + outerSpacing * 2); 

minimumWidth hardcoded to Screen.pixelDensity * 50 !! There was no hope that this would be consistent with the contents of the dialogue. minimumHeight works better (although not perfect, I think because distance is not taken into account).

I'm not sure why defaultContentItem is not distributed correctly, but anyway. It seems that the only solution at the moment is to use AbstractDialog and implement the buttons and accepted() / rejected() / etc. signals itself. A bit of pain.

Change / Decision

I did some further research.

  • The reason the defaultContentItem is not expanding is because the bottom anchor is not anchored to the top of the button row:

     Item { id: defaultContentItem anchors { left: parent.left right: parent.right top: parent.top margins: content.outerSpacing } implicitHeight: childrenRect.height } 
  • Minimum sizes just don't work well with snap based layouts. They work with GridLayout based layouts.

  • Unfortunately, childrenRect does not have implicitWidth / Height, so we should actually have children in ColumnLayout, not ColumnLayout.

...

 import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 // A Dialog that resizes properly. The defualt dialog doesn't work very well for this purpose. AbstractDialog { id: root default property alias data: defaultContentItem.data onVisibilityChanged: if (visible && contentItem) contentItem.forceActiveFocus() Rectangle { id: content property real spacing: 6 property real outerSpacing: 12 property real buttonsRowImplicitWidth: minimumWidth property bool buttonsInSingleRow: defaultContentItem.width >= buttonsRowImplicitWidth property real minimumHeight: implicitHeight property real minimumWidth: implicitWidth // Don't hard-code this. implicitWidth: Math.min(root.__maximumDimension, Math.max(Screen.pixelDensity * 10, mainLayout.implicitWidth + outerSpacing * 2)) implicitHeight: Math.min(root.__maximumDimension, Math.max(Screen.pixelDensity * 10, mainLayout.implicitHeight + outerSpacing * 2)) color: palette.window Keys.onPressed: { event.accepted = true switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: reject() break case Qt.Key_Enter: case Qt.Key_Return: accept() break default: event.accepted = false } } SystemPalette { id: palette } // We use layouts rather than anchors because there are no minimum widths/heights // with the anchor system. ColumnLayout { id: mainLayout anchors { fill: parent; margins: content.outerSpacing } spacing: content.spacing // We have to embed another item so that children don't go after the buttons. ColumnLayout { id: defaultContentItem Layout.fillWidth: true Layout.fillHeight: true } Flow { Layout.fillWidth: true id: buttonsLeft spacing: content.spacing Repeater { id: buttonsLeftRepeater Button { text: (buttonsLeftRepeater.model && buttonsLeftRepeater.model[index] ? buttonsLeftRepeater.model[index].text : index) onClicked: root.click(buttonsLeftRepeater.model[index].standardButton) } } Button { id: moreButton text: qsTr("Show Details...") visible: false } } Flow { Layout.fillWidth: true id: buttonsRight spacing: content.spacing layoutDirection: Qt.RightToLeft Repeater { id: buttonsRightRepeater // TODO maybe: insert gaps if the button requires it (destructive buttons only) Button { text: (buttonsRightRepeater.model && buttonsRightRepeater.model[index] ? buttonsRightRepeater.model[index].text : index) onClicked: root.click(buttonsRightRepeater.model[index].standardButton) } } } } } function setupButtons() { buttonsLeftRepeater.model = root.__standardButtonsLeftModel() buttonsRightRepeater.model = root.__standardButtonsRightModel() if (!buttonsRightRepeater.model || buttonsRightRepeater.model.length < 2) return; var calcWidth = 0; function calculateForButton(i, b) { var buttonWidth = b.implicitWidth; if (buttonWidth > 0) { if (i > 0) buttonWidth += content.spacing calcWidth += buttonWidth } } for (var i = 0; i < buttonsRight.visibleChildren.length; ++i) calculateForButton(i, buttonsRight.visibleChildren[i]) content.minimumWidth = calcWidth + content.outerSpacing * 2 for (i = 0; i < buttonsLeft.visibleChildren.length; ++i) calculateForButton(i, buttonsLeft.visibleChildren[i]) content.buttonsRowImplicitWidth = calcWidth + content.spacing } onStandardButtonsChanged: setupButtons() Component.onCompleted: setupButtons() } 

You should use it a little differently than regular Dialog . Imagine this is a ColumnLayout (this is a slightly different example of the original question):

 ColumnLayoutDialog { id: dialog1 standardButtons: StandardButton.Ok | StandardButton.Cancel Text { text: "Hello world? " } Text { text: "Hello world!" } // Spacer. Item { Layout.fillHeight: true; } Text { text: "Goodbye world? " } Text { text: "Goodbye world!" } } 

By the way, you can change ColumnLayout to GridLayout and set the columns property, if you want. That might make sense.

little problem

It turns out that the minimum width and height of a QWindow only ensures that the dialog will not be actively resized to be smaller than its contents. This does not guarantee that the dialogue will never be smaller than its contents, because the content can grow after the dialogue is created (for example, additional elements added). To get around this, I added this function to my ColumnLayoutDialog :

 // The minimumWidth/Height values of content are accessed by the C++ class, but they // only ensure that the window isn't resized to be smaller than its content. They // don't ensure that if the content grows the window grows with it. function ensureMinimumSize() { if (root.width < content.minimumWidth) root.width = content.minimumWidth; if (root.height < content.minimumHeight) root.height = content.minimumHeight; } 

It must be called up manually when changing the contents of the dialog box. Or do it automatically, you can add this to the content rectangle:

  onMinimumHeightChanged: { if (root.height < content.minimumHeight) root.height = content.minimumHeight; } onMinimumWidthChanged: { if (root.width < content.minimumWidth) root.width = content.minimumWidth; } 
+6
source

This is a bug in QT prior to version 5.6.0. Most likely, error No. 49058 . The code from the question works as expected in QT 5.6.1 and 5.7.0.

A partial workaround for older versions is to delete lines

 width: layout.implicitWidth height: layout.implicitHeight 

and replace

 anchors.fill: parent 

with

 anchors.right: parent.right anchors.left: parent.left 

This dialog box takes into account the minimum height, and the content expands horizontally.

This is also a complete workaround, but it relies on undocumented details of the Dialog implementation, so it should be used with caution. It works in 5.5.1, 5.6.0, 5.6.1 and 5.7.0. Note also that the second element is changed to a red rectangle to make the behavior more obvious.

 import QtQuick 2.3 import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.1 import QtQuick.Controls 1.2 Dialog { visible: true standardButtons: StandardButton.Ok | StandardButton.Cancel RowLayout { id: layout // In the horizontal direction, expansion and shrinking can be achieved with anchors. anchors.left: parent.left anchors.right: parent.right // Used only for guessing the height of the Dialog standard buttons. Button { id: hiddenButton visible: false } // Repeats until the relevant parts of the dialog (parent of the parent of the RowLayout) // are complete, then overwrites the minimum width and implicit height and stops repeating. Timer { id: timer interval: 50; running: true; repeat: true; onTriggered: { if(layout.parent.parent) { var lp = layout.parent var lpp = layout.parent.parent lpp.minimumWidth = layout.implicitWidth + 2 * lpp.outerSpacing layout.buttonHeight = 2 * lpp.outerSpacing + hiddenButton.implicitHeight + lpp.spacing lp.implicitHeight = layout.implicitHeight + 2 * lpp.outerSpacing running = false } } } // The guessed space needed for the Dialog buttons. property int buttonHeight: 80 // Expand and shrink vertically when the dialog is resized. height: parent.parent ? Math.max(parent.parent.height-buttonHeight, implicitHeight) : implicitHeight Item { width: 10 height: 1 } GridLayout { columns: 2 rowSpacing: 10 Layout.fillHeight: true Layout.fillWidth: true Text { text: "Hello world? " } Text { text: "Hello world!" } Text { text: "Goodbye world? " } Text { text: "Goodbye world!" } } Rectangle { Layout.fillHeight: true color: 'red' width: 10 } } } 
+1
source

All Articles