ListModel in repeater - qt

When using a ListModel for a repeater, if a property is not set in the first element of the model, then it is not considered in the following elements. Why?
import QtQuick 2.7
import QtQuick.Controls 2.3
Item{
id: root
property var labels: ListModel{}
Button{
text: 'create labels'
onClicked:{
root.labels.append({})
root.labels.append({name: '2'})
root.labels.append({name: '3'})
}
}
Column{
x: 10
y: 200
spacing: 2
Repeater{
model: root.labels
Button{
width: 120
height: 30
text: model.name
}
}
}
}
This code is ok:
....
onClicked:{
root.labels.append({name: '1'})
root.labels.append({})
root.labels.append({name: '3'})
}
....

It doesn't work because the roles of the ListModel get evaluated based on its first element. The property of the first element defines the roles of the model. If you had other properties in the following elements, those will be ignored.
That behavior is the default one when the dynamicRoles property is not set to true.
When set to true, the model will recalculate its roles for each inserted element and emit a modelReset every time the roles change. This is costly and not generally needed so its disabled by default.

Related

Property contentItem in Qml

The following Qml code gives the following output (expected):
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Window 2.11
Window {
height: 200
width: 200
visible: true
Button {
id: root
text: "Text"
anchors.centerIn: parent
Item {
anchors.fill: parent
Text {
text: "Item.Text"
color: "red"
}
}
}
}
The following code (using contentItem) produces a different output:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Window 2.11
Window {
height: 200
width: 200
visible: true
Button {
id: root
text: "Text"
anchors.centerIn: parent
contentItem: Item {
anchors.fill: parent
Text {
text: "Item.Text"
color: "red"
}
}
}
}
The Qt documentation is not very clear, at least for me. The question is what does the property contentItem do? When it should be used?
Short answer: The contentItem is meant for customizing the control and replaces the existing implementation of the visual foreground element by your text.
Long answer:
A Quick Item has a so called "default property" - the data property. By definition, if you add an item as child of another item, it is instead assigned to the default property. Which means the following example:
Item {
Text { text: "test1"}
}
Is actually identical to:
Item {
data: [
Text { text: "test2"}
]
}
If you know look at your example, in the first variant, you simply append a child item to the root button. Since no further information is given, it is placed at the coordinates (0,0) within it's parent.
The contentItem property however is defined in the documentation as follows:
This property holds the visual content item.
In case of a Button it is an internally used Label to display the text property of the button. It exists to modify the appereance of the button.
In your second example, you "customize" the button by replacing the internal label with your custom Text - but without any code to properly position or fill the item. The correct way to declare a content item can be found in the customization guide:
Button {
id: control
text: qsTr("Button")
contentItem: Text {
text: control.text
font: control.font
opacity: enabled ? 1.0 : 0.3
color: control.down ? "#17a81a" : "#21be2b"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
// ...
}
You could either define it as part of a custom style, or create a MyButton.qml where do exactly this and can then use MyButton in other QML files, giving you a custom styled button whilest keeping the API intact (like beeing able to set the text via the text property etc.)
I hope this was sufficient enough to help you understand how it works.

QML - setting width and height has no effect

Quick Controls 2, Qt 5.10.
I created table control based on ListView item.
One of its columns is displayed using this component:
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
Item
{
id: root
implicitHeight: item1.implicitHeight
ColumnLayout
{
id: item1
visible: !model.finished
width: parent.width
RowLayout
{
Label
{
text: "38%"
Layout.alignment: Qt.AlignLeft
}
Label
{
text: "Paused"
Layout.alignment: Qt.AlignRight
}
}
ProgressBar
{
from: 0; to: 100; value: 40
// Variant A
/*Layout.preferredWidth: 30
Layout.preferredHeight: 10*/
// Variant B
width: 30
height: 10
}
}
}
Can somebody please explain me why Variant B does not "work". I may specify any width/height values or even just remove them - no effect. Variant A (Layout.preferredWidth/Layout.preferredHeight) works fine.
Variant A:
Variant B:
The ...Layout items alter the dimensions of their children. That is their purpose, and the behavior is documented.
As per documentation of the ColumnLayout Layout.preferredWidth the behavior is:
This property holds the preferred width of an item in a layout. If the preferred width is -1 it will be ignored, and the layout will use implicitWidth instead. The default is -1.
Since the default is -1, it will take the implicitWidth - it is not written "and use width instead".
If you don't want to use Layout don't use Layout. You can just take Column instead.

ListView model function

I am just getting started in Qt, and trying to make function which operates ListView model's elements.
I have custom made button in "myButton.qml" which has states like "normal", "pressed", "selected", etc.
ListView is in "main.qml". Structure is like this:
ListView{
//...
model: nameModel
delegate: myButton {
//...
}
}
So here is my goal: this list of buttons should act like group of radiobuttons - only one can have selected state and selected state is when you press button. I think that I should have click handler and a function that calls on button click. Function should check the list of buttons and if one button was selected before function just changes its state to "Normal".
So I have no idea of how to write this func and where should I place it. I read Qt docs but still no idea.
A possible easy way to solve this problem is by exploiting ExclusiveGroup. As discussed in the documentation, support to this type can be added to any type:
It is possible to add support for ExclusiveGroup for an object or control. It should have a checked property, and either a checkedChanged, toggled(), or toggled(bool) signal. It also needs to be bound with ExclusiveGroup::bindCheckable() when its ExclusiveGroup typed property is set.
You can define an ExclusiveGroup at the ListView level and implement the required logic in the ListView delegate. By binding the delegate ExclusiveGroup property to the ExclusiveGroup of the ListView you should achieve what you want, without the need of a function that crawls the model.
Final toy example to demonstrate the usage:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: root
visible: true
width: 200
height: 500
ListView {
anchors.fill: parent
model: 10
spacing: 20
ExclusiveGroup { id: ex } // the group for all the delegate
delegate: Rectangle {
id: delegate
width: ListView.view.width
height: 30
color: checked ? "yellow" : "steelblue"
// code to have exclusive behaviour
property bool checked: false
property ExclusiveGroup exclusiveGroup: ex
onExclusiveGroupChanged: {
if (exclusiveGroup)
exclusiveGroup.bindCheckable(delegate)
}
// mouse area to trigger the property change
MouseArea {
anchors.fill: parent
onClicked: checked = true
}
}
}
}

