How to dynamically update a child ListModel within a singleton? - qt

I try to understand how shared qml objects can be used globally by other qml and javascript files. I have a QML app that has two comboboxes in one window. One is parent (country) combo box in ApplicationWindow, other one is child (city) combo box in Page1.qml. When selecting a country, the child combo box should have cities of the country at the same time. This is possible when using the comboboxes and the function are in ApplicationWindow, I had asked a question about it here. With the help of the answer of my previous question I tried to connect child combobox outside of ApplicationWindow to parent combobox in ApplicationWindow using qmldir, singleton and javascript.
I tried child combobox's list model to be a singleton object, it doesn't work and it gives error qrc:/script.js:11: Error: Cannot assign QJSValue to QQmlListModel*
main.qml:
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
import "script.js" as JS
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
property int currentindex: comboBox2.currentIndex
Settings{
property alias country : comboBox2.currentIndex
}
Dialog {
id: dialog
title: "Select Country"
implicitWidth: parent.width
implicitHeight: parent.height/2
Column{
ComboBox {
id: comboBox2
x: 199
y: 176
width: 277
height: 48
currentIndex: 0
model:
ListModel {
ListElement { text: qsTr("USA") }
ListElement { text: qsTr("Russia") }
ListElement { text: qsTr("Iran") }
}
onCurrentIndexChanged:{
currentindex = currentIndex
JS.coord_combo_changed()
}
}
}
}
ColumnLayout{
anchors.horizontalCenter: parent.horizontalCenter
spacing:20
width: parent.width
Button{
anchors.horizontalCenter: parent.horizontalCenter
id:select_country
text:"select country"
onClicked: dialog.open()
}
Page1{
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Page1.qml:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
Page{
RowLayout{
anchors.horizontalCenter: parent.horizontalCenter
spacing:5
width: parent.width
Text{
text:"Select City: "
}
ChildCombo{
id:comboBox1
model: Shared.childmodel
}
}
}
qmldir:
singleton Shared 1.0 Shared.qml
Shared.qml:
pragma Singleton
import QtQuick 2.9
import QtQuick.Controls 2.2
QtObject {
property ListModel childmodel: ChildModel{}
}
ChildModel.qml:
import QtQuick 2.0
ListModel {}
script.js:
function coord_combo_changed(){
if(currentindex === 0){
Shared.childmodel = ["New York", "Washington", "Houston"]
return Shared.childmodel
}
else if (currentindex === 1){
Shared.childmodel = ["Moscow","Saint Petersburg","Novosibirsk"]
return Shared.childmodel
}
else if (currentindex === 2){
Shared.childmodel = ["Tehran","Tabriz","Shiraz"]
return Shared.childmodel
}
}
I also tried to make combobox as a shared object, it also didn't work and it didn't give any error either. Is it possible to update the child combo box?
Thank you.

In QML a model can be a list, a ListModel, a QAbstractItemModel, etc., but those objects are not equivalent. And that is your mistake, you are trying to point out that a ListModel is a list, in this case the solution is to first clean the model and then add the elements with the append method:
function coord_combo_changed(){
var values = [];
if(currentindex === 0){
values = ["New York", "Washington", "Houston"]
}
else if (currentindex === 1){
values = ["Moscow","Saint Petersburg","Novosibirsk"]
}
else if (currentindex === 2){
values = ["Tehran","Tabriz","Shiraz"]
}
Shared.childmodel.clear()
for(var i in values){
Shared.childmodel.append({"text": values[i]});
}
}
On the other hand I see that your code throws warning indicating that you should not use the anchors if the items are within a layout, in your case you should change to:
ColumnLayout{
anchors.horizontalCenter: parent.horizontalCenter
spacing:20
width: parent.width
Button{
Layout.alignment: Qt.AlignHCenter // <---
id:select_country
text:"select country"
onClicked: dialog.open()
}
Page1{
Layout.alignment: Qt.AlignHCenter // <---
}
}

Related

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

How to set objectName for PopupItem from QML?

QML Popup and derived controls are creating a PopupItem object which is a visual representation of it, but Popup itself is parented to the contentData of the application window. objectName specified for Popup is not applied to PopupItem. For example, the following application:
import QtQuick 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Popup Test")
Button {
text: "Open"
onClicked: dummyPopup.open()
}
Popup {
id: dummyPopup
objectName: "dummyPopup"
x: 100
y: 100
width: 200
height: 300
modal: true
focus: true
}
}
creates PopupItem with empty objectName
Is there a way to set objectName for PopupItem from QML?
Set the objectName of its contentItem upon completion:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Popup Test")
Button {
text: "Open"
onClicked: dummyPopup.open()
}
Popup {
id: dummyPopup
objectName: "dummyPopup"
x: 100
y: 100
width: 200
height: 300
modal: true
focus: true
Component.onCompleted: {
contentItem.objectName = "foo"
print(contentItem)
}
}
}
By the way, if this is for auto tests, I have a hack in C++ that avoids the need to give an objectName to the contentItem:
QObject *TestHelper::findPopupFromTypeName(const QString &typeName) const
{
QObject *popup = nullptr;
foreach (QQuickItem *child, overlay->childItems()) {
if (QString::fromLatin1(child->metaObject()->className()) == "QQuickPopupItem") {
if (QString::fromLatin1(child->parent()->metaObject()->className()).contains(typeName)) {
popup = child->parent();
break;
}
}
}
return popup;
}
You can then use that function like this in your test:
const QObject *newProjectPopup = findPopupFromTypeName("NewProjectPopup");
QVERIFY(newProjectPopup);
QTRY_VERIFY(newProjectPopup->property("opened").toBool());

