Working in QML 5, I wish to format my table content (columns) differently based on the column number. For example, col 1 holds a string, col 2 holds a checkmark, col 3 holds and image. etc.
I was hoping to use the column number in my delegatechooser but it fails (never matches a roleValue). Though if I print model.column in the delegate it does show my column number.
How can I use column number in my chooser?
DelegateChooser {
id: chooser
role: "model.column"
DelegateChoice { roleValue: "0"; ItemDelegate { ... } }
DelegateChoice { roleValue: "1"; SwitchDelegate { ... } }
DelegateChoice { roleValue: "2"; SwipeDelegate { ... } }
}
The role property of DelegateChooser is not the one you need.
You need the column property of DelegateChoice:
DelegateChooser {
DelegateChoice { column: 0; ItemDelegate { ... } }
DelegateChoice { column: 1; SwitchDelegate { ... } }
DelegateChoice { column: 2; SwipeDelegate { ... } }
}
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
Is there any way to get value or property from Stackview item when it popped in Qml?
I want to get the edited name in 'EditProfile.qml' when it popped to 'Profile.qml' in the below project.
main.qml
StackView {
id: stackView
Component.onCompleted: push('Profile.qml', {name : 'David'})
}
Profile.qml
Page {
property string name: ''
Column {
Text {
text: 'name' + name
}
Button {
text: 'Edit'
onClicked: stackView.push('EditProfile.qml', {name : name})
}
}
}
EditProfile.qml
Page {
property alias name: txtName.text
Column {
TextEdit {
id: txtName
text: name
}
Button {
text: 'Back'
onClicked: stackView.pop()
}
}
}
After reading QT manual carefully, I found the answer. The push function returns the item which is pushed. so:
Profile.qml
Page {
id: root
property string name: ''
Column {
Text {
text: 'name' + name
}
Button {
text: 'Edit'
onClicked: {
var item = stackView.push('EditProfile.qml', {name : name})
item.exit.connect(change);
function change(text) {
item.exit.disconnect(change);
root.name = text;
}
}
}
}
}
EditProfile.qml
Page {
signal exit(var text)
property alias name: txtName.text
Column {
TextEdit {
id: txtName
text: name
}
Button {
text: 'Back'
onClicked: {
exit(txtName.text)
stackView.pop()
}
}
}
}
I try to make a converter app depending on two ComboBoxes using Controls 2.
For example:
// First combobox is input unit:
ComboBox {
id: res_combo1
width: res_comborect1.width
currentIndex: 0
model: ListModel {
id: model1
ListItem { text: qsTr("meter") }
ListItem { text: qsTr("mile") }
ListItem { text: qsTr("km") }
}
}
}
// Second combobox is output unit:
ComboBox {
id: res_combo2
width: res_comborect1.width
currentIndex: 0
model: ListModel {
id: model2
ListItem { text: qsTr("meter") }
ListItem { text: qsTr("mile") }
ListItem { text: qsTr("km") }
}
}
}
When current index is selected from first combobox, second combobox should remove the current index. When another index is selected from first, second should restore previous index and it should remove current index dynamically. For example if meter is selected, second combobox should be {"mile", "km"}
I only know a very long way that can be performed with combination of a few index, but my combobox data includes 20 item so I cannot apply this way. Long way:
function visible1(){
if(res_combo1.currentIndex==0){
return true
}
else{
return false
}
}
function visible2(){
if(res_combo1.currentIndex==1){
return true
}
else{
return false
}
}
function visible3(){
if(res_combo1.currentIndex==2){
return true
}
else{
return false
}
}
// if meter is selected from first combobox, second combobox:
RowLayout{
visible: parent.visible1()
ComboBox {
id: outcombo1
currentIndex: 0
model: ListModel {
id: model_o1
ListElement { text: qsTr("mile") }
ListElement { text: qsTr("km") }
}
}
}
// if mile is selected from first combobox:
RowLayout{
visible: parent.visible2()
ComboBox {
id: outcombo1
currentIndex: 0
model: ListModel {
id: model_o2
ListElement { text: qsTr("meter") }
ListElement { text: qsTr("km") }
}
}
}
// if km is selected from first combobox:
RowLayout{
visible: parent.visible3()
ComboBox {
id: outcombo1
currentIndex: 0
model: ListModel {
id: model_o3
ListElement { text: qsTr("meter") }
ListElement { text: qsTr("mile") }
}
}
}
I think we cannot change ListItem dynamically, so JavaScript with JSON list model is required by finding the current index via:
function find(model, criteria) {
for(var i = 0; i < model.count; ++i) if (criteria(model.get(i))) return i
return null
}
But I couldn't do this in QML. Is there any solution? Thanks
You cannot change a ListItem, but you CAN change the ListModel itself.
ListModel {
id: fromModel
ListElement {text: "m" }
ListElement {text: "mile" }
ListElement {text: "km" }
}
ListModel {
id: toModel
function updateModel()
{
// Update model Here
}
}
RowLayout
{
anchors.centerIn: parent
ComboBox
{
id: fromCombobox
model: fromModel
onCurrentIndexChanged: toModel.updateModel()
}
ComboBox
{
id: toComboBox
model: toModel
}
}
This way, every time the first combo changes, the second combo's model will be updated.
Before you update the model 2, check if the previously selected item in combo 2 will still be available after the update, in order to restore it once the model2 is rebuilt.
function updateModel()
{
// Save old selection
var selectedFrom = fromModel.get(fromCombobox.currentIndex).text
if(toComboBox.currentText != selectedFrom)
var valueToRestore = toComboBox.currentText
else
valueToRestore = ""
// Update model
clear()
for(var i=0; i<fromModel.count; i++)
{
if(i == fromCombobox.currentIndex)
continue
append(fromModel.get(i))
}
//Restore selection
for(var i=0; i<toModel.count; i++)
{
// If no value to restore, select first available
if(valueToRestore == "")
{
if(toModel.get(i).text != selectedFrom)
{
toComboBox.currentIndex = i
break
}
}
// Else, restore previously selected item
else
{
if(toModel.get(i).text == valueToRestore)
{
toComboBox.currentIndex = i
break
}
}
}
}
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
}
}
Is there a simple way to modify a row height given its index in a QML TableView ?
My problem is that I dynamically load data is the TableView's model. I have a "warnings" column in which i would like to display a list of warnings using Qt RichText HTML tags (ul, li). However, this list is higher than the cell that contains it and there is a y-overflow.
The QTableView class have many methods to solve this problem such as setRowHeight(int row, int height), resizeRowToContents(int row) or resizeRowsToContents().
But it seems that the QML equivalent does not have such methods to easily resize a row...
The rowDelegate might solve my problem but I don't know how to use it to modify the rows' heights separately (I mean given its index).
Does anyone had the same problem and could give me a trick to solve it ?
My TableView :
ListModel {
id: filesList
ListElement {
name:""
dir:""
type:""
status""
}
}
TableView {
id: filesTable
TableViewColumn {
id:nameColumn
role: "name"
title: "Name"
width: dropFiles.width*.2
}
TableViewColumn {
id:dirColumn
role: "dir"
title: "Path"
width: dropFiles.width*.8
}
TableViewColumn {
id:typeColumn
visible: false;
role: "type"
title: "Type"
width: dropFiles.width*.2
}
TableViewColumn {
id:statusColumn
visible: false;
role: "status"
title: "Status"
width: dropFiles.width*.3
}
rowDelegate: Rectangle {
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
height: parent.height
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
if (mouse.button == Qt.RightButton)
{
if(styleData.selected){
rowContextMenu.popup();
}
else {
filesTable.selection.deselect(0, filesTable.rowCount-1);
filesTable.selection.select(styleData.row);
rowContextMenu.popup();
}
}
}
}
}
anchors.fill: parent
selectionMode: SelectionMode.ExtendedSelection
model: filesList
}
The JS function that update my TableView :
function displayParsingStatus(){
for(var i= 0; i<newModel.files.length; ++i){
switch(newModel.files[i].status){
case 0:
filesList.get(i).status="<font color=\"green\">Success</font>";
break;
case 1:
var status ="";
status += "<ul>";
for(var j=0; j < newModel.files[i].warnings.length;j++){
status += "<li><font color=\"orange\">Warning: " + newModel.files[i].warnings[j] + "</font></li>";
}
status += "</ul>";
filesList.get(i).status=status;
break;
case 2:
filesList.get(i).status="<font color=\"red\"><b>Error: " + newModel.files[i].error + "</b></font>";
break;
case 3:
filesList.get(i).status="Ignored";
break;
}
}
}
You're already setting the height on the QML delegate element:
height: parent.height
It's binding the height to the parent height.
If you set the height with an expression, it'll be triggered (and re-evaluated) every time there's a change on any of the elements of the expression.
That's why the QML properties have a NOTIFY signal.
So if you want to bind the height to some other element you just need to assign it to the height attribute.
I haven't tried but the childrenRect may be what you're looking for: http://doc.qt.io/qt-5/qml-qtquick-item.html#childrenRect.height-prop
You can also use ternary operator to assign values to the property, i.e:
height: (model.get(styleData.row)[styleData.role] === 0)?30:100