How to display hierarchical tables - qt

I have a custom model that inherits from QAbstractItemModel. It implements a two-deep list (first layer has n elements, each element has m sub-elements). I can successfully bind a GridView's model to the first layer. How would I go about binding a nested ListView to display the second layer of elements?
GridView {
model: myModel
delegate: ColumnLayout {
Text { text: "First layer" }
ListView {
model: // What do I put here?
delegate: Text { text: "Second layer" }
}
}
}

You could use a DelegateModel.
The DelegateModel has a property called rootIndex which you can use to tell the ListView to shift the root of the model to the start of one of the sub-lists.
ListView {
model: DelegateModel {
model: myModel
delegate: {...}
rootIndex: index
}
}
Where index is the attached propery from your GridView which points to the parent entry for your sub-entries.

Related

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 TableView with delegated Checkbox has problems to show the

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
}

Fill QML ListModel from nested models

I have the following C++ model structure:
Manager // QAbstractListModel
↪ Slot // QAbstractListModel
↪ Processor // QAbstractListModel
↪ name // Q_PROPERTY
I pass only the Manager reference to QML on instantiation. I need to fill the ComboBox with Processor names but I don't know how to fill up with this nested structure.
Here is the code I plan to have (but is not working now):
ComboBox {
model: Repeater {
model: manager
delegate: Repeater {
model: slot
delegate:Repeater {
model: processor
delegate: ListModel {
ListElement {text: name}
}
}
}
}
}
I know that delegates are for specifying how to display data (and that's why ComboBox doesn't have this one), but I'm out of ideas how to implement this correctly.
So my question is: how to fill up a ListModel recursively?
I came up with the following solution to recursively fill a ComboBox:
ComboBox {
id: comboBox
model: ListModel {}
textRole: "processorName"
Repeater {
model: manager
delegate: Repeater {
model: slot
delegate: Repeater {
model: processor
Component.onCompleted:
comboBox.model.append(
{"processorName": model.Processor.Name}
);
}
}
}
}
Add to your QAbstractListModel role that returns another QAbstractListModel.

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
}
}

Can Repeater delegate in Qml be made to behave in a generic way to the given Items?

{New to Qml (quick 2.0 using Qt 5.1 beta) and learning}.
I wanted to know if such an idiom would be possible in Qml:
Below I have objLeftColumn which expects its children to expose a boolean m_bIsSelected and a MouseArea alias m_mouseProperty and uses them to make the collection of such children mutually exclusive, ie., only one of them can be in selected state. The followin works fine but I need to repeat it every time I want and specially if I wanted it for Row etc.
Column {
id: objLeftColumn
property int m_iLastButtonClicked: -1
property int m_iCurrentButtonClicked: -1
onM_iCurrentButtonClickedChanged: {
if(m_iLastButtonClicked != -1) {
objLeftColumn.children[m_iLastButtonClicked].m_bIsSelected = false
}
m_iLastButtonClicked = m_iCurrentButtonClicked
}
Repeater {
id: objLeftColumnRepeater
model: 5
delegate: ABCD {
id: objABCD
m_mouseProperty.onClicked: {
if(m_bIsSelected) {
objLeftColumn.m_iCurrentButtonClicked = index
}
else {
objLeftColumn.m_iLastButtonClicked = -1
objLeftColumn.m_iCurrentButtonClicked = -1
}
}
}
}
}
Can I write a generic objLeftColumn (in a separate qml file) that could arrange the given Items in Column while aslo dealing with exclusivity of their selection?
The idea is instead of giving the component to the delegate right there an then, I'll give it later and for each instantiation of the component (depending on numeric value of model above and below) the delegate: in Repeater should behave similarly.
eg., in psedo code:
in Exclusive.qml:
Column {
id: objLeftColumn
property int m_iLastButtonClicked: -1
property int m_iCurrentButtonClicked: -1
property alias m_delegate: objLeftColumnRepeater.delegate
onM_iCurrentButtonClickedChanged: {
if(m_iLastButtonClicked != -1) {
objLeftColumn.children[m_iLastButtonClicked].m_bIsSelected = false
}
m_iLastButtonClicked = m_iCurrentButtonClicked
}
Repeater {
id: objLeftColumnRepeater
model: 5
onItemAdded: {
//state of item can be manipulated but want to
//add behaviour to the item eg:
/*item {
m_mouseProperty.onClicked: {
//do something
}
}*/
}
}
}
in SomeOther.qml:
Exclusive {
model: 5
delegate: ABCD
}
Exclusive {
model: 9
delegate: DEFG
}
etc..So this way Column in Exclusive is more generic and can be called with any Item assigned to its delegate and will behave similarly. Is this possible in qml
This needs a bit of trickery to be solved, I can think of two ways:
Use the JS connect() function to manually create the connections. Something like this:
Repeater {
id: objLeftColumnRepeater
model: 5
onItemAdded: {
item.m_mouseProperty.onClicked.connect(function() {
console.log("Clicked!");
});
}
}
Wrap the delegate into an Item by using a Loader, and use aConnections element for the connection. Something like this:
property Component delegate
Repeater {
id: objLeftColumnRepeater
model: 5
delegate: Item {
Loader {
id: loader
sourceComponent: delegate
}
Connections {
target: loader.item.m_mouseProperty
onClicked: console.log("Clicked")
}
}

Resources