QML TableView with delegated Checkbox has problems to show the - qt

I created a dummy ListModel and loaded it to the TableView:
ListModel {
id: testModel
ListElement {
status: false
}
}
function testFunction() {
for (var i = 0; i < 45; i++) {
testModel.append({});
}
}
TableView {
model: testModel
TableViewColumn {
role: "status"
title: "Activation On/Off"
delegate: Checkbox {
id: idDelegatedCheckbox
}
}
}
I pretend to use the Checkbox as delegate component to select the items in the TableView. I check the first item in the table in the following way:
First item selected
Then I scroll down and return to the top and the items that I had selected disappear and other item is selected:
Different item selected
Do you know why?
Thanks in advance.

TableView delegates are instantiated as needed and may be recycled or destroyed at any time. As with any Qt Quick item views (ListView, GridView, PathView, TableView...), state should never be stored in a delegate. Hence, your CheckBox delegate must store its "checked" state in the model:
delegate: Checkbox {
id: idDelegatedCheckbox
checked: model.checked // read from the model when created or recycled
onCheckedChanged: model.checked = checked // write to the model when checked or unchecked
}

Related

Qml: How to set a property to all GridView items

I am trying to make a change to all items of a GridView.
I have tried to iterate through either the model or the grid, I looked at similar examples on the web, but everything I try ends with Cannot read property 'buttonText' of undefined.
It seems to me that the problem is that the interpreter can't figure out that the item from the grid or model is a Button. But I don't know how to cast it.
If I change the log to only display the item, not any property, (see code snippet), it seems that it knows it is an item... see my experiments below.
The only thing I can make work is set a property (or call a signal, or a function) from the delegate. But that only affects one grid item, not all.
How can I set a property on every item of the grid ? Alternatively, how can I send a signal, or call a function, on every item?
My experiments are in function changeEverythingFunction()
file: Button.qml
Item
{
id: itemButton
signal changeEverything
property int buttonIndex
property string buttonText
...
}
file: Model.qml
Item
{
id: modelItem
ListModel
{
id: listModel
}
property int buttonCount: listModel.count
function changeEverythingFunction()
{
// for (var i = 0; i < buttonCount; i++)
// listModel.setProperty(i, buttonText, "abc")
for(var childIndex in gridItems.contentItem.children)
{
console.log(listModel.get(childIndex).buttonText) // Cannot read property 'buttonText' of undefined
console.log(gridItems.contentItem.children[childIndex].buttonText) // Cannot read property 'buttonText' of undefined
console.log(gridItems.contentItem.children[childIndex]["buttonText"]) // undefined (I saw this in a SO example)
var item = gridItems.contentItem.children[childIndex]
console.log(item) // qml: QQuickItem(0xe496370)
}
}
MouseArea
{
....
Rectangle
{
...
GridView
{
id: gridItems
anchors.fill: parent
clip: true
model: listModel
delegate: Item
{
id: buttonDelegate
Button
{
buttonIndex: gridId
buttonText: itemText
onChangeEverything:
{
changeEverythingFunction();
}
}
}
}
}
}
}
Your approach is in the opposite direction: Your approach is to obtain the item of the view and modify it, but the approach that Qt points out is that the view reflects the information of the model and modifies it when necessary.
The following is a simple example where every time you press on the button with "change me" text increasing the number it shows, but if you press the button with "change all" text it will change all the numbers. As it is observed everything is done through the model, not through the view that are only used to display information or receive user interaction.
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
Window {
visible: true
width: 640
height: 480
ListModel{
id: listmodel
}
function changeAll(){
for(var i=0; i< listmodel.count; ++i){
listmodel.setProperty(i, "number", listmodel.get(i).number + 1)
}
}
GridView{
id: grid
anchors.fill: parent
clip: true
model: listmodel
cellHeight: 120
cellWidth: 120
delegate: Item {
width: grid.cellWidth; height: grid.cellHeight
Column {
anchors.fill: parent
Text { text: model.number; anchors.horizontalCenter: parent.horizontalCenter }
Button{text: "change me"; onClicked: model.number +=1}
Button{text: "change all"; onClicked: changeAll()}
}
}
}
Component.onCompleted: {
for(var i=0; i < 10; ++i){
listmodel.append({"number": 0});
}
}
}

