QML Dynamically Update ListModel - qt

I'm confused at some point in QML as I'm new to it. The simplified QML codes define a window that includes one ListView which includes my delegate Items. One button loads and unloads this ListView Element. When I load and unload the ListView Element, all text that I wrote inside the TextInput is reset from listModel as I expected. So I need to dynamically update listModel thus I will not lose the text that I wrote in TextInput. I added a Keys.onPressed in TextInput to achieve that. But it works with some logical error. When I type, let's say "aaaa", then I unload the ListView and load again via Button, what I get is "aaa" (the last letter is not passed to listModel). This is understandable but how can I update the list model roles dynamically in this example?
main.qml
ApplicationWindow {
id: applicationWindow
width: 300
height: 200
visible: true
title: qsTr("01_Change_Model_Data")
ListModel {
id: listModel1
ListElement {labelText: "Text Field 1:"; textInput_text : "This is text 1"}
ListElement {labelText: "Text Field 2:"; textInput_text : "This is text 2"}
}
Button {
id: loadUnloadBtn
height: 24
width: 50
text: "Load"
anchors {
right: parent.right
rightMargin: 20
top: parent.top
topMargin: 10
}
onClicked: {
if(listAreaLoader.source == "") {
listAreaLoader.source = "ListArea.qml"
}else {
listAreaLoader.source = ""
}
}
}
Loader {
id: listAreaLoader
anchors {
top: parent.top
topMargin: 10
bottom: parent.bottom
bottomMargin: 10
left: parent.left
leftMargin: 10
right: parent.right
rightMargin: 80
}
source: ""
}
}
ListArea.qml:
Item {
id: listViewDelegate
ListView {
id: listView1
anchors.fill: parent
model: listModel1
delegate: listElementDelegate
spacing: 6
}
Component {
id: listElementDelegate
Rectangle {
color: "#00000000"
height: 24
width: 50
Label {
id: label1
text: labelText
}
Rectangle {
color: "grey"
radius: 4
width: 100
height: 20
anchors {
verticalCenter: label1.verticalCenter
left: label1.right
leftMargin: 10
}
TextInput {
id: textInput1
anchors.fill: parent
leftPadding: 5
rightPadding: 5
clip: true
verticalAlignment: Text.AlignVCenter
text: textInput_text
Keys.onPressed: {
listView1.currentIndex = index
listModel1.setProperty(index, "textInput_text", textInput1.text)
}
}
}
}
}
}

Related

Change height of ListView based on visible property