ListView signals and slots for menu elements

I'm trying to implement some sort of custom Menu with custom elements. The ultimate goal is to create some sort of popup menu with text and icons. But during creation I faced with some issues. I can show 2 primary problems:
There is a strange menu element with title Hello world at the first position (looks like it's read title of application window):
From time to time I'm getting errors like qrc:/BreezeQuickMenu.qml:45: TypeError: Property 'clicked' of object QQuickListView(0x1120830) is not a function
Here is my actual code:
main.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Hello World")
width: Screen.width
height: Screen.height
visible: true
id: win
color: brPalette.normalBackground
BreezeQuickMenu{
id: brMenu
x: 490
y: 199
width: 128
height: 256
palette: brPalette
menuFont.pointSize: 16
BreezeQuickMenuItem{
title: "Item 1"
onClicked: mbox.show()
}
BreezeQuickMenuItem{
title: "Item 2"
}
BreezeQuickMenuItem{
title: "Item 3"
}
}
}
BreezeQuickMenu.qml
import QtQuick 2.4
Item {
id: root
property BreezeQuickPalette palette: BreezeQuickPalette
property alias currentIndex: menuList.currentIndex
property font menuFont
property bool menuVisible: false
implicitWidth: 128
implicitHeight: menuList.height
ListView{
id: menuList
anchors.fill: parent
model: root.children
clip: true
delegate: Component {
id: menuItem
Rectangle {
id: menuElement
property bool isCurrentItem: ListView.isCurrentItem
anchors {
left: parent.left
right: parent.right
}
color: palette.normalBackground
height: menuText.font.pixelSize*1.2
Text {
id: menuText
anchors.fill: parent
text: title
color: palette.normalText
font: menuFont
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
menuList.currentIndex = index
menuList.model[index].clicked()
}
}
}
}
}
}
BreezeQuickMenuItem.qml
import QtQuick 2.4
Item {
id: root
property string title: "Menu Element"
signal clicked
}
As you can see I'm trying to implement menu list and menu items with their own signals. I have 2 questions:
how can I properly get rid of using title property of parent element, since I need to read title property of childrens
what is the correct approach of using signals and slots in menu elements to avoid above error?
Please help me to understand. Full project can be pulled here:
git clone git://git.code.sf.net/p/breezequick/code breezequick-code
The problem with the signal is related to its declaration. Signals are always declared as a function would be: with a signature. In other words, a signal without parameters has the form
signal <signal_name>()
That's also why you got the error "is not a function". Apart from that, the usage of signals/signal handlers is correct. Anyhow, reading carefully the documentation wouldn't hurt. This page covers in detail the argument.
Coming to the other problem, you made the wrong assumption: anything that is declared inside a component is part of the children of the component itself. Here you declared a BreezeQuickMenu which has a child ListView. When you use it and add the BreezeQuickMenuItems, you add them to the same set to which the ListView belongs. In the end you have four elements in the children property. Also, by adding the ListView to itself through the model you mess up things to the point that a totally unrelated string is rendered.
There are several ways to handle Items as model members for a view, inclusing VisualItemModel and using object Instanced as models. However, by skimming your code, it is clear that you want to define a component which adds menu items in a declarative fashion. Using children is not sufficient in this case. You also need the default property:
An object definition can have a single default property. A default property is the property to which a value is assigned if an object is declared within another object's definition without declaring it as a value for a particular property.
Hence you can define the default property for your BreezeQuickMenu and exploit it to obtain the desired children for your list. A common approach would be the following (code simplified):
import QtQuick 2.4
Item {
id: root
property BreezeQuickPalette palette: BreezeQuickPalette
property alias currentIndex: menuList.currentIndex
// default declaration (1)
default property alias contents: addItem.children
// Item to which the inner declared meantime will belong (2)
Item {
id: addItem
}
property font menuFont
property bool menuVisible: false
implicitWidth: 128
implicitHeight: menuList.height
ListView{
id: menuList
anchors.fill: parent
model: contents // usage of the default property (3)
clip: true
delegate: Rectangle {
// your current delegate code
}
}
}
The basic idea is to exploit also property alias: basically in (1) we are saying that "all the Items declared inside BreezeQuickMenu are automatically children of addItem which is an inner declared Item (2). In this way the ListView is kept apart whereas all the BreezeQuickMenuItem are gathered together, under addItem children property. At this point, it is sufficient to use the same children property as the model (3) for the ListView and that's it.

