QML TableView rowDelegate styleData.row not defined - qt

I am trying to create a custom row delegate for a TableView. according to the Documentation, the rowDelegate should have access to a property called styleData.row. However, when I'm try to access this property, it is undefined. I used the debugger to check whats inside the styleData, and the row is missing:
My code is very simple:
TableView {
width: 500//DEBUG
height: 300//DEBUG
model: ListModel {
ListElement {
lectureName: "Baum1"
}
ListElement {
lectureName: "Baum2"
}
ListElement {
lectureName: "Baum3"
}
ListElement {
lectureName: "Baum4"
}
}
rowDelegate: HeaderRowDelegate {//simply a Rectangle with an in property called "modelRow"
id: rowDelegate
modelRow: {
var data = styleData;
return data.row;
}
}
TableViewColumn {
id: c1
role: "lectureName"
title: "TEST"
}
}

You don't have to assign it yourself: as the HeaderRowDelegate component is assigned to the rowDelegate property of the TableView, your component has already access to the styleData.row property.
Here's an example:
main.qml
import QtQuick 2.4
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.2
import QtQuick.Controls 1.3
ApplicationWindow {
id: app
title: qsTr("Test")
width: 800
height: 600
TableView {
width: 500//DEBUG
height: 300//DEBUG
model: ListModel {
ListElement {
lectureName: "Baum1"
}
ListElement {
lectureName: "Baum2"
}
ListElement {
lectureName: "Baum3"
}
ListElement {
lectureName: "Baum4"
}
}
rowDelegate: RowDel {}
TableViewColumn {
id: c1
role: "lectureName"
title: "TEST"
}
}
}
Now the row delegate, RowDel.qml
import QtQuick 2.4
Rectangle {
id: rowDel
color: "blue"
height: 60
readonly property int modelRow: styleData.row ? styleData.row : 0
MouseArea {
anchors.fill: parent
onClicked: {
console.log("[!] log: " + modelRow);
}
}
}
The important thing here is that you can reference styleData.row directly from your component (obliviously as long as you use this precise component as a row delegate).
As an example, if you click on each tableview row, you should see the correct row number displayed in the console log:
qml: [!] log: 0
qml: [!] log: 1
qml: [!] log: 2
qml: [!] log: 3

Related

Receive signal in newly added listview entry