TableView QML Type onClicked event

I am currently using a TableView in my QML file. I would like to know which cell the user clicked.. There is a post here: https://forum.qt.io/topic/84284/tableview-onclicked-slot/2 which shows the onClicked code in the QML file. However, when i tried on my code it says invalid property name. My QML file code is:
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import TransactionHistoryTableModel 1.0
import tech.siotgov.DataManager 1.0
import "../../components" as Components
import "../../utils/httpRequest.js" as HttpRequest
Rectangle {
id: root
objectName: "TransactionHistory"
color: "white"
property string pageTitle: "Transaction History"
ColumnLayout {
id: home
anchors.leftMargin: parent.width * 0.05
anchors.rightMargin: parent.width * 0.05
anchors.fill: parent
anchors.horizontalCenter: parent.horizontalCenter
Components.PageTitle {
title: pageTitle
}
Rectangle {
id: transactionHistoryDisplay
color: "white"
Layout.fillWidth: true
Layout.preferredHeight: parent.height - 100
Components.Table {
model: _transactionHistoryTableModelAPI
columnWidths: [0.4, 0.6]
onClicked: {
console.log(" click ")
console.log(color_string)
}
}
}
Item { //spacer
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
anchors.fill: parent
color: "white"
}
}
}
Component.onCompleted: {
const locationId = DataManager.currentLocation.locationId;
const d = new Date()
d.setTime(d.getTime() - d.getTimezoneOffset()*60*1000);
const datetimeStamp = d.toISOString().split('.')[0]
_transactionHistoryTableModelAPI.resetTable(locationId);
HttpRequest.query(
"query { transactionsByLocation(fromDateTime:\"2019-01-01T07:54:34\", toDateTime:\"" + datetimeStamp + "\", location:" + locationId + ") { transactionId, datetime, items { transactionItemId }, transactionType {name}, location_1{locationName}, location_2{locationName} } }",
res => {
_transactionHistoryTableModelAPI.updateTable(res.data.transactionsByLocation);
})
}
}
The Table.qml file is:
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
TableView {
anchors.fill: parent
clip: true
property var columnWidths: [0.5, 0.5] // as fractions of parent width
// preferably overwrite this when using
columnWidthProvider: function (column) { return Math.max(parent.width * columnWidths[column], 1) }
delegate: Rectangle {
implicitHeight: text.implicitHeight
border.color: "#dddddd"
color: (heading==true)?"#dddddd":"white"
Text {
id: text
text: tabledata
width: parent.width
wrapMode: Text.Wrap
padding: 5
}
}
}
In a view you must set the MouseArea in the delegate and expose it through a signal from the root of the component:
Table.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
TableView {
id: root
anchors.fill: parent
clip: true
signal clicked(int row, int column) // <---
property var columnWidths: [0.5, 0.5] // as fractions of parent width
// preferably overwrite this when using
columnWidthProvider: function (column) { return Math.max(parent.width * columnWidths[column], 1) }
delegate: Rectangle {
implicitHeight: text.implicitHeight
border.color: "#dddddd"
color: heading ? "#dddddd" : "white"
Text {
id: text
text: tabledata
width: parent.width
wrapMode: Text.Wrap
padding: 5
}
MouseArea{
anchors.fill: parent
onClicked: root.clicked(model.row, model.column) // <---
}
}
}
*.qml
// ...
Components.Table {
model: _transactionHistoryTableModelAPI
columnWidths: [0.4, 0.6]
onClicked: console.log(row, column)
}
// ...