How to set initial value of a custom slider in qml?

I am using Qt 5.4.1. I have made a custom slider element to be used in other qml components like so:
Slider.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
Item {
id: root
width: 150
height: 30
property int val: slider.value
property int maxVal: slider.maximumValue
property int minVal: slider.minimumValue
property int step: slider.stepSize
Slider {
id: slider
anchors.margins: 20
stepSize: step
maximumValue: maxVal
minimumValue: minVal
style: customStyle
// onValueChanged: print("From Slider.qml" ,value)
}
Component {
id: customStyle
SliderStyle {
handle: Rectangle {
width: 20
height: 12
antialiasing: true
color: Qt.lighter("#468bb7", 1.2)
}
groove: Item {
implicitHeight: root.height
implicitWidth: root.width
Rectangle {
height: 8
width: parent.width
anchors.verticalCenter: parent.verticalCenter
color: "#847878"
opacity: 0.8
Rectangle {
antialiasing: true
radius: 1
color: "#1a0d0d"
height: parent.height
width: parent.width * control.value / control.maximumValue
}
}
}
}
}
}
And in another file test.qml I am using this slider like so
test.qml
import QtQuick 2.3
Rectangle {
id: test
width: 640; height: 480
Slider {
id: slider
width: 300
height: 30
anchors.centerIn: parent
maxVal: 1000
minVal: 0
step: 50
val: 500 // when commented out, onValChanged is triggered on sliding
onValChanged: print(val)
}
}
I want to set the slider to an initial value when instantiated in test.qml, using the property val. But when I set initial value, onValChanged does not get triggered when sliding the slider. But when I comment that line out (val: 500), onValChanged is triggered when the slider is slid, but the slider starts with initial value of 0 which I don't want. I don't understand what I am doing wrong here!
The setting of the property val to a specific value overrides the binding, as defined in your slider component. Once the binding is lost, any update of the slider is not delivered to val resulting in the behaviour you experienced. On the other way around, if you don't set the property the binding is maintained, i.e. as the slider value changes the value of val changes accordingly, triggering the signal.
That's not the way to go in this case, also because you are adding a set of properties which simply exposes inner properties of Slider. Just use properties alias:
Property aliases are properties which hold a reference to another property. Unlike an ordinary property definition, which allocates a new, unique storage space for the property, a property alias connects the newly declared property (called the aliasing property) as a direct reference to an existing property (the aliased property).
Rewrite your properties inside Slider.qml as follows:
property alias val: slider.value
property alias maxVal: slider.maximumValue
property alias minVal: slider.minimumValue
property alias step: slider.stepSize
This way val is slider.value and setting it to 500 directly affect the slider without breaking any binding.
On a sidenote, you can also for example write
property alias maximumValue: slider.maximumValue
i.e. expose inner properties with their very same name to maintain the consistency in API naming.

Resources