I am trying to execute a function in a newly added entry to my ListView.
After adding an entry via button I want to execute the just added delegate's onNewEntry function. But only the old delegates execute it.
Minimal example:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.0
Window {
id:root
width: 640
height: 480
visible: true
signal newEntry(int new_row)
property var someProperty
ListModel {
id:listmodel
ListElement {
name: "Bill Smith"
}
ListElement {
name: "John Brown"
}
ListElement {
name: "Sam Wise"
}
}
ListView{
id: listView
width:100
height:200
model: listmodel
delegate: ItemDelegate{
id:delegateId
Text{
text:name
}
Connections { //ISSUE!!!: the new delegate doesnt execute this, just the old ones
target: root
function onNewEntry(new_row){
console.debug(index)
console.debug(new_row)
if(new_row==index){ //doesnt get true
listView.currentIndex = index
setProductData()
}
function setProductData(){
root.someProperty=name
}
}
}
}
}
Button {
anchors.top: listView.bottom
id: btnAdd
text:"+"
onClicked:{
listmodel.append({"name":"Joe Black"})
newEntry(listView.count-1) //emit signal newEntry
}
}
}
output:
qml: 0
qml: 3
qml: 1
qml: 3
qml: 2
qml: 3
My already mentioned workaround is using ListView.onAdd in the delegate:
delegate: ItemDelegate{
ListView.onAdd: {
setProductData()
}
My question is: Why does the newly added entry doesnt listen to the newEntry signal. Thank you
It would be easier to just set the currentIndex after inserting the new row:
Button {
id: btnAdd
onClicked: {
sqlTableModel.insertNewEmptyRow()
listView.currentIndex = listView.count - 1
}
}
I found a better, probably more efficient solution:
delegate: ItemDelegate{
ListView.onAdd: {
listView.currentIndex=index
setProductData()
}
only the newly added delegate receives onAdd not all, like in the approach before.

Dynamically add more combobox without resetting exiting data

I am looking to dynamically add more Combobox to my view. The code works fine and adds a new Combobox on the click of the button.
However, when I add a new Combobox, the data in existing Comboboxes are reset to default. How do I just append another Combobox without resetting the earlier selected values. I will prefer not to save the existing values in some variable
Here is my code
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.3
Page {
id : somepageid
property int modelList: 0
property int counter: 0
// Combobox Listmodel
ListModel{
id: listmodel
ListElement{
elem: "A"
}
ListElement{
elem: "B"
}
ListElement{
elem: "C"
}
}
// Add more item to Listview
function addMore(){
if(counter < listmodel.count){
counter++
modelList++
listid.model = modelList
}
}
// Button
Button{
id: testButton
text: "Click to add Combobox"
onClicked: {
addMore()
}
}
// Listview
ListView{
id: listid
model: modelList
anchors.top: testButton.bottom
height: listid.model * 40
delegate: Row{
ComboBox{
id: combo
textRole: "elem"
model:listmodel
}
}
}
}
The problem is you are resetting the ListView each time you do a listid.model = modelList.
You need to have a fixed model for your listview and do the changes there.
An example (applied to your code) could look like this:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.3
Page {
id : somepageid
property int counter: 0
// ListView model
ListModel{
id: listmodel
}
// Add more item to Listview
function addMore(){
if(counter < 3){
counter++
listmodel.append({elements: [{elem: "A"}, {elem: "B"}, {elem: "C"}]})
}
}
// Button
Button{
id: testButton
text: "Click to add Combobox"
onClicked: {
addMore()
}
}
// Listview
ListView{
id: listid
model: listmodel
anchors.top: testButton.bottom
height: listid.model.count * 40
delegate: Row{
ComboBox{
id: combo
textRole: "elem"
model: elements
}
}
}
}

QML reset dialog with tabview

I was trying to implement a tabbed Dialog in QML with the means to reset it to the intial values.
Since tabs are dynamically instantiated, none of the straight forward methods seem to work. The parent Dialog can not reference the inner Combobox and the Combobox can not reference the outer Dialog. How can this be achieved?
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
Dialog {
id: dlg
title: "Settings"
visible: true
standardButtons: StandardButton.Apply | StandardButton.Reset
property string val: ""
onApply: console.log(val)
onReset: {
// RESET COMBOBOX TO DEFAULT
}
TabView {
id: tabView
anchors.fill: parent
Tab {
title: "ValueTab"
id: tabVal
GridLayout {
id: gridVal
anchors.fill: parent
GroupBox {
title: qsTr("Choose value")
id: gb
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
id: cl
ComboBox {
id: valueChooser
editable: false
model: ListModel {
id: listModel
ListElement { text: "One" }
ListElement { text: "Two" }
ListElement { text: "Three" }
}
Layout.fillWidth: true
onCurrentTextChanged : val = currentText
}
}
}
}
}
}
}
I am quite unsure, if I got your question right as you say, you can not reference the Dialog from within the Combobox. I can not see the reason why.
Assuming the example of yours contains indeed your problem and all you want to do is to reset the values (and you know the original values) once the reset button is pressed, this is how I would solve it.
Using the Connections-type to connect to the Dialog's reset() from within the Combobox
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
Dialog {
id: dlg
title: "Settings"
visible: true
standardButtons: StandardButton.Apply | StandardButton.Reset
property string val: ""
onApply: console.log(val)
onReset: {
// **DONT** RESET COMBOBOX TO DEFAULT **HERE**
}
TabView {
id: tabView
anchors.fill: parent
Tab {
title: "ValueTab"
id: tabVal
GridLayout {
id: gridVal
anchors.fill: parent
GroupBox {
title: qsTr("Choose value")
id: gb
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
id: cl
ComboBox {
id: valueChooser
editable: false
model: ListModel {
id: listModel
ListElement { text: "One" }
ListElement { text: "Two" }
ListElement { text: "Three" }
}
Layout.fillWidth: true
onCurrentTextChanged : val = currentText
/// *** INTERESTING PART HERE! ***
Connections {
target: dlg
onReset: {
// RESET COMBOBOX TO DEFAULT **HERE** INSTEAD
valueChooser.currentIndex = 0
}
}
}
}
}
}
}
}
}