QML - How to get children in an item?

I had a problem with the item in QML. I wanna get children of an item but it seems working in the first element.
The detail code is below:
I have a gridview with a list custom component AAA_Styles
GridView{
id: grdViewDeviceControl
clip: true
interactive: true
ScrollIndicator.vertical: ScrollIndicator{}
cellWidth: 200
cellHeight: 300
model: ListModel{}
delegate: Item {
width: grdViewDeviceControl.cellWidth
height: grdViewDeviceControl.cellHeight
AAA_Styles{
id: deviceControl
objectName: "deviceControl"
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
name: Names
subname: Subnames
}
}
My custom AAA_RTS is a QML component have some functions such as:
- function_a()
- function_b()
I added 2 items into model using
grdViewDeviceControl.model.append()
I ensure the model have data that I added because It appeared in my design and the count of gridview is 2 elements
console.log(grdViewDeviceControl.count) //the result is 2
After that, I tried to get each element to access functions that they are available using a method in signal onRelease of a button:
onReleased: {
console.log("number of item: " + grdViewDeviceControl.count)
var myRTS = grdViewDeviceControl.contentItem.children[0]
console.log(myRTS)
console.log(myRTS.children[0])
myRTS = grdViewDeviceControl.contentItem.children[1]
console.log(myRTS)
console.log(myRTS.children[1])
}
The result on console:
qml: number of item: 2
qml: QQuickItem(0x9828f60)
qml: AAA_Styles_QMLTYPE_0_QML_112(0x9829070, "deviceControl")
qml: QQuickItem(0x5554140)
qml: undefined
With the first element grdViewDeviceControl.contentItem.children[0], I access function_a or function_b successful but when I using the second the error appeared
TypeError: Cannot call method 'function_a' of undefined
So can anyone tell me why I wrong and how to fix it?
Many thanks for any help!
Do not try to access directly to the child items. Use delegate IDs, signals and slots instead:
Give a "delegate ID" to all your delegates through the model of your GridView.
In your GridView, add signals that will be used to broadcast to all the delegates the following things:
The "delegate ID" of the delegate that you want it to execute the function.
The arguments for the AAA_Styles function.
In your delegate, add one slot per AAA_Styles function. Each slot will execute the AAA_Styles function only if the broadcasted delegate ID is the delegate's: if (broadcastedID === delegateID) { function_ab() }.
When you want to execute function_a() or function_b() in a delegate, broadcast the delegate ID and the function arguments through the corresponding GridView signal (in onReleased, for example).
The following piece of code sums up what I have just described to you. If it does not work put the delegate in a separated QML file. This should work for good:
// Your grid
GridView {
id: grdViewDeviceControl
clip: true
interactive: true
ScrollIndicator.vertical: ScrollIndicator {}
cellWidth: 200
cellHeight: 300
model: ListModel {
/*
Example of list element:
ListElement { m_uuid: "{element-uuid}" }
*/
}
delegate: Item {
width: grdViewDeviceControl.cellWidth
height: grdViewDeviceControl.cellHeight
AAA_Styles {
id: deviceControl
objectName: "deviceControl"
anchors.centerIn: parent
name: Names
subname: Subnames
}
// The delegate ID
property string delegate_id: m_uuid
// Broadcast receivers
function delfunc_a(uuid, argA0) {
if (uuid === this.delegate_id) {
deviceControl.function_a(argA0)
}
}
function delfunc_b(uuid, argB0, argB1) {
if (uuid === this.delegate_id) {
deviceControl.function_b(argB0, argB1)
}
}
// Connecting broadcasters to receivers
Component.onCompleted: {
grdViewDeviceControl.exec_a.connect(this.delfunc_a)
grdViewDeviceControl.exec_b.connect(this.delfunc_b)
}
Component.onDestruction: {
grdViewDeviceControl.exec_a.disconnect(this.delfunc_a)
grdViewDeviceControl.exec_b.disconnect(this.delfunc_b)
}
}
// Your broadcasters
signal exec_a(string uuid, int argA0)
signal exec_b(string uuid, bool argB0, string argB1)
}
// Somewhere else in your code:
onReleased: {
/*
* Only the delegate whose ID is "{a-given-uuid}"
* will execute deviceControl.function_a(3):
*/
grdViewDeviceControl.exec_a("{a-given-uuid}", 3)
/*
* Only the delegate whose ID is "{another-given-uuid}"
* will execute deviceControl.function_b(true, "U got style!"):
*/
grdViewDeviceControl.exec_b("{another-given-uuid}", true, "U got style!")
}

Does QML TreeView support signal layoutChanged emitted from model?

I have a model that works well with QTreeView. In the model I implemented a sort that looks like this:
void SimpleTreeModel::sort(Node* sortedNode)
{
emit layoutAboutToBeChanged(QList<QPersistentModelIndex>(), VerticalSortHint);
QModelIndexList oldIndices = persistentIndexList();
Node::SortType sortType = Node::Down;
//sort starting node
sortedNode->sortChildren(sortType);
QModelIndexList newIndices;
newIndices.reserve(oldIndices.size());
for(const auto &i : oldIndices)
{
Node* node = const_cast<Node*>(nodeFromIndex(i));
QModelIndex index = indexFromNode(node);
newIndices.push_back(index);
}
changePersistentIndexList(oldIndices, newIndices);
QModelIndex startingIndex = indexFromNode(sortedNode);
emit layoutChanged({ QPersistentModelIndex(startingIndex) }, VerticalSortHint);
}
when I call this function, QTreeView updates the view, but TreeView in QML don't do this.
QML TreeView usage:
TreeView
{
model: treeModel
TableViewColumn
{
title: "Title"
role: "title"
width: 700
}
}
What am I doing wrong? Why the view does not update the layout of the elements after sorting?
I think you need to delegate the tree view item. Data is provided to delegate.
Try changing your QML TreeView as shown below by adding itemDelegate
TreeView
{
model: treeModel
itemDelegate: Item {
Text {
color: styleData.textColor
text: styleData.value
}
}
TableViewColumn
{
title: "Title"
role: "title"
width: 700
}
}
Look into below link to understand the importance of delegate, between model and QML view. There is an image which easily explains.
http://doc.qt.io/qt-5/qtquick-modelviewsdata-modelview.html
Delegate - dictates how the data should appear in the view. The
delegate takes each data in the model and encapsulates it. The data is
accessible through the delegate.

QML ListView - change all but current item

I'm following this tutorial (without the flickable content in each entry) for Qt 4.8 while using Qt 5.7 with QtQuick 2.0. The way the ListView there works is as follows:
User clicks on item in list
Alternative (detailed) view of item is displayed
User has to click on Close button in detailed view to reset the state of entry to its default compact view.
This leads to a clutter where at some point if the user clicks on all items in which case all will be shown in their full view. Having the user click on the Close button every time he/she opens a detailed view also is (omho) not that handy.
I've altered the entry to close when the user clicks on the view. I'm also trying to prevent this clutter and achieve a more (omho) flowing behaviour:
User clicks on item in list
Alternative view of item is displayed
User clicks on detailed view to reset state of entry to its default compact view OR
User clicks on another entry and all currently in detailed view entries are reset to their compact view
Currently I'm looping through my ListView's contentItem.children[loop_index] and setting the state to "" ("Details" = show detailed view | "" = show compact view). Due to the way ListView works (loading/unloading delegates on demand) this is quite unreliable and I often get an undefined reference when I try to access the state of other delegates. The following MouseArea, which I'm using to do all that, is part of every delegate:
// state is a QML `State` that is bound to the delegate (see below for the details on it)
MouseArea {
anchors.fill: background
onClicked: {
// Iterate through all other entries and close them
for (var entry = 0; entry < listView.count; ++entry) {
if(listView.contentItem.children[entry] !== gestureEntry) {
console.log("Hide other element");
listView.contentItem.children[entry].state = ""; // IT FAILS HERE (SOMETIMES)
}
}
// Change view of current entry
if(gestureEntry.state === "Details") {
gestureEntry.state = "";
console.log("Hiding details")
}
else {
gestureEntry.state = "Details";
console.log("Showing details");
}
}
}
with state being a delegate's state:
states: State {
name: "Details"
PropertyChanges { target: background; color: "white" }
PropertyChanges { target: gestureImage; width: 130; height: 130 } // Make picture bigger
PropertyChanges { target: gestureEntry; detailsOpacity: 1; x: 0; y: 0 } // Make details visible
PropertyChanges { target: gestureEntry; height: listView.height } // Fill the entire list area with the detailed view
}
I'm thinking that the state information can be stored inside the ListModel itself making it possible to iterate through the model's contents (which are always there unlike the contents of the delegates) however I don't know how to automatically update my list (and the currently visible/invisible delegates) when an entry changes in the model. From what I've found so far it seems not possible to do that since the ListView doesn't actively monitor its ListModel.
Is this indeed the case? If yes, then is it possible to go around this problem in a different way?
Why don't you use the currentIndex property of your ListView?
Just modify your delegate like this:
Item {
id: gestureEntry
...
state: ListView.isCurrentItem?"Details":""
...
MouseArea {
anchors.fill: background
onClicked: {
if(listView.currentIndex == index)
listView.currentIndex = -1
else
listView.currentIndex = index
}
}
}
EDIT:
The only issue with the solution above is that - upon loading - an entry in the ListView is preselected which automatically triggers the detailed view of that entry. In order to avoid that the following needs to be added to listView:
Component.onCompleted: {
listView.currentIndex = -1;
}
This ensures that no entry will be preselected.
guess it is an issue because you stored a state in your delegate. You should not do this as described in the delegate-property (Link), because the delegates get reused when they get out of view.
At least you should use a when: ListView.isCurrentItem in the State and depend on a value of the ListView. So only your current delegate is maximized. Then in the MouseArea only set `ListView.view.currentIndex = index'. Don't change the state manually in the function!
I ran in the same trouble, removed the states completely and just used the attached property ListView.isCurrentItem. But binding the state to a Value from the ListView should also work, because it's not stored in the delegate.
Minimal example:
import QtQuick 2.0
Item {
width: 800
height: 600
ListView {
id: view
anchors.fill: parent
model: 3
spacing: 5
currentIndex: -1
delegate: Rectangle {
id: delegate
color: ListView.isCurrentItem ? "lightblue" : "green" // directly change properties depending on isCurrentItem
height: 100
width: 100
states: State {
name: "maximized"
when: delegate.ListView.isCurrentItem // bind to isCurrentItem to set the state
PropertyChanges {
target: delegate
height: 200
}
}
MouseArea {
anchors.fill: parent
//onClicked: delegate.ListView.view.currentIndex = model.index // if only selection is wanted
onClicked: {
//console.debug("click");
if (delegate.ListView.isCurrentItem)
{
delegate.ListView.view.currentIndex = -1;
}
else
{
delegate.ListView.view.currentIndex = model.index;
}
}
}
Text {
anchors.centerIn: parent
text: index
}
}
Text {
text: "CurrentIndex: " + parent.currentIndex
}
}
}

QML dynamic list of widgets

I have a QStringList property, and I basically want to turn that into a group of radio buttons dynamically, so that when the QStringList property changes the number of buttons and their labels is automatically updated.
I can sort of do it with a ListView, but it has problems:
It's not really a desktop widget so you have all the mobile bounciness.
I can't get the ListView selection and the radio button checks to interact nicely.
Here's my attempt anyway. I'd ideally like to do it without a ListView though:
ListView {
id: myList
orientation: ListView.Horizontal
ExclusiveGroup {
id: myListExclusiveGroup
}
Component {
id: myDelegate
RadioButton {
text: modelData
onCheckedChanged: {
if (checked)
myList.currentIndex = index
}
exclusiveGroup: myListExclusiveGroup
}
}
model: myListOfStrings
delegate: myDelegate
focus: true
}
Thanks to koopajah, I changed it to use Repeater and it works now. Note that it seems Repeater adds everything to the end of its parent's children, which means you can't rely on its position in a layout - you have to put it inside another layout, for example like this:
ExclusiveGroup {
id: myListExclusiveGroup
}
RowLayout {
Repeater {
id: myList
RadioButton {
text: modelData
exclusiveGroup: myListExclusiveGroup
}
model: myListOfStrings
focus: true
}
}

Resources