How to dynamically update a child combobox from parent combobox

I have a QML app that has two combo boxes, one is parent (country) combo box, other one is child (city) combo box. When selecting a country, the child combo box should have cities of the country at the same time.
I have a code that first combo box selects country but it doesn't set the list model of city's combo box. It sets after reopening the app.
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
property alias comboBox: comboBox2
Settings{
property alias country : comboBox2.currentIndex
}
Dialog {
id: dialog
title: "Select Country"
implicitWidth: parent.width
implicitHeight: parent.height/2
Column{
ComboBox {
id: comboBox2
x: 199
y: 176
width: 277
height: 48
currentIndex: 0
flat: false
model: ["USA","Russia","Iran"]
}
}
}
ColumnLayout{
anchors.horizontalCenter: parent.horizontalCenter
spacing:20
width: parent.width
Button{
anchors.horizontalCenter: parent.horizontalCenter
id:select_country
text:"select country"
onClicked: dialog.open()
}
RowLayout{
anchors.horizontalCenter: parent.horizontalCenter
spacing:5
width: parent.width
Text {
text:"Select City: "
}
ComboBox {
id: comboBox1
x: 199
y: 176
width: 277
height: 48
model: ListModel {
id: model1
dynamicRoles: true
Component.onCompleted: {
coord_combo()
}
function coord_combo(){
var i = comboBox1.currentIndex
if (comboBox2.currentIndex===0){
comboBox1.model = ["New York","Washington","Houston"]
return comboBox1
}
else if (comboBox2.currentIndex === 1){
comboBox1.model = ["Moscow","Saint Petersburg","Novosibirsk"]
return comboBox1
}
else if (comboBox2.currentIndex === 2){
comboBox1.model = ["Tehran","Tabriz","Shiraz"]
return comboBox1
}
}
}
}
}
}
}
I used javascript functions to change Material QML Theme using combo box. But the same logic doesn't work for updating other combo box's list model. Is there any way to update the child combobox dynamically using qml and javascript?
You should use onCurrentIndexChanged signal in parent ComboBox and update the child whenever it is triggered. Something like this;
onCurrentIndexChanged:
{
if(currentIndex === 0)
comboBox1.model = ["New York", "Washington", "Houston"]
...
}
You can go further and write a function to get the cities of current country. Then you can update the model of child ComboBox whenever the currentIndex of parent ComboBox changes.

How to use mouseclick area from loaded Form in qml?

below is my code snippet where i'm loading qml ui using dynamic object creation method now i have to implement mousearea which reside in loaded file, can anyone help me to do this
Qt.createQmlObject(" import QtQuick 2.0
Loader {
id: pageLoader
source: '/HomeScreenForm.ui.qml'
anchors.fill: parent
anchors.rightMargin: 0
anchors.leftMargin: 0
anchors.bottomMargin: parent
anchors.topMargin: parent
}
", rectangle7)
Create custom item contains MouseArea. To make the area accessible from outside you can use alias, for example:
MyItem.qml
import QtQuick 2.7
Rectangle {
id: root
color: "yellow"
property alias area: mouseArea
MouseArea {
id: mouseArea
anchors.fill: parent
}
Text {
anchors.centerIn: parent
text: "Click me!"
}
}
And then you can create it dynamically:
import QtQuick 2.7
import QtQuick.Window 2.0
Window {
id: mainWindow
width: 600
height: 600
visible: true
Component.onCompleted: {
var component = Qt.createComponent("MyItem.qml");
if (component.status === Component.Ready) {
var obj = component.createObject(mainWindow);
obj.width = 200;
obj.height = 200;
obj.anchors.centerIn = mainWindow.contentItem;
obj.area.onPressed.connect(
function(mouse){
console.log("pressed at (", mouse.x,",",mouse.y,")")
});
}
}
}
Another way is using Connections, as #derM already noticed.

Resources