Get active Tab from TabView and change item property

In the context of a dual-pane file manager, I have two TabView items side by side, each contains multiple tabs of course, and each Tab loads a TableView showing the content of a specific directory using FolderListModel.
SplitView
TabView
Tab
Tab
TabView
Tab
My current task is to implement a toolbar button to toggle the showHidden property of the FolderListModel instance shown in the active tab. Therefore, I need a way to find out what the currently active tab is.
Next, once I get the active Tab, I need to change Tab.item.some_property, in particular, the property of interest is show_hidden, which is an alias to the showHidden property of the underlying FolderListModel. For example, a hard-coded scenario would be:
ToolButton {
onClicked: {
tab1.item.show_hidden = false;
tab1.destroy(); // need "refresh" instead
}
}
First I need to get tab1 based on whether it is active, and second, after I change show_hidden, the view doesn't refresh by itself, so I need to call some kind of reload function, but which? Or maybe reload isn't the best way to do it? Is it possible to do it using a custom signal handler? (Again I can only think conceptually without knowing how to implement it.)
As suggested I'm posting a running example below:
/* main.qml */
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
ApplicationWindow {
visible: true
width: 1280
height: 700
toolBar: ToolBar {
RowLayout {
anchors.fill: parent
ToolButton {
onClicked: { // TODO toggle folderModel.showHidden property
tab1A.item.show_hidden = false;
// tab1A.destroy(); // fixme how to refresh the view?
}
}
}
}
Item {
anchors.fill: parent
SplitView {
id: splitView
anchors.fill: parent
TabView {
id: tabView1
width: splitView.width / 2
Tab {
id: tab1A
title: qsTr("Home")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///tmp";
}
}
Tab {
title: qsTr("Folder")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///home";
}
}
}
TabView {
id: tabView2
Tab {
title: qsTr("Home")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///home";
}
}
}
}
}
}
/* dirview.qml */
import QtQuick 2.4
import QtQuick.Controls 1.4
import Qt.labs.folderlistmodel 2.1
TableView {
property alias folder_url: folderModel.folder
property alias show_hidden: folderModel.showHidden
id: tableView
anchors.fill: parent
TableViewColumn {
role: "fileName"
title: qsTr("Name")
width: tableView.width * 0.7
}
TableViewColumn {
role: "fileSize"
title: qsTr("Size")
width: tableView.width * 0.2
}
FolderListModel {
id: folderModel
nameFilters: ["*"]
showHidden: true
showDirsFirst: true
showDotAndDotDot: true
}
model: folderModel
}
Thank you.
Noticed something weird: Tab.item.folder_url has the right info, however, Tab.item.show_hidden is always false, even if I remove the line where I manually set it to false. This is hard to understand as I initially set FolderListModel.showHidden to true in dirview.qml.
ToolButton {
onClicked: { // TODO toggle folderModel.showHidden property
var cur_tab_idx = tabView1.currentIndex;
console.log(tabView1.getTab(cur_tab_idx).item.folder_url);
console.log(tabView1.getTab(cur_tab_idx).item.show_hidden);
}
}
Here is an explanation of how I have got it to work.
I solved first problem using focus flag. When current Tab in TabView changes one Tab gains focus and the other one looses. So by using onFocusChanged() signal you can know exactly when one Tab becomes active or inactive.
The focus of Tab does not change when focus of whole TabView changes. Because of this I created an Array (named tabs in code) containing references to every TabView and Tab it contains. With this when TabView becomes inactive I can set focus of its Tab objects to false using simple for.
Second problem was more tricky. I see no other option of turning showHidden flag off than destroying and creating a new FolderListModel. We cannot (or I could not :) ) provide model to TableView dynamically so I made a ListModel. Advantage of regular ListModel compared to FolderListModel is that it can be cleared and refilled with data. Every time folder_url or show_hidden changes I destroy current FolderListModel and create a new one. After it is created I rewrite its data to the ListModel.
Here is the working code.
main.qml
/* main.qml */
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
ApplicationWindow {
visible: true
width: 1280
height: 700
property var tabs: [
[tabView1, [tab1A, tab1B]],
[tabView2, [tab2A]]
]
toolBar: ToolBar {
RowLayout {
anchors.fill: parent
ToolButton {
onClicked: { // TODO toggle folderModel.showHidden property
tab1A.item.show_hidden = false;
// tab1A.destroy(); // fixme how to refresh the view?
}
}
}
}
Item {
anchors.fill: parent
SplitView {
id: splitView
anchors.fill: parent
TabView {
id: tabView1
width: splitView.width / 2
Tab {
id: tab1A
title: qsTr("Home")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///tmp";
}
onFocusChanged: {
item.show_hidden = focus
}
}
onFocusChanged: {
if (!focus)
for (var i = 0 ; i < tabs[0][1].length ; i++)
tabs[0][1][i].focus = false
}
Tab {
id: tab1B
title: qsTr("Folder")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///home";
}
onFocusChanged: {
item.show_hidden = focus
}
}
}
TabView {
id: tabView2
Tab {
id: tab2A
title: qsTr("Home")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///tmp";
}
onFocusChanged: {
item.show_hidden = focus
}
}
onFocusChanged: {
if (!focus)
for (var i = 0 ; i < tabs[1][1].length ; i++)
tabs[1][1][i].focus = false
}
}
}
}
}
dirview.qml
/* dirview.qml */
import QtQuick 2.4
import QtQuick.Controls 1.4
import Qt.labs.folderlistmodel 2.1
TableView {
property string folder_url
property bool show_hidden
id: tableView
anchors.fill: parent
TableViewColumn {
role: "fileName"
title: qsTr("Name")
width: tableView.width * 0.7
}
TableViewColumn {
role: "fileSize"
title: qsTr("Size")
width: tableView.width * 0.2
}
ListModel {
id: secondListModel
}
property var fm
property int folderModelCount
onFolder_urlChanged: {
reloadFolderModel()
}
onShow_hiddenChanged: {
reloadFolderModel()
}
onFolderModelCountChanged: {
resetSecondListModel()
}
function reloadFolderModel() {
folderModelCount = 0
if (typeof(fm) !== "undefined")
fm.destroy()
var component = Qt.createComponent("foldermodel.qml")
if (component.status === Component.Ready)
fm = component.createObject(
tableView, {"folder_url": folder_url, "show_hidden": show_hidden})
else
console.error(component.errorString())
folderModelCount =
Qt.binding(function(){return fm.folderModel.count})
}
function resetSecondListModel() {
secondListModel.clear()
for (var i = 0 ; i < folderModelCount ; i++) {
secondListModel.append({
"fileName": fm.folderModel.get(i, "fileName"),
"filePath": fm.folderModel.get(i, "filePath"),
"fileURL": fm.folderModel.get(i, "fileURL"),
"fileBaseName": fm.folderModel.get(i, "fileBaseName"),
"fileSuffix": fm.folderModel.get(i, "fileSuffix"),
"fileSize": fm.folderModel.get(i, "fileSize"),
"fileModified": fm.folderModel.get(i, "fileModified"),
"fileAccessed": fm.folderModel.get(i, "fileAccessed"),
"fileIsDir": fm.folderModel.get(i, "fileIsDir")
})
}
}
model: secondListModel
}
foldermodel.qml (add this file)
import QtQuick 2.4
import QtQuick.Controls 1.4
import Qt.labs.folderlistmodel 2.1
Item {
property string folder_url
property bool show_hidden
property alias folderModel: folderModelObject
FolderListModel {
id: folderModelObject
nameFilters: ["*"]
folder: folder_url
showHidden: show_hidden
showDirsFirst: true
showDotAndDotDot: true
}
}
Now you understand why QML is not very flexible. :)
Solution to finding the current Tab in the active TabView (pane): declare a property of SplitView to store the TabView that has activeFocus.
A StatusBar is added to demo the functionality.
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
ApplicationWindow {
visible: true
width: 1280
height: 700
toolBar: ToolBar {
RowLayout {
anchors.fill: parent
ToolButton {
onClicked: { // TODO toggle folderModel.showHidden property
// Demo: get the current tab of the active pane
var active_pane = splitView.activePane;
var cur_tab_idx = active_pane.currentIndex;
var cur_tab_item = active_pane.getTab(cur_tab_idx).item;
testLabel.text = cur_tab_item.folder_url;
}
}
}
}
SplitView {
id: splitView
property TabView activePane: tabView1
anchors.fill: parent
TabView {
id: tabView1
width: splitView.width / 2
onActiveFocusChanged: {
if (activeFocus) {
splitView.activePane = tabView1;
}
}
Tab {
title: qsTr("tmp")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///tmp";
}
}
Tab {
title: qsTr("home")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///home";
}
}
}
TabView {
id: tabView2
onActiveFocusChanged: {
if (activeFocus) {
splitView.activePane = tabView2;
}
}
Tab {
title: qsTr("bin")
source: "dirview.qml"
onLoaded: {
item.folder_url = "file:///bin";
}
}
}
}
statusBar: StatusBar {
RowLayout {
Label {
text: (splitView.activePane === tabView1) ? "Pane 1" : "Pane 2"
}
Label {
id: testLabel
}
}
}
}