I have a ListView where the items become visible or not based on whether a header was expanded or not. When the visible property is set to false, there is just blank space in that area. How do I remove the black space in the list so that the next visible item comes in view?
Currently I have set the height:model.count * 50 with 50 being height of each item. I tried subtracting 50 every time an item's visible property is set to false but this just reduces the height of the list overall but the blank space remains.
ListView { id: list
focus: true
anchors.top: header.bottom
anchors.left: parent.left
anchors.right: parent.right
width: parent.width
height:model.count*50
model:hall
Component {
id:comp
Item{
width: parent ? parent.width : 0
height: parent ? parent.height: 0
Column{
id:col1
width: parent.width - 16
height: 25
anchors.centerIn: parent
spacing: 3
Text { id: name
width: parent.width
text: name
elide: Text.ElideMiddle
font: "black"
color: "grey"
}
Row{
width: parent.width
Text { id: file1
width: parent.width * 0.6 - 5
text: "hello"
horizontalAlignment: Text.AlignLeft
elide: Text.ElideMiddle
font: systemFont.TableCellFont
}
Text { id: file2
width: parent.width*0.4 - 3
horizontalAlignment: Text.AlignRight
text: mod
font: systemFont.TableCellFont
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
console.warn("Mouse onClicked")
}
}
}
}
delegate: Item {
width: parent.width;
height: {
if (!cric && expanded)
return 50
else
return 0
}
Loader {
anchors.fill: parent
sourceComponent: comp
height: expanded? 50: 0
readonly property int index:model.index
readonly property string mod: model.mod
visible: !cric && expanded
}
}
Keys.onReturnPressed: {
console.warn("Return pressed")
}
}
The blank space is created because the List item which is hidden by setting visible to false still has its dimensions set. You can update the item height to 0 and the blank space will not be visible. Try this example for reference.
import QtQuick 1.1
Rectangle{
width: 400; height: 400
color: 'lightblue'
ListView{
width: 400; height: count * 30
model: 20
delegate: Rectangle{
id: rectDel
width: 200; height: 30
color: 'lightgreen'
border{
width: 1
color: 'black'
}
Text{
text: index
anchors.centerIn: parent
}
Rectangle{
width: 20; height: 20
radius: 10
color: 'red'
anchors{
verticalCenter: parent.verticalCenter
right: parent.right; rightMargin: 10
}
Text{ text: 'X'; anchors.centerIn: parent; }
MouseArea{
anchors.fill: parent
onClicked: {
rectDel.visible = false
rectDel.height = 0
}
}
}
}
}
}

Qt Quick - What is the correct way to create a GridView with variable row height?

I'm Developing an application with Qt 5.13.0 on a Windows 10 professional computer. I need to create a Whatsapp like message view, and to achieve that, I used a GridView component as a base. However, in order to draw the messages correctly, I need to create each GridView row with a different height, depending on the message text.
But to my surprise, I could not find any solution on the internet to achieve that, although I thought it was a simple formality. I tried a solution by myself, which I attached below, however it doesn't work. The issue is that all the rows take the height of the last resized one.
I have no doubt that Qt can do this, unfortunately, I have been looking for days now, and I can not find a solution to this issue. I simply have no idea about how to achieve that. So someone can explain to me how to create a GridView with variable height rows, or if the GridWiew isn't the appropriate component to do that, whcih I should use instead?
Here is my qss file:
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Window 2.12
import QtQuick.Layouts 1.11
Window
{
visible: true
width: 640
height: 480
title: qsTr("Grid view")
color: "#ffffff"
ColumnLayout
{
transformOrigin: Item.Center
spacing: 0
x: 0
y: 0
width: parent.width
height: parent.height
/**
* Grid view item
*/
Component
{
id: itGridItem
Item
{
Column
{
Rectangle
{
property int messageWidth: (gvMessageGrid.cellWidth / 2) - 50
id: itemRect
x: senderIsMyself ? 25 : gvMessageGrid.cellWidth - (25 + messageWidth)
y: 5
width: messageWidth
height: itemTextID.height + 20
color: senderIsMyself ? "#d5d5d5" : "#800b940e"
radius: 5
clip: true
Text
{
id: itemTextID
width: parent.width - 20
text: itemText
renderType: Text.NativeRendering
textFormat: TextEdit.RichText
wrapMode: Text.WordWrap
font.family: "Segoe UI Emoji"
font.pixelSize: 18
anchors.margins: 10
anchors.left: parent.left
anchors.top: parent.top
color: "#101010"
}
onHeightChanged: gvMessageGrid.cellHeight = height + 10
}
}
}
}
/**
* Messages grid view
*/
GridView
{
id: gvMessageGrid
y: 0
Layout.fillHeight: true
flickableDirection: Flickable.VerticalFlick
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
clip: true
contentWidth: 700
contentHeight: 300
cellWidth: contentWidth
cellHeight: 150
model: lmGridModel
delegate: itGridItem
ScrollBar.vertical: ScrollBar
{
visible: true
}
onWidthChanged: cellWidth = width
}
}
}
--- Edited on October 18, 2019
Following the eyllanesc suggestion below, here are 2 screenshots of what I want and what I get:
what I want
what I get
NOTE I'm using several languages, for test purposes, because my application must have international support. However the issue I face has nothing to do with that, i.e it's not an error with text calculation, because 1. The green rect sizes around the text are always correct, and 2. I face the exact same issue with pure English text.
I suggest you to use ListView instead of GridView. You can easily view anything inside full-width list item. Let me share with you my ListDelegate class from an old project. Pay attention to usage of isIncoming property in anchor binding, for example:
anchors {
left: isIncoming? undefined : parent.left
right: isIncoming? parent.right : undefined
}
Full listing:
import QtQuick 2.5
import "units.js" as Units
Rectangle {
id: chatMsgDelegRoot
property bool isIncoming: !model.out
property bool isSelected: model.isSelected
signal clicked(int index)
width: parent.width
height: dlgColumn.height + Units.gu(2.5)
color: "#edf1f5"
Column {
id: dlgColumn
spacing: Units.gu(4)
width: parent.width
anchors.verticalCenter: parent.verticalCenter
BorderImage {
id: borderImage
source: isIncoming?
(isSelected ? "/images/img/MsgOut_Selected_2.png" : "/images/img/MsgOut_2.png") :
(isSelected ? "/images/img/MsgIn_Selected_2.png" : "/images/img/MsgIn_2.png")
// Texture-dependent.
border {
left: isIncoming? 20 : 30
top: 20
right: isIncoming? 30 : 20
bottom: 35
}
anchors {
left: isIncoming? undefined : parent.left
right: isIncoming? parent.right : undefined
}
width: Math.max(content.width + Units.gu(15), Units.gu(21))
height: content.height + Units.gu(9)
MouseArea {
id: msgDelegateMa
anchors.fill: parent
onClicked: chatMsgDelegRoot.clicked(model.index)
}
Loader {
id: content
sourceComponent: model.type === "Text" ? textComponent : controlComponent
anchors {
left: isIncoming? undefined : parent.left
right: isIncoming? parent.right : undefined
leftMargin: Units.gu(10)
rightMargin: Units.gu(10)
top: parent.top
topMargin: Units.gu(4)
}
}
Text {
text: model.date.toTimeString()
font.pointSize: 8
font.italic: true;
color: "lightgrey"
anchors {
left: isIncoming? undefined : parent.left
right: isIncoming? parent.right : undefined
rightMargin: Units.gu(7.5)
leftMargin: Units.gu(7.5)
bottom: parent.bottom
bottomMargin: Units.gu(1)
}
}
} // BorderImage
} // Column
// TODO To separate files.
Component {
id: textComponent
Rectangle {
id: textComponentRoot
color: "#00000000"
width: msgText.paintedWidth
height: msgText.height
Text {
id: msgText
font.pointSize: 10
textFormat: Text.RichText
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
width: chatMsgDelegRoot.width * 0.7
text: model.body
color: isSelected? "white" : "black"
}
}
} // Component
Component {
id: controlComponent
Rectangle {
id: textComponentRoot
color: "#00000000"
width: innerColumn.width
height: innerColumn.height
Column {
id: innerColumn
spacing: Units.gu(1)
Text {
id: fileNameText
font.pointSize: 10
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
width: chatMsgDelegRoot.width * 0.7
elide: Text.ElideRight
text: "File transfer: " + model.body
color: isSelected? "white" : "black"
}
Row {
id: innerRow
anchors.right: parent.right
spacing: Units.gu(1)
SimpleButton {
id: allowBtn
width: Units.gu(15)
height: Units.gu(8)
text: "Allow"
}
SimpleButton {
id: denyBtn
width: Units.gu(15)
height: Units.gu(8)
text: "Deny"
}
}
} // Column
}
} // Component
}

Drag and Drop in ListView

I am trying to drag and drop a ListView row and I am having trouble at the drag stage. Below code doesn't drag anything and name column is not aligned properly. I don't want to specify a size for ListView and I want it to get its size from its content Row with id row. It should be able to because text has size coming from its font size and Rectangle has set width. ListView can get wider as needed to show the name part.
import QtQuick 2.11
import QtQuick.Layouts 1.11
ListView {
id: root
height: scrollview.viewport.height
model: listModel
delegate: dragDelegate
Component {
id: dragDelegate
MouseArea {
id: dragArea
anchors { left: parent.left; right: parent.right }
height: content.height
drag.target: pressed ? content : undefined
drag.axis: Drag.XAndYAxis
Rectangle {
id: content
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
height: row.implicitHeight
border.width: 1
border.color: "lightsteelblue"
color: "green"
radius: 2
states: State {
when: dragArea.pressed
ParentChange { target: content; parent: root }
AnchorChanges {
target: content
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
Row {
id: row
Text {
text: name
font.pointSize: 15
}
Rectangle {
height: parent.height
width: 40
color: hexColor
}
}
}
}
}
ListModel {
id: listModel
ListElement {
name: "first-name"
hexColor: "red"
}
ListElement {
name: "second-name"
hexColor: "green"
}
}
}

Long TabBar - adding a size and position indicator to reflect the presence of off-screen tabs

I have a tab bar with a stacklayout like the following:
Rectangle {
id: rect
height: 190
anchors.right: parent.right
anchors.left: parent.left
color: "transparent"
anchors.top: uniqueHandleText.bottom
anchors.topMargin: 100
TabBar {
id: frame
anchors.right: parent.right
anchors.left: parent.left
background: Rectangle {
color: "#737373"
}
x: -hbar.position * width
Repeater {
model: wizard.categories
TabButton {
id: tabData
property bool selected: false
text: modelData.name
width: 200
font.pixelSize: 18
contentItem: Text {
text: tabData.text
font: tabData.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
wrapMode: Text.WordWrap
color: "#FFFFFF"
}
background: Rectangle {
implicitWidth: frame.width
implicitHeight: 180
opacity: enabled ? 1 : 0.3
color: tabData.checked ? "#BD9CBE": "#737373"
}
}
}
}
ScrollBar {
id: hbar
hoverEnabled: true
active: hovered || pressed
orientation: Qt.Horizontal
size: rect.width / frame.width
anchors.left: parent.left
anchors.right: parent.right
anchors.top: frame.bottom
}
Text {
font.pixelSize: 18
text: "Next"
anchors.right: parent.right
visible: frame.x != frame.width ? true: false
}
StackLayout {
id: stack1
anchors.left: parent.left
anchors.right: parent.right
anchors.top: frame.bottom
currentIndex: frame.currentIndex
Repeater {
model: wizard.categories
Item {
id: homeTab
TabBar {
id: homeTabTab
anchors.right: parent.right
anchors.left: parent.left
anchors.top: parent.top
height: 180
background: Rectangle {
color: "#958096"
}
Repeater {
model: modelData.sub_categories
TabButton {
property bool selected: false
id: currentTab
text: modelData.name
width: 200
font.pixelSize: 18
background: Rectangle {
implicitWidth: frame.width
implicitHeight: 180
opacity: enabled ? 1 : 0.3
color: currentTab.checked ? "#958096": "#8D758E"
}
contentItem: Text {
text: currentTab.text
font: currentTab.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
wrapMode: Text.WordWrap
color: "#FFFFFF"
MouseArea {
anchors.fill: parent
onClicked: {
if(currentTab.checked){
currentTab.checked = false
} else {
currentTab.checked = true
}
}
onDoubleClicked: {
currentTab.selected = true
var found = false;
var someText = frame.itemAt(stack1.currentIndex).text;
print(someText)
for(var i = 0; i<wizard.selectedSkills.count; i++){
if(wizard.selectedSkills.get(i).name === someText){
wizard.selectedSkills.get(i).sub_categories.append({"name":currentTab.text});
wizard.skills.push({"name": someText})
found = true;
}
}
if(!found){
print(currentTab.text)
wizard.selectedSkills.append({"name":someText, "sub_categories":[{"name":currentTab.text}]})
}
print(window.selectedSkills)
}
}
}
}
}
}
}
}
}
}
I've tried many different things to add a scrollbar or to figure out how to use the flickable functionality that TabBar has. However, the documentation doesn't specify how it works, it just does. Therefore, they are not accessible (or even rewritteable, to use those properties). I want to add a small indicator like an arrow to specify that there is more elements for ease of navigation on desktop on top of the TabBar functionality.
It doesn't seem like the necessary properties are exposed in order to make this happen the easy way.
However, since this is QML, it means the whole object tree is gaping wide open to introspection, allowing us to establish that the item that does the flicking is the contentItem of a ListView inside the Container the ToolBar inherits. The view happens to be the second visible child, although this is technically "private implementation" that one should not rely on. So it is better to take some extra care to establish whether or not you have the correct object.
ApplicationWindow {
id: main
width: 640
height: 480
visible: true
TabBar {
id: toolbar
width: parent.width
height: 50
Repeater {
model: 10
TabButton {
text: "test " + index
width: 100
}
}
}
Rectangle {
height: 5
width: main.width * (view ? view.visibleArea.widthRatio : toolbar.width / toolbar.contentWidth)
color: "red"
anchors.top: toolbar.bottom
x: view ? (main.width - width) * (view.contentX / (view.contentWidth - view.width)) : 0
}
property ListView view: {
var l = toolbar.visibleChildren.length
while (--l) if ("cacheBuffer" in toolbar.visibleChildren[l]) return toolbar.visibleChildren[l]
return null
}
}
And there you have it. We iterate the tabview children until we find one that has a property cacheBuffer which is fairly unique to ListView, and once we have that, we can access the needed properties. As you see, for the indicator width we can do even without the list view, as the toolbar exposes a contentWidth property, but for the indicator position there is no workaround.
And it works:

GridLayout: width and height issues

I have a few questions about GridLayout in QtQuickLayouts 1.3:
How should I make it , so the GridLayout places into CORRECT row/column every element of my grid even if the size of the element is not clearly specified by with or height parameter?
Let me remove the width and height and I will show you the broken layout. I will just comment it out:
Item {
id: test_item
//Layout.fillWidth: true
//height: 20
}
Full source code of the .qml file is here, and can be tested with bin/qmlscene:
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
ApplicationWindow {
width: 400
height: 500;
ColumnLayout {
anchors.fill: parent
GridLayout {
anchors.fill: parent
columns: 2
Label {
text:"Email:"
}
Rectangle {
width: 80; height: 20
border.color: "magenta"
}
Label {
text: "Full Name:"
Layout.fillWidth: true
}
Item {
id: test_item // the commented out portion, to show the flaw
//Layout.fillWidth: true
//height: 20
}
Label {
text: "Gender:"
}
RowLayout {
Layout.minimumHeight: 20
Layout.fillWidth: true
RadioButton {
text: "Male"
width: parent.width/2
height: 20
}
RadioButton {
text: "Female"
width: parent.width/2
height: 20
}
}
Label {
text: "Mobile phone:"
}
Rectangle {
width: 80; height: 20
border.color: "magenta"
}
}
Row {
Button {
text: "Cancel"
}
Button {
text: " Ok "
}
}
}
}
The 'broken` output looks like this:
While the correct output , with Item's size specified looks like this:
2) The second question. How do I load a component into a GridLayout cell via Loader using Component type? For example, this is my full source with the Component item and the component is missing when rendering:
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
ApplicationWindow {
width: 400
height: 500;
ColumnLayout {
anchors.fill: parent
GridLayout {
anchors.fill: parent
columns: 2
Label {
text:"Email:"
}
Rectangle {
width: 80; height: 20
border.color: "magenta"
}
Label {
text: "Full Name:"
Layout.fillWidth: true
}
Loader {
id: test_item
sourceComponent: field_template
}
Label {
text: "Gender:"
}
RowLayout {
Layout.minimumHeight: 20
Layout.fillWidth: true
RadioButton {
text: "Male"
width: parent.width/2
height: 20
}
RadioButton {
text: "Female"
width: parent.width/2
height: 20
}
}
Label {
text: "Mobile phone:"
}
Rectangle {
width: 80; height: 20
border.color: "magenta"
}
}
Row {
Button {
text: "Cancel"
}
Button {
text: " Ok "
}
}
}
Component {
id: field_template
Item {
Layout.fillWidth: true
height: 33
Rectangle {
border.color: "blue"
color: "transparent"
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.rightMargin: 10
TextEdit {
anchors.fill: parent
anchors.leftMargin: 5
anchors.topMargin: 3
anchors.rightMargin: 2
clip: true
text: "type here"
}
}
}
}
}
The picture of the output rendered by qmlscene is this:
I have correctly speecified witdht and height, why isn't the component being loaded with Loader ?
For the second question folibis is right in the comment. You just have to check the size. Here, is a working code.
...
Item {
Layout.fillWidth: true
//width: 80
height:20
Loader {
id: test_item
anchors.fill: parent
sourceComponent: field_template
}
}
...
Component {
id: field_template
Item {
Rectangle {
border.color: "blue"
color: "transparent"
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.rightMargin: 10
TextEdit {
anchors.fill: parent
anchors.leftMargin: 5
anchors.topMargin: 3
anchors.rightMargin: 2
clip: true
text: "type here"
}
}
}

Resources