I have a ListView nested inside another ListView in my qml. Both the ListViews are being populated by a data models which I get from a C++ class SelectRoomView. This is the code for my qml file:
import bb.cascades 1.0
import Data.SelectRoomView 1.0
import "commons"
Page {
id: pageSelectARoom
property alias currentPage: pageSelectARoom
property string propId
attachedObjects: [
SelectRoomView {
id: selRoom
}
]
onPropIdChanged: {
selRoom.propId = propId;
}
Container {
layout: StackLayout {
orientation: LayoutOrientation.TopToBottom
}
background: Color.create("#FFFFFF")
ScrollView{
horizontalAlignment: HorizontalAlignment.Fill
objectName: "scrollView"
ListView {
id: ruleList
dataModel: selRoom.modelRule
listItemComponents: [
ListItemComponent {
id: outerList
Container{
Label {
id: outerLabel
text: ListItemData.ruleName
textStyle.fontSize: FontSize.XXSmall
textStyle.color: Color.Black
}
Label{
id: outerId
text: ListItemData.ruleId
textStyle.fontSize: FontSize.XXSmall
textStyle.color: Color.Black
visible: true
}
ListView{
id: roomList
dataModel: selRoom.modelRoom //I get the message ReferenceError: Can't find the variable: selRoom
listItemComponents: [
ListItemComponent {
id: innerList
Label{
id: innerLabel
text: ListItemData.name
textStyle.fontSize: FontSize.XXSmall
textStyle.color: Color.Black
}
}
]
}
}
}
]
}
}
}
}
Upon running the app, ruleName of ruleList shows but not the innerLabel of roomList. The console displays the following message:
asset:///SelectARoom.qml:100: ReferenceError: Can't find variable: selRoom
asset:///SelectARoom.qml:100: ReferenceError: Can't find variable: selRoom
i.e., selRoom is not in scope for roomList (the nested list).
What am I doing wrong?
I cannot find a documentation of the ListView.listItemComponents property but it looks like some kind of delegate.
If the sub-list is part of the first list's delegate, it might not have access to global variables.
The QML docs state that delegates can access their position index and the variables of the model only.
Try to access other values outside the delegate's scope like to see whether that works
ListView {
id: roomList
Component.oncompleted: console.log(ruleList.height)
...
}
I found a solution, but it is not very efficient. The following is the new code:
Page {
id: pageSelectARoom
property alias currentPage: pageSelectARoom
property string propId
attachedObjects: [
SelectRoomView {
id: selRoom
}
]
onPropIdChanged: {
selRoom.propId = propId;
}
Container {
layout: StackLayout {
orientation: LayoutOrientation.TopToBottom
}
background: Color.create("#FFFFFF")
ScrollView{
horizontalAlignment: HorizontalAlignment.Fill
objectName: "scrollView"
ListView {
id: ruleList
dataModel: selRoom.modelRule
function getModelMain() { //the object 'selRoom' is accessible from this function.
return selRoom.modelRoom;
}
listItemComponents: [
ListItemComponent {
id: outerList
Container{
id: ruleContainer
function getModel() {
return ruleContainer.parent.getModelMain();
}
Label {
id: outerLabel
text: ListItemData.ruleName
textStyle.fontSize: FontSize.XXSmall
textStyle.color: Color.Black
}
Label{
id: outerId
text: ListItemData.ruleId
textStyle.fontSize: FontSize.XXSmall
textStyle.color: Color.Black
visible: true
}
ListView{
id: roomList
dataModel: ruleContainer.getModel()
listItemComponents: [
ListItemComponent {
id: innerList
Label{
id: innerLabel
text: ListItemData.name
textStyle.fontSize: FontSize.XXSmall
textStyle.color: Color.Black
}
}
]
}
}
}
]
}
}
}
}
Is there a better way to do this or is there an alternate more efficient solution? Waiting for response. Thanks.
Related
I am new to QML so kindly help me out as why QML throws error when trying to build this one
import QtQuick
import QtQuick.Window
import QtQuick.Controls 2.4
Window {
width: 640
height: 480
visible: true
title: qsTr("App title")
ListModel {
id: modelName
ListElement { name: "alpha" }
ListElement { name: "beeta" }
ListElement { name: "gamma" }
}
Component {
id: delegateName
Text {
text: modelName.name // QML throw error here
font.pixelSize: 32
}
}
ListView {
anchors.fill: parent
model: modelName
delegate: delegateName
clip: true
}
}
I'm building this using QT-Creator. Any help would be appreciated
In a delegate, try this one: text: model.name. Every delegate has its own model property with accordingly model's data
Suppose, we have the following code:
import QtQuick 2.4
import QtQuick.Window 2.0
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Window {
id: win
width: 800
height: 600
ListModel {
id: listModel
ListElement { name: "element1" }
ListElement { name: "element2" }
ListElement { name: "element3" }
}
ColumnLayout {
anchors.centerIn: parent
width: 200
height: 200
ComboBox {
model: listModel
currentIndex: 1
Layout.fillWidth: true
}
ListView {
model: listModel
delegate: Text {
text: name
}
Layout.fillHeight: true
Layout.fillWidth: true
}
Button {
text: "Change model"
onClicked: {
listModel.get(1).name = "changed text";
//listModel.setProperty(1,"name","changed text"); this line not works too
}
}
}
}
So clicking the button have to change model's element with index 1. But changing the model affects only ListView. The ComboBox remains unchanged.
Why that happens? Is it bug or feature? Is there a way to update ComboBox after changing its model?
I had a similar problem, I used a workaround. In onClicked function of button, create copy of model, change it as you want and then assign it again to ListViews model:
ListView {
id: listView
...
}
Button {
onClicked: {
var copy = listView.model;
copy.get(1).name = "changed text";
listView.model = copy; }
}
}
I need to press my create button under red circle and by pressing this button i am trying to fill first row of my table view. I am a new user in QT Quick please help me. I have wasted lot of time but have no way to do it.
I am providing UI and code as well please have a look.
Thank you !
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.0
import QtQuick.Dialogs 1.0
import "content"
import "images"
ApplicationWindow {
visible: true
title: "Mask Editor: Subsystem"
width: 720
height: 420
minimumHeight: 400
minimumWidth: 720
statusBar: StatusBar {
id: minstatusbar
RowLayout {
id:row1
spacing: 2
width: parent.width
Button {
text:"Unmask"
}
Item { Layout.fillWidth: true }
Button {
text:"OK"
}
Button {
text:"Cancel"
}
Button {
text:"Help"
}
Button {
text:"Apply"
}
}
}
/* ListModel {
id: largeModel
Component.onCompleted: {
for (var i=0 ; i< 10 ; ++i)
largeModel.append({"index":i , "prompt": "pmpt", "variable":i+10, "type" : "Female","tabname":"something"})
}
}*/
Action {
id: openAction
text: "&Open"
shortcut: "Ctrl+O"
iconSource: "images/document-open.png"
onTriggered: fileDialog.open()
tooltip: "Open an Image"
}
TabView {
id:frame
enabled: enabledCheck.checked
tabPosition: controlPage.item ? controlPage.item.tabPosition : Qt.TopEdge
anchors.fill: parent
anchors.margins: Qt.platform.os === "osx" ? 12 : 2
Tab {
id: controlPage
title: "Parameters"
Item {
id:root
anchors.fill: parent
anchors.margins: 8
ColumnLayout {
id: mainLayout
anchors.fill: parent
spacing: 4
GroupBox {
id: gridBox
title: "Grid layout"
Layout.fillWidth: true
ListModel {
id: nestedModel
ListElement{index:"1";prompt:"pmt";variable:10; type: "to be defined";tabname:"something"}
}
GridLayout {
id: gridLayout
anchors.fill: parent
rows: 3
flow: GridLayout.TopToBottom
ToolButton { iconSource: "images/window-new.png"
action:filldata
Accessible.name: "New window"
onClicked: {
nestedmodel.append({"index":i , "prompt": "pmpt", "variable":i+10, "type" : "Female","tabname":"something"})
}
tooltip: "Toggle visibility of the second window" }
ToolButton { iconSource: "images/view-refresh.png"
onClicked: window1.visible = !window1.visible
Accessible.name: "New window"
tooltip: "Toggle visibility of the second window" }
ToolButton { Accessible.name: "Save as"
iconSource: "images/document-save-as#2x.png"
tooltip: "(Pretend to) Save as..." }
TableView{
model: modelcontl
Layout.rowSpan: 3
Layout.fillHeight: true
Layout.fillWidth: true
//anchors.fill: parent
TableViewColumn {
role: "index"
title: "#"
width: 36
resizable: false
movable: false
}
TableViewColumn {
role: "prompt"
title: "Prompt"
width: 120
}
TableViewColumn {
role: "variable"
title: "Variable"
width: 120
}
TableViewColumn {
role: "type"
title: "Type"
width: 200
visible: true
}
TableViewColumn {
role: "tabname"
title: "Tab Name"
Layout.fillWidth: true
visible: true
}
}
}
}
Rectangle{
Layout.fillHeight: true
Layout.fillWidth: true
GridLayout {
//id: gridLayout
rows: 1
flow: GridLayout.TopToBottom
anchors.fill: parent
GridLayout{
id: grid1
columns: 2
anchors.fill: parent
GroupBox {
//id: gridBox
title: "Grid layout"
Layout.fillHeight: true
Layout.fillWidth: true
}
GroupBox {
//id: gridBox
title: "Grid layout"
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
}
}
}
}
Tab {
title: "Documentation"
Controls { }
}
Tab {
title: "dialog"
MessageDialogs { }
}
}
}
If you want to add a QML control into a cell you just have to define custom delegate. Since you need different delegates for different columns it can be done in a bit "tricky" way with Loader. See my example below:
Component {
id: comboDelegate
ComboBox {
model: ListModel {
ListElement { text: "Banana" }
ListElement { text: "Apple" }
ListElement { text: "Coconut" }
}
}
}
Component {
id: textDelegate
Text {
text: itemText
}
}
TableView {
Layout.fillWidth: true
Layout.fillHeight: true
TableViewColumn{ role: "col1" ; title: "Column1" }
TableViewColumn{ role: "col2" ; title: "Column2" }
TableViewColumn{ role: "col3" ; title: "Column3" }
model: ListModel {
id: myModel
}
itemDelegate: Item {
height: 25
Loader {
property string itemText: styleData.value
sourceComponent: styleData.column === 0 ? comboDelegate : textDelegate
}
}
}
Try this:
TableView {
TableViewColumn{ role: "col1" ; title: "Column1" }
TableViewColumn{ role: "col2" ; title: "Column2" }
TableViewColumn{ role: "col3" ; title: "Column3" }
model: ListModel {
id: myModel
}
}
Button {
text: "Add row"
onClicked: {
myModel.append({col1: "some value",
col2: "Another value",
col3 : "One more value" });
}
}
UPDATE
I wanna implement the setting by swiping the top bezel, here's my code am I doing this right? Currently this doesn't work. I wanna know how to implement it. Where should I put the code for it, and what do I lack of so that it'll work?
import bb.cascades 1.0
NavigationPane {
//property variant menu;
Menu.definition: MenuDefinition {
settingsAction: SettingsActionItem {
imageSource: "asset:///images/navbar_icon_settings.png"
onTriggered: {
cppObj.onSettingsClicked();
}
}
actions: [
ActionItem {
title: "Action 1"
imageSource: "asset:///images/navbar_icon_settings.png"
onTriggered: {
cppObj.onSettingsClicked();
}
}
]
}
firstPage: Page {
Container {
background: Color.create("#f9f7f2");
layout: StackLayout {}
// Container for holding the title
Container {
horizontalAlignment: HorizontalAlignment.Center
layout: DockLayout {}
ImageView {
horizontalAlignment: HorizontalAlignment.Fill
verticalAlignment: VerticalAlignment.Fill
imageSource: "asset:///images/navigation_bar.png"
}
/*Container {
horizontalAlignment: HorizontalAlignment.Right
rightPadding: 30
topPadding: 40
layout: DockLayout {}
ImageButton {
id: btnsettings
verticalAlignment: VerticalAlignment.Center
defaultImageSource: "asset:///images/navbar_icon_settings.png"
onClicked: {
// show settings page when the button is clicked
cppObj.onSettingsClicked();
}
}
}*/
}
Container {
topPadding: 20
leftPadding: 20
rightPadding: 20
bottomPadding: 20
background: Color.create("#F4E9E1");
horizontalAlignment: HorizontalAlignment.Fill
layout: StackLayout {}
Label {
verticalAlignment: VerticalAlignment.Center
horizontalAlignment: HorizontalAlignment.Left
text: cppObj.name
textStyle {
// fontFamily: FontStyle.Default.Myriad
// fontSize: 36
color: Color.create("#60323C")
}
}
}
Container {
verticalAlignment: VerticalAlignment.Center
horizontalAlignment: HorizontalAlignment.Center
layout: DockLayout {}
Divider {}
ScrollView {
scrollViewProperties {
scrollMode: ScrollMode.Vertical
}
/* ImageView {
id: listviewbackground
verticalAlignment: VerticalAlignment.Center
horizontalAlignment: HorizontalAlignment.Center
scalingMethod: ScalingMethod.Fill
imageSource: "asset:///images/list_view_cell.png"
}*/
ListView {
id: lvprojects
dataModel: cppObj.model()
listItemComponents: [
ListItemComponent {
type: "item"
Container {
horizontalAlignment: HorizontalAlignment.Center
layout: DockLayout {}
touchPropagationMode: TouchPropagationMode.Full;
StandardListItem {
title:ListItemData.desc
}
}
}
]
onTriggered: {
var selectedItem = dataModel.data(indexPath);
onClicked: {
// show issue's comment page when the button is clicked
cppObj.onIssueClicked(selectedItem.name);
}
}
}
}
}
}
actions: [
ActionItem {
title: qsTr ("Add Issue")
imageSource: "asset:///images/actionbar_icon_add.png"
ActionBar.placement: ActionBarPlacement.OnBar
onTriggered: {
cppObj.onAddIssuesClicked();
}
},
ActionItem {
title: qsTr ("Issues")
imageSource: "asset:///images/actionbar_icon_issues.png"
ActionBar.placement: ActionBarPlacement.OnBar
onTriggered: {
cppObj.onIssuesClicked();
}
},
ActionItem {
title: qsTr ("Members")
imageSource: "asset:///images/actionbar_icon_members.png"
ActionBar.placement: ActionBarPlacement.OnBar
onTriggered: {
cppObj.onMembersClicked();
}
}
]
}
/*attachedObjects: [
ComponentDefinition {
id: settingsPage
source: "topsettings.qml"
}
]
onCreationCompleted: {
// Create the app menu for the cookbook.
menu = settingsPage.createObject();
}
onPopTransitionEnded: {
// Transition is done destroy the Page to free up memory.
page.destroy();
}*/
}
And here's the topsettings.qml
MenuDefinition {
settingsAction: SettingsActionItem {
imageSource: "asset:///images/navbar_icon_settings.png"
onTriggered: {
cppObj.onSettingsClicked();
}
}
Settings is not displayed:
Your code looks good: the only issue is that you try to define your Menu.definition in a Page, but you should be defining it in a Pane. See here for example: https://github.com/Kernald/tt-rss-bb10/blob/master/assets/main.qml#L9
By the way, note that you have predefined actions for Help and Settings. See the link before, I used them too.
Let's say you have the NavigationPane as the QML document loaded first upon application launch, so that's should be placed in MenuDefinition like that:
NavigationPane {
Menu.definition: MenuDefinition {
helpAction: HelpActionItem {
onTriggered: {
}
}
actions: [
ActionItem {
title: "Action 1"
imageSource: "asset:///images/ic_action1.png"
onTriggered: {
}
},
ActionItem {
title: "Action 2"
imageSource: "asset:///images/ic_action2.png"
onTriggered: {
}
}
]
}
firstPage: Page {
}
}
Note, that there might be up to 4 elements and HelpActionItem as well SettingsActionItem are already defined if you want to implement similar menu entries.
I am trying to sort a list of items in the QML listview, as per the input provided by user. I have written the logic for sorting but unable to set the sorted model. It seems the sorted model is not assigned tp the actual model used for the original ListView. Please have a look to the code and let me know if I am doing something wrong.
//main.qml
import QtQuick 1.1
Rectangle {
id: page
width: 500; height: 400
color: "#edecec"
ModifiedForSorting {
id: search; focus: true
}
}
//ModifiedForSorting.qml
import QtQuick 1.1
FocusScope {
id: focusScope
width: 250; height: 28
Text {
id: typeSomething
anchors.fill: parent; anchors.leftMargin: 8
verticalAlignment: Text.AlignVCenter
text: "Type here..."
color: "gray"
font.italic: true
}
MouseArea {
anchors.fill: parent
onClicked: { focusScope.focus = true; textInput.openSoftwareInputPanel(); }
}
TextInput {
id: textInput
anchors { left: parent.left; leftMargin: 8;rightMargin: 8; verticalCenter: parent.verticalCenter }
focus: true
selectByMouse: true
onTextChanged: {
//update list as per input from user
container.getSortedItems(textInput.text);
color = "red"
}
}
states: State {
name: "hasText"; when: textInput.text != ''
PropertyChanges { target: typeSomething; opacity: 0 }
}
transitions: [
Transition {
from: ""; to: "hasText"
NumberAnimation { exclude: typeSomething; properties: "opacity" }
},
Transition {
from: "hasText"; to: ""
NumberAnimation { properties: "opacity" }
}
]
Rectangle {
id: container
width: 500; height: 400
color: "#343434"
anchors.top: textInput.bottom
ListModel {
id: namesModel
ListElement {
title: "Mumbai"
}
ListElement {
title: "Pune"
}
ListElement {
title: "Bangalore"
}
ListElement {
title: "Kolkata"
}
ListElement {
title: "Hyderabad"
}
ListElement {
title: "Nagpur"
}
ListElement {
title: "Thane"
}
}
// The delegate for each item in the model:
Component {
id: listDelegate
Item {
id: delegateItem
width: listView.width; height: 55
clip: true
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: 10
Column {
anchors.verticalCenter: parent.verticalCenter
Text {
text: title
font.pixelSize: 15
color: "white"
}
}
}
}
}
function showAll() {
var filteredItems = "";
for (var i = 0; i < namesModel.count; i++) {
filteredItems = filteredItems + namesModel.get(i).title;
}
listView.model = filteredItems;
//namesModel = filteredItems;
}
function getSortedItems(searchTerm) {
var filteredItems = "";
if (searchTerm === "") {
showAll();
return;
}
for (var i = 0; i < namesModel.count; i++) {
if (namesModel.get(i).title.indexOf(searchTerm) === 0) {
filteredItems = filteredItems + namesModel.get(i).title;
}
}
listView.model = filteredItems;
//namesModel = filteredItems;
}
// The ListView:
ListView {
id: listView
anchors.fill: parent; anchors.margins: 20
model: namesModel
delegate: listDelegate
}
}
}
As shown above I guess the sorted model is not reassigned to the ListView. I am not sure. Please help me out. Thanks.
Taking a look at your code I guess you mean filtering items, and not sorting.
In your functions you try to assign a string value filteredItems to the listView.model property. You're probably getting an explicit error for that in your application log.
To solve this error you can use an auxiliary ListModel and populate it with just the items that fits your filter.
Try to replace all your code below the listDelegate Component with this:
ListModel{ id: filteredModel }
function getSortedItems(searchTerm) {
// Clear the aux model
filteredModel.clear();
// Add fitting items to the aux model
for (var i = 0; i < namesModel.count; i++) {
if (searchTerm === "" || namesModel.get(i).title.indexOf(searchTerm) === 0) {
filteredModel.append(namesModel.get(i));
}
}
}
// The ListView:
ListView {
id: listView
anchors.fill: parent; anchors.margins: 20
model: filteredModel
delegate: listDelegate
}
You can now do this with far less code using Array filter(). Below is an example written using Qt6.x syntax:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
background: Rectangle { color: "#edecec" }
property var cities: [
{ title: "Mumbai" },
{ title: "Pune" },
{ title: "Bangalore" },
{ title: "Kolkata" },
{ title: "Hyderabad" },
{ title: "Nagpur" },
{ title: "Thane" }
]
ColumnLayout {
anchors.fill: parent
TextField {
id: textField
Layout.fillWidth: true
placeholderText: qsTr("Type something here")
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: cities.filter( city => !textField.text || city.title.toLowerCase().indexOf(textField.text.toLowerCase()) !== -1 )
delegate: ItemDelegate { text: modelData.title }
}
}
}
You can Try it Online!
Alternatively, you can consider using a ListModel combined with a DelegateGroup. The advantage of this is you do not need to repopulate a ListModel everytime the filter changes. Instead, you update the visibility of the item in the DelegateGroup:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQml.Models
Page {
background: Rectangle { color: "#edecec" }
ListModel {
id: cities
ListElement { title: "Mumbai" }
ListElement { title: "Pune" }
ListElement { title: "Bangalore" }
ListElement { title: "Kolkata" }
ListElement { title: "Hyderabad" }
ListElement { title: "Nagpur" }
ListElement { title: "Thane" }
}
ColumnLayout {
anchors.fill: parent
TextField {
id: textField
Layout.fillWidth: true
placeholderText: qsTr("Type something here")
onTextChanged: delegateModel.refresh()
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: DelegateModel {
id: delegateModel
model: cities
groups: [
DelegateModelGroup {
id: allItems
name: "all"
includeByDefault: true
},
DelegateModelGroup {
id: visibleItems
name: "visible"
}
]
filterOnGroup: "visible"
delegate: ItemDelegate { text: title }
function refresh() {
allItems.setGroups(0, allItems.count, ["all"]);
for (let i = 0; i < allItems.count; i++) {
let v = true;
let item = allItems.get(i);
if (textField.text) {
v = item.model.title.toLowerCase().indexOf(textField.text.toLowerCase()) !== -1;
}
if (v) allItems.setGroups(i, ["all", "visible"] );
}
}
}
}
}
Component.onCompleted: delegateModel.refresh()
}
You can Try it Online!