I have a listview where I want to use either a defaultDelegate or a customDelegate depending on the value of a property.
So far I've tried using a component to load the different delegates:
Component{
id: delegate
Loader {
sourceComponent: type == 3 ? customDelegate : defaultDelegate
}
}
However, I can't access the properties I have in my model from the two delegates I have. I have the following error:
ReferenceError: name is not defined
Here is the model I use:
ListModel {
id: test
ListElement {
name: "Bill"
team: "554"
type: 2
}
ListElement {
name: "John"
team: "555"
type: 3
}
ListElement {
name: "Sam"
team: "556"
type: 1
}
}
Does anyone Have any idea, what I am doing wrong here?
It is, of course, a context problem. In your code, the name, team and type context properties inserted by the ListView into delegate's context is inaccessible to the components inside your delegates, as the Loader uses the creation context of customDelegate and defaultDelegate as the parent context when instantiating them, and name, team and type do not refer to anything withing that context chain.
One solution is to explicitly set the required information as a property of the Loader (this works because the Loader sets itself as the context object for the component it is loading).
Following a working example:
ListModel {
id: testModel
ListElement {
name: "Bill"
team: "554"
type: 2
}
ListElement {
name: "John"
team: "555"
type: 3
}
ListElement {
name: "Sam"
team: "556"
type: 1
}
}
ListView {
anchors.fill: parent
model: testModel
delegate: myDelegate
}
Component {
id: myDelegate // yourDelegate
Loader {
property string modelName: model.name
property string modelTeam: model.team
property int modelType: model.type
sourceComponent: modelType === 3 ? colonDelegate : semicolonDelegate
}
}
Component {
id: colonDelegate
Text { text: modelName + ": " + modelTeam }
}
Component {
id: semicolonDelegate
Text { text: modelName + "; " + modelTeam }
}
For further reading and improvements I highly recommend you to read this.
Related
There are 9 parameters that I need to use TextField1 to input value.
So I use
ListModel lstPara {
ListElement{
text:"A";value:"123"
}...(9 Elements)
}
Grid{
id: grid
anchors.fill: parent
columns: 3
spacing: 5
Repeater {
id: rpPara
model: lstPara
delegate: TextField1 {
}
}
}
But now there is a parameter that i need to use in another QML type to set the value, all others are used in TextField1.
I tried to define ListModel like this
ListModel lstPara{
ListElement {
text: "A";
type: 1";
value: "123"
}
ListElement {
text: "B";
type: 2";
value: "321"
}
...(9 Elements)
}
Grid{
id: grid
anchors.fill: parent
columns: 3
spacing: 5
Repeater {
id: rpPara
model: lstPara
(some code : like this)
delegate: {
return type === 1 ?
TextField1 {
}
:
another QML type {
}
}
}
}
The code above can not run.
And I don`t want to write 8 TextField1 and 1 another QML type.
So, is there a way to use ListModel?
You can't directly use an Item declaration in a conditional expression like that, but you can do it with a Component. Here's an example of how to do it using a Loader as your delegate, and choosing which Component to load based on the model:
ListModel {
id: lstPara
ListElement {
text: "A"
type: 1
value: "123"
}
ListElement {
text: "B"
type: 2
value: "321"
}
}
Grid {
id:grid
anchors.fill: parent
columns: 3
spacing: 5
Repeater {
id: rpPara
model: lstPara
delegate: Loader {
sourceComponent: type === 1 ? someText : otherText
onLoaded: {
item.text = text
item.value = value
}
}
}
Component {
id: someText
Text {
property string value
color: "blue"
}
}
Component {
id: otherText
Text {
property string value
color: "red"
}
}
}
I want to check if an item of component is vissible or not but I don't know how to do this.
Here is my code but error is "ReferenceError: tagFilterPlaylist is not defined" how to solve this?
Repeater {
id: tagRepeater
model: main.playListBrowseModel
Component {
id: componentTagFilterPlaylist
BasicUI.Tag {
id: tagFilterPlaylist
tag: "playlist"
selected: true
}
}
Loader {
id: filterLoader
sourceComponent:
if (item_type === "playlist"){
console.debug("check"+tagFilterPlaylist)
if (!tagFilterPlaylist) {
tagFilterPlaylist.visible = true;
return componentTagFilterPlaylist
}
}
}
}
You can't refer to an id within a Component because you first need to have an instance of that Component. What you should do is simply keep a boolean property outside the Repeater that keeps track of whether or not you're displaying your one and only tagFilterPlaylist.
property bool playlistVisible: false
Component {
id: componentTagFilterPlaylist
...
}
Repeater {
id: tagRepeater
model: main.playListBrowseModel
Loader {
id: filterLoader
sourceComponent:
if (item_type === "playlist"){
if (!playlistVisible) {
playlistVisible = true;
return componentTagFilterPlaylist
}
}
}
}
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
}
}
}
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)