Qt QML binding child property to parent property - qt

I want to set the ApplicationWindow to minimum width and height by the minimum width and height of the child element "mainLayout". I am having trouble to use the property of "mainLayout" in the parent QML ApplicationWindow. I tried to make the property viewable by making an alias. Not sure if it is the right solution. It does not work. But there is also no Error when I run.
My code looks like this:
main.qml
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
ApplicationWindow {
visible: true
width: 1500
height: 1200
property int margin: 11
minimumWidth: serial.mainLayout.minimumWidth + 2 * margin //this one is not working
minimumHeight: serial.mainLayout.minimumHeight + 2 * margin //this one is not working
Serial {
id: serial
anchors.fill: parent
}
}
Serial.qml
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import io.qt.serialComm 1.0
Item {
anchors.fill: parent
id: item
property alias mainLayout: mainLayout
ColumnLayout {
id: wrapper
width: parent.width/2
height: parent.height/2
ColumnLayout {
id: mainLayout
anchors.fill: parent
anchors.margins: margin
GroupBox {
id: rowBox
title: "Row layout"
Layout.fillWidth: true
RowLayout {
id: rowLayout
anchors.fill: parent
TextField {
placeholderText: "This wants to grow horizontally"
Layout.fillWidth: true
}
Button {
text: "Button"
}
}
}
GroupBox {
id: gridBox
title: "Grid layout"
Layout.fillWidth: true
GridLayout {
id: gridLayout
rows: 3
flow: GridLayout.TopToBottom
anchors.fill: parent
Label { text: "Line 1" }
Label { text: "Line 2" }
Label { text: "Line 3" }
TextField { }
TextField { }
TextField { }
TextArea {
text: "This widget spans over three rows in the GridLayout.\n"
+ "All items in the GridLayout are implicitly positioned from top to bottom."
Layout.rowSpan: 3
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
TextArea {
id: t3
text: "This fills the whole cell"
Layout.minimumHeight: 30
Layout.fillHeight: true
Layout.fillWidth: true
}
GroupBox {
id: stackBox
title: "Stack layout"
implicitWidth: 200
implicitHeight: 60
Layout.fillWidth: true
Layout.fillHeight: true
StackLayout {
id: stackLayout
anchors.fill: parent
function advance() { currentIndex = (currentIndex + 1) % count }
Repeater {
id: stackRepeater
model: 5
Rectangle {
color: Qt.hsla((0.5 + index)/stackRepeater.count, 0.3, 0.7, 1)
Button { anchors.centerIn: parent; text: "Page " + (index + 1); onClicked: { stackLayout.advance() } }
}
}
}
}
}
}
}
When I put the code in one file, it works and the ApplicationWindow does not get smaller than the minimum height and width of the child element "mainLayout". But splitting into 2 files does not work..

The reason why you are not able to use the property minimumWidth of your QML element with the id mainLayout like serial.mainLayout.minimumWidth is that it doesn't have one.
However, the QML element in question does have an attached property Layout.minimumWidth because it's an item in a ColumnLayout with the id wrapper. You already found out that you could access it through serial.mainLayout.Layout.minimumWidth.
Attached property mechanism that enables the minimumWidth for mainLayout is not the easiest one to understand. In short, it enables objects to be annotated with extra properties that are otherwise unavailable to the object but are relevant in certain circumstances. In this case minimumWidth is considered relevant for child items of ColumnLayout. Items in a ColumnLayout support these attached properties.

Related

How to implement Master Details View in Qt/QML (part 2)

I previously asked how to implement a Master Details View in Qt/QML here: How to implement a master-details view Qt/QML on an Android tablet?.
Having continued working on this, I came out with the following mockup QML layout:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtQuick.Controls 1.4
Item {
y: 50
Layout.fillHeight: true
width: appWindow.width
RowLayout {
id: mainLayout
anchors.fill: parent
ListModel {
id: navigation
ListElement {
item: "Item 1"
}
ListElement {
item: "Item 2"
}
ListElement {
item: "Item 3"
}
ListElement {
item: "Item 4"
}
ListElement {
item: "Item 5"
}
ListElement {
item: "Item 6"
}
ListElement {
item: "Item 7"
}
ListElement {
item: "Item 8"
}
ListElement {
item: "Item 9"
}
ListElement {
item: "Item 10"
}
ListElement {
item: "Item 11"
}
}
ScrollView{
Layout.fillHeight: true
verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
ListView {
id: listview
Layout.fillHeight: true
Layout.preferredWidth: 300
contentWidth: 300
model: navigation
delegate: Rectangle {
id: wrapper
width: 300
height: 50
Text {
id: itemInfo
text: item
color: "red"
}
}
}
}
Rectangle {
x: 300
y: 50
Layout.preferredWidth: appWindow.width - listview.width-4
height: appWindow.height - 50
color: "green"
border.width: 1
}
}
}
The master view is essentially a ListView with a number of items (each item represents a selectable element, which will trigger an update of the details view, which is currently represented by the green rectangle (see attached screenshot below)
At the moment I am still having a couple of issues with the following points:
How should I modify the Layout so that the ListView covers the entire screen height?
When I "scroll" through the ListView, I have noticed a lot of screen flickering? How can I minimize this?
How can I prevent the entire upper status bar (where device system information such as battery charge is shown) from being displayed?
Edit: Modified the code by adding the ListView in a ScrollView. In this case, the ScrollView's height is the same as the screen height, which is also what I wanted (minus a 50 offset at the top, see Figure below). I think that the ListView is behaving as expected and not occupying more space that its items.
What needs to be achieved now is to change the Background color of the SrollView so that it matches the ListView color. In that case it will appear as if the ListView is occupying the entire space.
In order to hide the status bar, the easiest thing to do is to specify a theme and apply it in the manifest file. Other solutions require modifying the activity and such.
In yourApp/android/res/values create a theme.xml with the following content:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="#android:style/Theme.DeviceDefault.Light.NoActionBar">
</style>
</resources>
Then in the manifest, on the same line where you added the screen orientation, add the theme:
android:theme="#style/AppTheme"
And in your main window use Window.FullScreen visibility instead of maximized.
For the layouting, it appears you could do with less. There is nothing wrong with Layout, just IMO it is more suited to standard scalable "micro" GUI elements like buttons and such rather than custom macro elements. Here is a condensed but functional example:
Row {
anchors.fill: parent
ListView {
id: lv
width: 200
height: parent.height
model: 30
delegate: Rectangle {
width: 200
height: 50
color: index == lv.currentIndex ? "lightgray" : "white"
Text {
text: "item " + index
color: "red"
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: lv.currentIndex = index
}
}
Rectangle {
anchors.right: parent.right
width: 5
height: parent.height * parent.visibleArea.heightRatio
color: "grey"
y: parent.height * parent.visibleArea.yPosition
}
}
Rectangle {
width: parent.width - lv.width
height: parent.height
color: "green"
Text {
anchors.centerIn: parent
text: "selected item n" + lv.currentIndex
color: "white"
font.pointSize: 15
}
}
}
The result:
Although it is not exactly clear the reason you offset things vertically, if you want to have the free space at the top, simply don't fill the entire parent with the root Rowelement but rather size it accordingly.
I am a bit clueless, how it comes, that you consider the ScrollView to be needed.
I removed it from your example, added clipping to the ListView and I was done.
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
ApplicationWindow
{
id: appWindow
width: 1024
height: 800
visible: true
Item {
y: 50
Layout.fillHeight: true
width: appWindow.width
RowLayout {
id: mainLayout
anchors.fill: parent
ListModel {
id: navigation
ListElement { item: "Item 1" }
Component.onCompleted: {
for (var i = 2; i < 50; i++) append({ item: 'Item' + i })
}
}
ListView {
id: listview
Layout.fillHeight: true
Layout.preferredWidth: 300
contentWidth: 300
model: navigation
clip: true //<--- Add this, so there won't be no elements outside the ListView-area
delegate: Rectangle {
id: wrapper
width: 300
height: 50
Text {
id: itemInfo
text: item
color: "red"
}
}
}
Rectangle {
x: 300
y: 50
Layout.preferredWidth: appWindow.width - listview.width-4
height: appWindow.height - 50
color: "green"
border.width: 1
}
}
}
}
There are a few things you might misunderstand:
The ListView provides no background. If you want such, you need to draw something behind it, e.g. a Rectangle
The ListView does not provide ScrollBars by itself. You need to add them like this:
ScrollBar.vertical: ScrollBar { }
The ScrollBar has no native style. And the handle will disapear by default. You can find more than one question here, on how to style a ScrollBar.
If you don't clip the ListView you will see some elements protruding the ListView and suddenly disappear. If you have nothing that covers this anyway, you should set clip: true
For your ListView to take all the height, you can simply set it to fill the height of the layout. However make sure the Layout (and its parent in your case) have the right size too. Size defaults to (0,0) for Item in QML.
ListView {
id: listview
//...
Layout.fillHeight: true
//...
}
Regarding the "flickering", you can try increasing the ListView cacheBuffer property, which corresponds to the content height, in pixels, which is preloaded. However if this is really flickering, there's probably little you can do.
Flickering appears when display is refreshed with the wrong timings regarding screen refresh rate, and typically solved by using multiple buffers and/or synchronization. QtQuick hides all this complexity and uses OpenGL for rendering, but I didn't saw (yet) any flickering on Android with recent Qt versions.
You can remove the status bar by editing the Android manifest file as explained in this other post, or worse case, through JNI.

Spacer Item in QML Layouts

I want to create a layout in QML and I'd like to add a spacer item (the bottom selected item from the image below) just as you do using widgets like so:
But I couldn't find anything to suit this on the QtQuick side of things...is it possible to have this kind of layout in QML w/o using the anchoring system?
I'd prefer the layouts approach...
You can simply use an Item with Layout.fillHeight: true :
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
ColumnLayout {
anchors.fill: parent
Button {
Layout.fillWidth: true
text: "PushButton"
}
Button {
Layout.fillWidth: true
text: "PushButton"
}
Label {
Layout.fillWidth: true
text: "TextLabel"
}
Item {
// spacer item
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle { anchors.fill: parent; color: "#ffaaaa" } // to visualize the spacer
}
}
}
EDIT: Alternatively here, you could have used a Column with no spacer item since a Column just positions its children from top to bottom and don't spread them to take all the available space.
For those coming from Qt widgets and for comparison: the intended solution in QML for this situation is the anchoring system that the question mentions. In this case, it would look as follows, and I think it's not so bad :)
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
ColumnLayout {
// anchors.fill sets all four directional anchors.
// Loosening one yields the space at the bottom.
anchors.fill: parent
anchors.bottom: undefined
// Alternative approach: only set the three anchors we want.
// anchors.top: parent.top
// anchors.left: parent.left
// anchors.right: parent.right
Button {
Layout.fillWidth: true
text: "PushButton"
}
Button {
Layout.fillWidth: true
text: "PushButton"
}
Label {
Layout.fillWidth: true
text: "TextLabel"
}
}
}

How to put attached properties to child item

Let's assume I have a component like this
RowLayout {
MyItem {
Layout.fillWidth: true
Layout.fillHeight: true
... // other properties
}
MyItem {
Layout.fillWidth: true
Layout.fillHeight: true
... // other properties
}
}
in which MyItem.qml is defined like this
Rectangle {
... // other properties
// Layout.fillWidth: true
// Layout.fillHeight: true
}
Can I put Layout.fillWidth to MyItem, so that I don't need to repeat it in RowLayout ?
Can I put Layout.fillWidth to MyItem, so I don't need to repeat it in RowLayout ?
I think the question has the answer in it: if you don't want to repeat, just use the Repeater type. The documentation states that
Items instantiated by the Repeater are inserted, in order, as children of the Repeater's parent. The insertion starts immediately after the repeater's position in its parent stacking list. This allows a Repeater to be used inside a layout.
The example which follows in the documentation uses Row but the very same approach can be applied to other layouts, e.g. RowLayout. Actually, it works for any type with attached properties as per the Repeater nature ("insert items inside parent").
Here is an example. Assume we have defined an Example type.
import QtQuick 2.5
Rectangle {
property alias text: inner.text
color: "steelblue"
Text {
id: inner
anchors.centerIn: parent
font.pixelSize: 30
}
}
We can add the layout properties to our Example type inside the Repeater, for instance like this:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window {
id: window
width: 600
height: 400
visible: true
RowLayout {
id: row
anchors.fill: parent
Repeater {
model: 6
delegate : Example {
text: index
Layout.fillWidth: true // layout options added in the delegate
Layout.fillHeight: true
Layout.alignment: Qt.AlignCenter
Layout.maximumWidth: parent.width / model.length
}
}
}
}
The model property of the Repeater can be either an array of strings or another model, as usual.
This approach is flexible enough to combine several Repeaters to create more complex structures. See for instance the following example in which Text is used to fill the screen inside a GridLayout:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
Window {
id: window
width: 600
height: 400
visible: true
GridLayout {
id: grid
anchors.fill: parent
rows: 2
columns: 6
Repeater {
model: grid.columns
Text {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.row: 0
Layout.column: index
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: index + 1 // index of the repeater as text
}
}
Repeater {
model: grid.columns
Text {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.row: 1
Layout.column: index
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: index + 7
}
}
}
}
Yes, you can do that, but it will end in an error whenever you decide to use that component in a context where it has no attached property named Layout.fillWidth or, more in general, whenever you decide not to use it as a top element within a layout.

How to ensure Button has focus when QML tab is activated, rather than TextField?

In QML, I have a Tab containing a TextField and a Button. How do I ensure the Button has focus when the tab is selected, instead of the TextField? Setting "focus:" to true and false, respectively, does not do it. In the code below, the goal is for btnRefresh to have focus when a tab is selected, instead of txtName.
main.qml:
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.2 // For TabViewStyle
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TabView {
id: tabView
anchors.fill: parent
anchors.margins: 20
tabPosition: Qt.BottomEdge
Tab {title: "Tab 1"; source: "mytab.qml"}
Tab {title: "Tab 2"; source: "mytab.qml"}
style: TabViewStyle {
frameOverlap: 1
tab: Rectangle {
color: styleData.selected ? "steelblue" :"lightsteelblue"
border.color: "steelblue"
implicitWidth: Math.max(text.width + 4, 80)
implicitHeight: 20
radius: 2
Text {
id: text
anchors.centerIn: parent
text: styleData.title
color: styleData.selected ? "white" : "black"
}
}
frame: Rectangle { color: "steelblue" }
}
}
}
mytab.qml:
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
Rectangle {
anchors.fill: parent
GridLayout {
columns: 2
anchors.fill: parent
rowSpacing: 10
RowLayout {
Layout.columnSpan: 2
Label {
id: lblName
text: "Name:"
}
TextField {
id: txtName;
text: "a name"
Layout.preferredWidth: lblName.implicitWidth * 1.5;
focus: false
}
}
TextArea {
id: textSetup
text: "Text Area"
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.fillHeight: true
}
Button {
id: btnRefresh
Layout.columnSpan: 2
Layout.alignment: Qt.AlignHCenter
text: qsTr("Refresh")
focus: true
}
}
}
Whenever you switch tabs in a TabView, a signal handler onVisibleChanged is called on the two tabs (one that disappeared and the one that appeared) since the visibility of these tabs has changed. You can try adding following code to your Tabs:
Tab {
id: tab1
title: "Tab 1"; source: "mytab.qml"
onVisibleChanged: {
if (visible) tab1.item.funcSetFocusOnButton();
}
}
Please note the way a function is called on a tab using item.
Now in "mytab.qml", you create a javascript function funcSetFocusOnButton which sets focus on your button. So your mytab.qml will have this additional code:
Rectangle {
//Your GridLayout{}
funcSetFocusOnButton() {
btnRefresh.forceActiveFocus();
}
}
Note here that the function funcSetFocusOnButton should be a direct child of your base item (rectangle here). Hope this helps!

QML ListView current item not changing with keystrokes or mouse

I have a very simple ListView.
ListView {
id: logListView
anchors.fill: parent
model: LogEntryListModel
delegate:
Text {
text: "Log Item: " + timestamp + ", " + verb
}
highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
focus: true
clip: true
}
It shows the model fine and highlights the first item. It does not move the highlight when I click on another item nor when I use the arrow keys. I know how to control the highlighted item manually by adding event handlers but I see references in the docs to automatic handling of the selectedItem. I was wondering:
Does QML provide an automatic changing of the selected item highlighting? What do I need to add to turn it on?
The keyboard handling is done automatically:
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 400
height: 400
ListView {
id: logListView
anchors.fill: parent
model: 10
delegate: Text {
text: "Log Item: " + modelData
}
highlight: Rectangle {
color: "lightsteelblue";
radius: 5
}
focus: true
clip: true
}
}
If using the up and down arrow keys does not change the selected item for you, using the code above, then it's a bug.
Using a mouse to select items is not handled by default, however; only flicking/dragging of the list is. It's easy to add, though:
import QtQuick 2.0
import QtQuick.Controls 1.1
Rectangle {
width: 400
height: 400
ListView {
id: logListView
anchors.fill: parent
model: 10
delegate: Text {
text: "Log Item: " + modelData
MouseArea {
anchors.fill: parent
onClicked: logListView.currentIndex = index
}
}
highlight: Rectangle {
color: "lightsteelblue";
radius: 5
}
focus: true
clip: true
}
}

Resources