How to access the current item in a TableViewColumn?
TableView {
id: atcTableView
model: myatclist
...
TableViewColumn {
...
}
TableViewColumn {
id: atcTableViewColFreq
role: "frequency"
title: "Frequency"
width: 120
delegate: Component {
Text {
text: "Freq is " + currentItem / model / model.frequency
}
}
}
As of this similar question " How do you access the roles of the currentItem from a listview in QML? " I have tried all kind of combinations model, modelData , currentItem, and something like model.role.
If I remove the delegate entirely, frequency displays correctly. Model is based on QAbstractListModel. Any hints?
Btw, can I see in QML debugging what properties are available in a delegate?
-- Edit based on Kakadu's comment --
delegate {
Text {
text: "freq is " + frequency
}
}
gives me: ReferenceError: frequency is not defined
delegate: Text { text: view.model.get(styleData.row).frequency }
Yow must to define the role in te QAbstractItemModel.
QHash YourClassModel::roleNames() const {
roles[Qt::UserRole + 1] = "frequency";
return roles;
}
Try to use styleData.value:
delegate: {
Text { text: styleData.value }
}
It's described here (look for itemDelegate property)
Related
I would like to be able to bind to a property of an item generated by Repeater to do something with it, e.g. to show its coordinates. For that purpose I am using itemAt() like this:
ListModel {
id: modelNodes
ListElement { name: "Banana"; x: 100; y: 200 }
ListElement { name: "Orange"; x: 150; y: 100 }
}
Repeater {
id: foo
model: modelNodes
Rectangle {
x: model.x; y: model.y
width: textBox.implicitWidth + 20
height: textBox.implicitHeight + 20
color: "red"
Drag.active: true
Text {
id: textBox
anchors.centerIn: parent
color: "white"
text: model.name + ": " + foo.itemAt(index).x
}
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
}
Text {
id: moo
Binding {
target: moo
property: "text"
value: foo.itemAt(0).x + " -> " + foo.itemAt(1).x
}
}
Inside the delegate this works fine, but when I attempt to use it outside of the Repeater (i.e. to bind moo's text to it), I get the following error:
TypeError: Cannot read property 'x' of null
How to fix this?
The reason the Binding object doesn't work outside of the Repeater is because the Repeater has not constructed its items yet when the binding is being evaluated. To fix this, you can move the binding into the Component.onCompleted handler. Then just use the Qt.binding() function to do binding from javascript (docs).
Text {
Component.onCompleted: {
text = Qt.binding(function() { return foo.itemAt(0).x + ", " + foo.itemAt(1).x })
}
}
You don't.
(or more precisely, you shouldn't)
Delegates shouldn't store state or data, just display it or be able to interact with it.
In your case what you are after is the data stored in the model.
Your solution should be to modify your model in your delegates and get the data from your model if you want.
I've created a small example of what I mean:
import QtQuick 2.15
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
visible: true
width: 800
height: 640
ListModel {
id: modelNodes
ListElement { name: "Banana"; x: 50; y: 50 }
ListElement { name: "Orange"; x: 50; y: 100 }
}
Row {
anchors.centerIn: parent
spacing: 1
Repeater {
model: 2 // display 2 copy of the delegates for demonstration purposes
Rectangle {
color: "transparent"
width: 300
height: 300
border.width: 1
Repeater {
id: foo
model: modelNodes
Rectangle {
x: model.x; y: model.y
width: textBox.implicitWidth + 20
height: textBox.implicitHeight + 20
color: "red"
DragHandler {
dragThreshold: 0
}
onXChanged: model.x = x // modify model data when dragging
onYChanged: model.y = y
Text {
id: textBox
anchors.centerIn: parent
color: "white"
text: model.name + ": " + foo.itemAt(index).x
}
}
}
}
}
}
Instantiator {
model: modelNodes
delegate: Binding { // the hacky solution to the initial problem.
target: myText
property: model.name.toLowerCase() + "Point"
value: Qt.point(model.x, model.y)
}
}
Text {
id: myText
property point bananaPoint
property point orangePoint
anchors.right: parent.right
text: JSON.stringify(bananaPoint)
}
ListView {
anchors.fill: parent
model: modelNodes
delegate: Text {
text: `${model.name} - (${model.x} - ${model.y})`
}
}
}
I've used a hacky solution to your initial problem with an Instantiator of Bindings, I don't really understand the usecase so that might not be the ideal solution. Here it creates a binding for every element of your model but that's weird. If you only want data from your first row, you may want to do when: index === 0 in the Binding. I've created a third party library to get a cleaner code : https://github.com/okcerg/qmlmodelhelper
This will result in the following code for your outside Text (and allowing you to get rid of the weird Instantiator + Binding part):
Text {
readonly property var firstRowData: modelNodes.ModelHelper.map(0)
text: firstRowData.x + ", " + firstRowData.y
}
Note that my point about not storing data in delegates (or accessing them from outside) still stands for whatever solution you chose.
I have a TableView defined in my QML which will obviously have multiple rows populated by a ListModel.
I want to fetch the ListElement associated with the row which is double clicked.
I have my rowDelegate of the table view defined as such:
rowDelegate: Rectangle {
color: "#D3D3D3"
height: 30
MouseArea {
anchors.fill: parent
onDoubleClicked: {
console.log("table view row clicked...")
// How to fetch the ListElement associated with the row
// and return it for use by another module?
}
}
}
My comment pretty much emphasises what I'm looking for.
You probably don't even need a MouseArea to handle the click in your delegate.
The TableView already has a doubleClicked signal that you can use to retrieve the model data from the clicked row index:
TableView {
model: ListModel {
ListElement {
name: "name 1"
}
ListElement {
name: "name 2"
}
}
TableViewColumn {
title: "name"
delegate: Text {
text: model.name
}
}
rowDelegate: Rectangle {
color: "#D3D3D3"
height: 30
// no MouseArea
}
// handle the click directly in TableView
onDoubleClicked: {
const element = model.get(row)
console.error("doubleClicked on", element.name)
}
}
In scope of your delegate you can use model pseudo-property to fetch associated ListElement (or any other piece of data which is displayed via that delegate). You may think of it as a reference to original data item. It has all properties of ListElement (for example text or color or whatever) and also index property (index of item in your ListModel or any other model).
TreeView {
anchors.fill: parent
model: theModel
onCurrentIndexChanged: console.log("current index: " + currentIndex+ " current row: " + currentIndex.row)
itemDelegate: Rectangle {
color: ( styleData.row % 2 == 0 ) ? "white" : "lightblue"
height: 40
Text {
anchors.verticalCenter: parent.verticalCenter
text: styleData.value === undefined ? "" : styleData.value // The branches don't have a description_role so styleData.value will be undefined
}
}
TableViewColumn {
role: "name_role"
title: "Database name"
}
onClicked: {
console.log("clicked", index)
}
this is my treeview code.it will show database names as parent and table names as the child. I need to get the child name when I click on the child area.
that's all I need.
for eg:
database_name
|____table_one
|____table_two
when i click on 'table_one' i need to get the table_one as a text/string
this is my application I need to get the child items names as a text from this treeview
After some search, I have got the solution for the above question myself
syntax: model_class_name.data(index,"Role_name");
*the above code will return the current item that in focus in a tree view
In the qml file, you must set the class name as model_class_name.data(index,INT); where INT - it is a number of role.
I have trouble retrieving the index of a delegate that is instantiated inside a DelegateModel for a ListView.
The minimal example as following:
LastProcedures.qml
ListModel {
ListElement {
procedure: "Liver Resection"
surgeon: "Prof. Dr. Joyride"
recent: true
}
...
}
main.qml
ListView {
id: list_lastProcedures
model: displayDelegateModel
}
DelegateModel {
id: displayDelegateModel
delegate: lastProceduresDelegate
model: LastProcedures {}
groups: [
DelegateModelGroup {
includeByDefault: false
name: "recent"
}
]
filterOnGroup: "recent"
Component.onCompleted: {
var rowCount = model.count;
items.remove(0,rowCount);
for( var i = 0;i < rowCount;i++ ) {
var entry = model.get(i);
// Only the recent three
if((entry.recent == true) && (items.count < 3)) {
items.insert(entry, "recent");
}
}
}
}
Component {
id: lastProceduresDelegate
Text{
text: model.index
}
}
The text index prints always -1. Without a DelegateModel it prints the index in the ListView. How can I access the correct index of the delegate in the Listview?
you can use "lastProceduresDelegate.DelegateModel.itemsIndex" instead of "model.index"
just like this:
Component {
id: lastProceduresDelegate
Text{
text: lastProceduresDelegate.DelegateModel.itemsIndex
}
I ended up with not removing all entries and adding them back to groups, but instead just remove unwanted entries. This ways the index stays valid.
If someone could explain this behavior further, that would be nice.
DelegateModel {
id: displayDelegateModel
delegate: lastProceduresDelegate
model: LastProcedures {}
groups: [
DelegateModelGroup {
includeByDefault: true
name: "recenttrue"
}
]
filterOnGroup: "recenttrue"
Component.onCompleted: {
for( var i = 0;i < items.count;i++ ) {
var entry = items.get(i).model;
// Only the recent
if((entry.recent != true)) {
items.removeGroups(i, 1, "recenttrue");
}
}
}
}
The DelegateModel has some hidden magic regarding groups (it's not very visible but it's here ). For each group you create, the DelegateModel attached property will receive two new properties: <group>Index and in<Group>.
In your case this means you will get the following properties: recentIndex and inRecent (or in your own answer: recenttrueIndex and inRecenttrue).
I think with what you want to do you should go the recenttrue route and draft the Component as follows:
Component {
id: lastProceduresDelegate
Text {
text: lastProceduresDelegate.DelegateModel.recenttrueIndex
}
}
I am building an application with HsQML. This is my first encounter with QML, my second ever work in Qt, and first larger project with Haskell, so forgive my ignorance.
In the UI, I have a TabView. The first Tab contains a ListView which is bound to a model and displays a list of items. Double-clicking an item in the ListView opens a new tab with a component which correctly shows that item's details (my guess is by virtue of the new tab inheriting its context from the list item that was clicked).
Now, my objective is to open a tab in which to create a new item for that model. The idea is to create a blank data item (optionally adding it to the model), and "load" this into the same component type used for editing existing items. I scoured QML's documentation and could not find anything even remotely related, which makes me think the approach is completely flawed.
TabView {
id : rootTabs
Tab {
ListView {
model : AutoListModel {
source : workflowModel // this is sort of HsQML specific, data comes as a list from Haskell
}
delegate : Rectangle {
Text {
text : modelData.name
}
MouseArea {
anchors.fill : parent
// this part works because the new component inherits its modelData from the current context
// so the new tab has correct data
onDoubleClicked : {
rootTabs.addTab(modelData.name, Qt.createComponent("WorkflowView.qml"))
rootTabs.currentIndex = rootTabsCount - 1
}
}
}
}
Button {
text : "Create workflow"
// this is the part in question - how do I assign the newly appended data to comp?
onClicked : {
wModel.appendBlank()
comp = Qt.createComponent("WorkflowView.qml")
var tab = rootTabs.addTab("New workflow", comp)
comp.statusChanged.connect(tabLoaded)
}
}
}
}
WorkflowEdit.qml:
Rectangle {
TextField {
id : nameInput
text : modelData.name
Binding {
target : modelData
property : "name"
value : nameInput.text
}
}
}
I think I have what you're looking for. It was a little tricky because Tab are essentially loaders. It was a matter of creating an extra property for the Tab QML type as a place to store a model index. And since tabs are simply children of a TabView, new tabs can be parented to the TabView instead of using the addTab() method. Note that for my model I used a ListModel.
main.qml
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
TabView {
id : rootTabs
anchors.fill: parent
ListModel {
id: listModel
ListElement { car: "Toyota" }
ListElement { car: "Chevrolet" }
ListElement { car: "Honda" }
ListElement { car: "Daihatsu" }
ListElement { car: "Ford" }
ListElement { car: "Nissan" }
ListElement { car: "Hyundai" }
ListElement { car: "Acura" }
}
MyTab {
title: "Default"
Item {
ListView {
id: listView
anchors { fill: parent; bottomMargin: 240 }
model : listModel
delegate : Rectangle {
width: parent.width
height: 40
Text {
text : car
color: "black"
font.pointSize: 20
}
MouseArea {
anchors.fill : parent
onDoubleClicked : {
var myTab = Qt.createComponent("MyTab.qml")
var workflow = Qt.createComponent("Workflow.qml")
myTab.createObject(rootTabs, { "title": car, "modelIndex": index, "sourceComponent": workflow });
rootTabs.currentIndex = rootTabs.count - 1
}
}
}
}
Button {
anchors {fill: parent; topMargin: 240 }
text : "Create workflow"
onClicked : {
listModel.append( { "car" : "New car" } )
var myTab = Qt.createComponent("MyTab.qml")
var workflow = Qt.createComponent("Workflow.qml")
myTab.createObject(rootTabs, { "title": "New Workflow", "modelIndex": listModel.count - 1 , "sourceComponent": workflow });
}
}
}
}
}
}
MyTab.qml
import QtQuick 2.0
import QtQuick.Controls 1.4
Tab {
property int modelIndex
}
Workflow.qml
import QtQuick 2.0
import QtQuick.Controls 1.4
Rectangle {
TextField {
id : nameInput
text : listModel.get(modelIndex).car
onTextChanged: {
// Update model using modelIndex. Observe updates in listview
listModel.set(modelIndex, { "car" : text })
}
}
}
TabView::addTab returns a Tab object, which is basically a Loader object. Loader::item is the current loaded object. So, the solution is to add an new empty model data to the tab as follows (in Button::onClicked):
var tab = ...
tab.loaded.connect(function () {tab.item.data = newModelData;}); // newModelData = wModel.appendBlank() ???
And you should add the property modelData explicitly to WorkflowEdit.qml:
Rectangle {
property var data: modelData // create property data and assign the context variable modelData to it by default
TextField {
id : nameInput
text : data === undefined ? "" : data.name
Binding {
target : data
property : "name"
value : nameInput.text
}
}
}