Qt/QML: How to refer to a component/object from a GridView's model's ListElement

I have a GridView with a delegate that is supposed to use a Loader to load and display components which are defined in the same QML file.
Let's say I have a GridView like this:
GridView {
delegate: Rectangle {
Loader { sourceComponent: model.pageContents }
}
model: ListModel {
ListElement { /* how do I reference any of the components defined below from here, so the Loader can actually load and display it... ? */ }
}
}
Component {
id: page_01
Rectangle {
color: "red"
// Page contents for page 1 go here.
}
}
Component {
id: page_02
Rectangle {
color: "red"
// Page contents for page 2 go here.
}
}
I know I can create QML objects and components from Strings, external files and URLs. But I'd like to ideally do something like this:
ListModel {
ListElement { pageContents: page_01 }
ListElement { pageContents: page_02 }
}
I'd prefer to keep everything in a single QML file, so I can easily transfer and store it on the device without having to worry about resolving external dependencies, etc.
How do I refer to components in the same QML file from within ListElements?
Due to ListElement values limitation you cannot just put item id here. But you easily can use some external storage, for example property to store pointers to your pages. In example below I use array with pages ids and an index to wanted page as ListElement data:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
width: 600
height: 600
GridView {
id: grid
anchors.fill: parent
property var pages: [page_01, page_02]
model: ListModel {
ListElement { pageIndex: 0 }
ListElement { pageIndex: 1 }
}
delegate: Loader { sourceComponent: grid.pages[pageIndex] }
}
Component {
id: page_01
Rectangle {
color: "red"
width: 100
height: 100
Component.onCompleted: console.log("page_01 was created")
}
}
Component {
id: page_02
Rectangle {
color: "blue"
width: 100
height: 100
Component.onCompleted: console.log("page_02 was created")
}
}
}

Resources