I have the following code. It basically builds a dialog with a TableView in it, in which I can set values manually in the cells:
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.2
Window {
visible: true
width: 538
height: 360
MouseArea {
anchors.fill: parent
onClicked: {
dial.open()
}
}
Dialog {
id:dial
width: 300
height: 500
title: "Set Path Parameters"
standardButtons: StandardButton.Ok | StandardButton.Cancel
signal updSig(var content)
ListModel {
id: streetsModel
property int count: 100
Component.onCompleted: {
for (var i=1 ; i<= count ; ++i)
streetsModel.append({"street_alias":"str_"+i , "start_xstart_y": "", "end_xend_y": "", "width": ""})
}
//todo: erste Spalte auch einbeziehen
function getColumnContent(role) {
var cont = ""
for (var i=0 ; i< count ; ++i) {
var cellContent;
cellContent = streetsModel.get(i).role
//streetsModel.get(styleData.row).role
if(!(cellContent === "") && !(cellContent === undefined))
cont += cellContent === "" ? "" : (cellContent + "\n")
}
cont = cont.slice(0,-1)
return cont
}
}
Item {
anchors.fill: parent
Component {
id: editableDelegate
Item {
TextInput {
id: textinput
width: parent.width
anchors.margins: 4
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: styleData.value
color: styleData.textColor
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: textinput.forceActiveFocus()
}
onEditingFinished: {
var role = styleData.role
streetsModel.set(styleData.row, {role: textinput.text})
console.log(streetsModel.get(styleData.row).role)
console.log(styleData.row, role)
}
}
}
}
}
TableView {
id: streetsTab
model: streetsModel
anchors.margins: 12
anchors.fill:parent
TableViewColumn {
id: strt_al_cl
role: "street_alias"
title: "Street Alias"
width: 120
}
TableViewColumn {
id: start_xy_cl
role: "start_xstart_y"
title: "StartX,StartY"
width: 120
}
TableViewColumn {
id: end_xy_cl
role: "end_xend_y"
title: "EndX,EndY"
width: 120
}
TableViewColumn {
id: width_cl
role: "width"
title: "Width"
width: 120
}
itemDelegate: {
return editableDelegate;
}
}
onAccepted: {
var content = [streetsModel.getColumnContent(start_xy_cl.role), streetsModel.getColumnContent(end_xy_cl.role), streetsModel.getColumnContent(width_cl.role)];
updSig(content)
this.close()
}
onRejected: this.close()
}
}
Now, if I set the values 1 in cellnumber (0,0), 2 in cellnumber (0,1) and 3 in cellnumber (0,2) (and click on an arbitrary other cell after that to insert the content), I get as content in my onAccepted-method [3,3,3]. This means the values for the first two roles start_xstart_y and end_xend_y get overwritten by the third role value for the role width. Why is this? If the code is run, on the console, the correct values in the cells are printed (see the console.log(...) parts), which stem from the model directly. So I don't understand why they are finally overwritten
Related
I was thinking I need a component similar to ListModel, but I need to extend it to expose a readonly bool property such as "all list elements were within minimum and maximum limit" so I can do logic outside the component the determine certain things. How should I go about doing this extending a boolean property based on model's contents?
I guess naive way is to just add the qml property and do javascript loop on QML side to check all model contents but that might not be so good performance
Have you considered DelegateModel? It allows you to create "views" on your ListModel so you can control what you want to be displayed via the filterOnGroup property.
It is rather difficult to comprehend, but, in the following example, I have a ListModel containing 5 cities. When you start changing the RangeSlider the 5 cities will be filtered based on the minimum/maximum population selected. This works by updating the boolean function filter on the DelegateModel to reflect the cities that are now visible.
property var filter: model => model.pop >= rangeSlider.first.value
&& model.pop <= rangeSlider.second.value
Here's the full code snippet:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
Page {
anchors.fill: parent
ColumnLayout {
anchors.fill: parent
Label { text: qsTr("States") }
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: DelegateModel {
id: filterDelegateModel
property int updateIndex: 0
property var filter: model => model.pop >= rangeSlider.first.value
&& model.pop <= rangeSlider.second.value
onFilterChanged: Qt.callLater(update)
model: us_states
groups: [
DelegateModelGroup {
id: allItems
name: "all"
includeByDefault: true
onCountChanged: {
if (filterDelegateModel.updateIndex > allItems.count) filterDelegateModel.updateIndex = allItems.count;
if (filterDelegateModel.updateIndex < allItems.count) Qt.callLater(update, filterDelegateModel.updateIndex);
}
},
DelegateModelGroup {
id: visibleItems
name: "visible"
}]
filterOnGroup: "visible"
delegate: Frame {
id: frame
width: ListView.view.width - 20
background: Rectangle {
color: (frame.DelegateModel.visibleIndex & 1) ? "#f0f0f0" : "#e0e0e0"
border.color: "#c0c0c0"
}
RowLayout {
width: parent.width
Text {
text: (frame.DelegateModel.visibleIndex + 1)
color: "#808080"
}
Text {
Layout.fillWidth: true
text: model.state
}
Text {
text: qsTr("pop: %1 M").arg((pop / 1000000).toFixed(2))
}
}
}
function update(startIndex) {
startIndex = startIndex ?? 0;
if (startIndex < 0) startIndex = 0;
if (startIndex >= allItems.count) {
updateIndex = allItems.count;
return;
}
updateIndex = startIndex;
if (updateIndex === 0) {
allItems.setGroups(0, allItems.count, ["all"]);
}
for (let ts = Date.now(); updateIndex < allItems.count && Date.now() < ts + 50; updateIndex++) {
let visible = !filter || filter(allItems.get(filterDelegateModel.updateIndex).model);
if (!visible) continue;
allItems.setGroups(updateIndex, 1, ["all", "visible"]);
}
if (updateIndex < allItems.count) Qt.callLater(update, updateIndex);
}
Component.onCompleted: Qt.callLater(update)
}
}
Label { text: "Population Range" }
RangeSlider {
id: rangeSlider
Layout.fillWidth: true
from: 0
to: 100000000
first.value: 1
first.onMoved: Qt.callLater(filterDelegateModel.update)
second.value: 100000000
second.onMoved: Qt.callLater(filterDelegateModel.update)
stepSize: 1000000
}
Label { text: qsTr("Minimum %1 M").arg((rangeSlider.first.value / 1000000).toFixed(2)) }
Label { text: qsTr("Maximum %1 M").arg((rangeSlider.second.value / 1000000).toFixed(2)) }
}
ListModel {
id: us_states
ListElement { state:"California"; pop: 39350000 }
ListElement { state:"Texas"; pop: 28640000 }
ListElement { state:"New York"; pop: 8380000 }
ListElement { state:"Nevada"; pop: 3030000 }
ListElement { state:"Las Vegas"; pop: 644000 }
}
}
You can Try it Online!
I have refactored the above into a FilterDelegateModel reusable component. Feel free to check it out:
https://github.com/stephenquan/qt5-qml-toolkit
https://github.com/stephenquan/qt5-qml-toolkit/wiki/FilterDelegateModel
I'm trying to adapt the row height of a TreeView to automatically fit the content in its itemDelegate, but I have no clue how to do so.
So far, I tried to create a property "lines" in my itemDelegate object, but everytime I try to access it QML says my treeDeleg item is undefined.
Is there a way to get the result I want ?
EDIT : following what JarMan said, it's not possible to reference a delegateComponent by ID. So instead, is there's a way to automatically adapt the row height to its content ? Something along the lines of "height: auto" in CSS. This is really important because long values make my interface hard to read.
If all else fails, would there be a better component to create a tree styled view able to contain long, editable values ?
main.qml :
TreeView
{
id:treeView
anchors.top: rowInfo.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
itemDelegate: TreeDelegate
{
id: treeDeleg
}
style: TreeViewStyle
{
id: styleTree
rowDelegate:Rectangle
{
id : rowDeleg
height: styleData.selected ? 75 : 25 * treeDeleg.lines
color:
{
var baseColor = styleData.alternate ? "white" : "#d9edf7"
return styleData.selected ? "yellow" : baseColor
}
}
}
Component.onCompleted:
{
treeView.model = Qt.binding(function()
{
if (typeof theModel !== "undefined")
{
return theModel;
}
else
{
return null;
}
});
}
TableViewColumn
{
id:column1
role: "Name"
title: "Name"
width: 450
}
TableViewColumn
{
id:column3
role: "Value"
title: "Value"
width: 400
}
TableViewColumn
{
id:column4
role: "Desc"
title: "Description"
width: 750
}
}
TreeDelegate.qml
Item
{
id: item
width : parent.width
height : 25
property var lines: null
TextEdit
{
id: text1
font.pixelSize: 14
readOnly: true
focus: false
width : parent.width
height: 25 * lineCount
anchors.left: parent.left
wrapMode: TextEdit.Wrap
text: (styleData.value !== null) ? (styleData.value.text + styleData.value.id) : "";
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
Component.onCompleted:
{
lines = text1.lineCount == 0 ? 1 : text1.lineCount;
console.log("Lines : " + lines);
}
}
}
```
I want to get the name of listElement (append dynamicaly with python) when I click for reuse this name with python function. How can I get this name ?
In this example I can only get the index 0 element...
QML part :
Rectangle {
id: listRowDb
anchors.top: toolbar.bottom
width: head_row.width
height: units.gu(100)
ListModel {
id: listDb
ListElement {
name: ""
}
}
Component {
id: listDbDelegate
Row {
spacing: 100
Text { text: 'Nom de la DB : ' + name}
}
}
ListView {
id: listView1
anchors.fill: parent
model: listDb
delegate: listDbDelegate
#Don't work. When I click I get the index 0 name.
MouseArea {
anchors.fill: parent
onClicked: {python.call('example.speak2', [listDb.get(listView1.currentIndex).name], function(returnValue) {console.log(returnValue)});}
}
}
}
Python {
id: python
Component.onCompleted: {
addImportPath(Qt.resolvedUrl('../src/'));
importModule('example', function() {
console.log('module imported');
#Just a litlle test
python.call('example.speak', ['Hello World!'], function(returnValue) {
console.log('example.speak returned ' + returnValue);
})
#Python list all DB and append in listModel
python.call('example.listDb', [], function(returnValue) {
for(var i=0; i<returnValue.length; i++) {
listDb.append({"name":returnValue[i]});
}
});
});
}
The python part work (only print element name)
You have to set the MouseArea on the delegate:
Component {
id: listDbDelegate
Row {
spacing: 100
Text {
text: 'Nom de la DB : ' + name
MouseArea{
anchors.fill: parent
onClicked: console.log(name, index)
}
}
}
}
ListView {
id: listView1
anchors.fill: parent
model: listDb
delegate: listDbDelegate
}
i was trying to make an app with Local Storage for the database and i want to display the data from the database to a listview in different QML file. i know i can make a property alias to the listview so i can access it from another file then append the query result. but when i hit the button to show the data it says that listViewKos was not define but it was already property alias
i did my best to keep the code short, some of the component might have been deleted due to it but appart from the problem i describe above everything works just fine.
#main.qml the JS is just a JS object where the data came from
import QtQuick 2.5
import QtQuick.Controls 2.5
import QtQuick.Dialogs 1.1
import QtQuick.LocalStorage 2.0
import "./Storage.js" as Storage
ApplicationWindow {
id: applicationWindow
width: 640
height: 480
property int kontrakanJumlahKamar
property int kontrakanPrice
property int kosPrice
property string kosGenderType
property alias kosloader: kosloader
property var db
Component.onCompleted: {
db = LocalStorage.openDatabaseSync("ngomahyuk", "1.0", "StorageDatabase", 1000000)
db.transaction(function(tx){
tx.executeSql('CREATE TABLE IF NOT EXISTS kos(namakos TEXT, alamat TEXT, thumbnail TEXT)');
});
// insert data for kos
db.transaction(function(tx){
for (var i = 0; i < Storage.kos.length; i++){
try {
tx.executeSql("INSERT INTO kos (namakos, alamat, thumbnail) VALUES('"+Storage.kos[i].namakos+"','"+Storage.kos[i].alamat+"','"+Storage.kos[i].thumbnail+"'");
} catch (err) {
console.log(err);
}
}
});
}
Button {
id: button
text: qsTr("Search")
MouseArea{
anchors.fill: parent
onClicked:{
if (textFieldHarga.text === ""){
kosPrice = 0
} else {
kosPrice = parseInt(textFieldHarga.text)
}
kosGenderType = comboBoxGender.currentText
kosloader.visible = true
db.transaction(function(tx){
var rs = tx.executeSql("SELECT * FROM kos WHERE gender = '"+kosGenderType+"' AND harga <= "+kosPrice);
if (rs.rows.length === 0){
alertDialogKos.open()
}else {
for (var i = 0; i < rs.rows.length; i++){
listViewKos.model.append({ //this is the one that suppose to be working
imagePath : rs.rows[i].thumbnail,
kosName : rs.rows[i].namakos,
kosAlamat : rs.rows[i].alamat,
})
}
kosloader.source = "Kos.qml"
}
});
}
}
MessageDialog{
// messagedialog code deleted to keep it short
}
background: Rectangle {
// deleted to keep it short
}
contentItem: Text {
id: textItem
text: "Search"
}
}
Loader{
id: kosloader
width: 640
height: 480
opacity: 1
clip: false
visible: false
active: false
anchors.fill: parent
source: ""
}
}
#Kos.qml i use PageBackground.qml as a background
import QtQuick 2.4
import QtQuick.Controls 2.3
PageBackground {
id: kos
width: 640
height: 480
property alias listViewKos: listViewKos
ListView {
id: listViewKos
x: 15
y: 87
width: 640
height: 410
clip: true
model: ListModel{
// need to be in for loop and data from database
}
// delegate listview template
delegate: Item {
height : 195
width : 640
Image {
id: idthumbnail
width: 235
height: 165
source: imagePath
}
Text {
id: idnamakos
x: 252
y: 8
text: kosName
}
Text {
id: idalamat
text: qsTr("Alamat : " + kosAlamat)
}
}
}
}
}
I am using QtQuick TableView to show data from a database through QSqlTableModel and QSortFilterProxyModel.
The remove row operation doesn't work as it should. I have implemented a method in a class derived from QSortFilterProxyModel to call removeRows methods of QSortFilterProxyModel.
Everything works correctly as long as I have a filter setted in QSortFilterProxyModel ( i set it through a text box ). But when the filter is empty, the TableView rowCount property doesn't decrement and, after each delete, the currentRow property is set to rowCount-2. Why? To me it looks like a bug. Why it works when the filter is not empty?
Q_INVOKABLE void eliminaCliente(int row) {
removeRows(row,1);
}
import QtQuick 2.6
import QtQuick.Controls 1.5
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
import Material 0.2
import Material.ListItems 0.1
ApplicationWindow {
id: root
visible: true
width: 1024
height: 640
title: qsTr("assiBase")
Page {
id: pLayout
anchors.fill: parent
ColumnLayout {
anchors.fill: parent
Toolbar {
id: aBar
Layout.fillWidth: true
page: pLayout
backgroundColor: "#eeeeee"
RowLayout {
anchors.fill: parent
ActionButton {
id: addButton
Layout.leftMargin: 10
iconName: "content/add_circle"
backgroundColor: "#4CAF50"
onClicked: modalDialog.show()
isMiniSize: true
}
ActionButton {
id: editButton
iconName: "content/create"
isMiniSize: true
}
ActionButton {
id: deleteButton
iconName: "action/delete"
isMiniSize: true
backgroundColor: "#FF0000"
onClicked: {
if (dataView.currentRow != -1) {
var r = dataView.currentRow
console.log(dataView.currentRow)
sqlSortedData.eliminaCliente(dataView.currentRow)
console.log(dataView.rowCount)
//dataView.currentRow = r
}
}
}
RowLayout {
Layout.alignment: Qt.AlignRight
Icon {
name: "action/search"
Layout.alignment: Qt.AlignBottom
}
TextField {
id: searchBox
Layout.rightMargin: 20
Layout.minimumWidth: 400
Layout.preferredWidth: 500
placeholderText: qsTr("cerca...")
onTextChanged: sqlSortedData.setFilterWildcard(searchBox.text)
font.capitalization: Font.MixedCase
}
}
}
}
TableView {
anchors.top: aBar.bottom
anchors.topMargin: 3
sortIndicatorVisible: true
frameVisible: false
Layout.fillWidth: true
Layout.fillHeight: true
onSortIndicatorColumnChanged: model.sort(sortIndicatorColumn, sortIndicatorOrder)
onSortIndicatorOrderChanged: model.sort(sortIndicatorColumn, sortIndicatorOrder)
id: dataView
TableViewColumn {
role: "ID"
visible: false
}
TableViewColumn {
role: "Nome"
title: "Nome"
width: 200
}
TableViewColumn {
role: "Residenza"
title: "Residenza"
width: 200
}
TableViewColumn {
role: "Assicurazione"
title: "Assicurazione"
width: 200
}
TableViewColumn {
width: 128
resizable: false
delegate: RowLayout {
anchors.fill: parent
clip: true
IconButton {
iconName: "content/create"
onClicked: console.log(styleData.row)
}
IconButton {
iconName: "action/delete"
onClicked: {
console.log(styleData.row)
sqlSortedData.eliminaCliente(styleData.row)
console.log(dataView.rowCount)
}
}
}
}
model: sqlSortedData
}
}
}
Take a look at here. There is an workaround suggestion.
It seems like QSortFilterProxyModel needs some love for a long time.