Qt 5.11.3 - QML - treeView - resize row height depending of itemDelegate property - qt

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);
}
}
}
```

Related

Binding text from a TextField that belongs to a Repeater

My main.qml:
Window
{
visible: true
width: 640
height: 480
color: "grey"
GridLayout
{
anchors.fill: parent
columns : 2
rows : 2
Repeater
{
id: rectRepeater
model: 3
TextField
{
text: "hi"
}
}
}
Rectangle
{
id: r1
width: 100
height: 100
x: 200
y: 200
border.color: "red"
Text
{
id: t1
}
}
Component.onCompleted:
{
t1.text= rectRepeater.itemAt(0).text
}
}
The Text in the rectangle r1 displays the text at the start, but if I enter new text to the TextField, the rectangle will not be updated. How can I solve this?
A more elegant and maintainable solution is to implement a model that reflects the changes, and then make a binding of the first element with the text that shows Text:
Window{
visible: true
width: 640
height: 480
color: "grey"
ListModel{
id: mymodel
}
Component.onCompleted: {
for(var i=0; i<3; i++){
mymodel.append({"text" : "hi"})
}
}
GridLayout{
anchors.fill: parent
columns : 2
rows : 2
Repeater{
model: mymodel
TextField{
id: tf
onTextChanged: model.text = tf.text
Component.onCompleted: tf.text= model.text
}
}
}
Rectangle{
id: r1
width: 100
height: 100
x: 200
y: 200
border.color: "red"
Text {
id: t1
text: mymodel.count > 1 ? mymodel.get(0).text : ""
}
}
}
What you want, is to create a binding between the two.
Component.onCompleted:
{
t1.text = Qt.binding(function() { return rectRepeater.itemAt(0).text })
}
That being said, we would need to know exactly what you are trying to do, because creating bindings manually is an anti-pattern when not required. It is much better to bind directly, or to use signals.
Do you need the first elements, and the repeater, or is this just an test for you? What is your UI and what are you trying to achieve? This is some context worth giving for a proper answer.
One possible simpler solution
Repeater
{
id: rectRepeater
model: 3
TextField
{
text: "hi"
// See also `onEditingFinished` and `onValidated`
onTextChanged: {
if (index == 0)
t1.text = text
}
}
}
For more details about the property thing, look at my answers from your other question: Qml Repeater with ids

ListModel content not set properly

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

Drag and Drop in QML TreeView

In QML i have a TreeView with (properly working) multiselection:
TreeView {
id: treeview
anchors.fill: parent
model: myTestModel
selectionMode: SelectionMode.ExtendedSelection
selection: ItemSelectionModel {
model: treeview.model
}
TableViewColumn {
role: "name_role"
title: "Name"
width: 160
}
TableViewColumn {
role: "type_role"
title: "Type"
width: 75
}
}
I'd like to implement drag & drop to be able to "pull" items out of the treeview into a DropArea.
But when I use the approach I found several times, namely defining an itemDelegate that contains a MouseArea, the selection doesn't work anymore.
TreeView {
id: treeview
anchors.fill: parent
model: myTestModel
// broken due to MouseArea in itemDelegate !
selectionMode: SelectionMode.ExtendedSelection
selection: ItemSelectionModel {
model: treeview.model
}
TableViewColumn {
role: "name_role"
title: "Name"
width: 160
}
TableViewColumn {
role: "type_role"
title: "Type"
width: 75
}
itemDelegate: Item {
Rectangle {
id: rect
anchors.fill: parent
color: styleData.selected ? "blue" : "transparent"
Text {
anchors.verticalCenter: parent.verticalCenter
color: styleData.selected ? "white" : "black"
text: styleData.value
}
MouseArea {
anchors.fill: parent
drag.target: symbolAvatar
onPressed: {
var tmp = mapToItem(container, mouse.x, mouse.y);
symbolAvatar.x = tmp.x;
symbolAvatar.y = tmp.y;
symbolAvatar.dragging = true;
symbolAvatar.text = styleData.value;
}
}
}
}
}
where symbolAvatar is an item that becomes visible when a drag has started.
Any ideas how to implement drag and drop in a QML TreeView without breaking the selection?
Edit: Using the onPressAndHold event handler inside the TreeView would be a solution if I could access the mouse position there, but it doesn't seem to exist :-(

QML: TableView dynamic change row height

I have a table of 4 columns, in one of which is text. When you change the width of the column, the width of the internal text also changes. In some cases, the width of the text becomes rather small and the text begins to increase its height (text wrap). How can I increase the height of the row in which this text is located?
This is a column with text
TableViewColumn {
id: fullNameColumn
property int minColWidth: 175
property int maxColWidth: 500
role: "fullName"
title: "ФИО"
width: 360
property string imageSource : ""
onWidthChanged: {
if (width < minColWidth)
width = minColWidth
}
delegate: Rectangle{
id: rowCellContentFullName
color: "transparent"
Text {
property int maxColWidth: 400
id: labelFullName
objectName: "labelFullName"
font.pixelSize: 13
wrapMode: Text.Wrap
anchors.leftMargin: 38
anchors.left: parent.left
horizontalAlignment: styleData.textAlignment
anchors.verticalCenter: parent.verticalCenter
visible: parent.visible
width: parent.width - this.anchors.leftMargin * 2
text: styleData.value !== undefined ? styleData.value : ""
color: "#474747"
renderType: Text.NativeRendering
}
}
This is the rowDelegate
property Component rowDelegate: Rectangle {
id: rowDel
objectName: "RowDelegate"
height: parent.parent.children[1].children[2].children[0].children[0].height < 50 ? 50 : parent.parent.children[1].children[2].children[0].children[0].height
property color selectedColor: styleData.hasActiveFocus ? primaryColor : secondaryColor
property bool selectionMaskVisible
property bool selected: false
}
In rowDelegate Im binding height with terrible expression, but it works. Works only for column 3 (where FullName). If i drag this column in another space my code doesnt work.
Can I change rows height in TableView QML without this terrible expressions? Mb You know some way to solve this problem?
Rostislav.
I had the same problem.
My solution:
TableView {
id: skillsGroupSkills
model: root.updated ? skillsProxy : null
property var rects: ({})
TableViewColumn {
title: "ID"
role: "id"
width: 30
}
TableViewColumn {
id: skillColumn
title: "Название"
role: "skill"
width: 200
}
TableViewColumn {
title: "Sort"
role: "sort_id"
width: 60
}
itemDelegate: Item {
Text {
id: text
color: styleData.textColor
elide: styleData.elideMode
text: styleData.value
width: parent.width
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
onContentHeightChanged: {
if (styleData.column === 1 && styleData.row !== -1) {
var id = skillsGroupsSkillsTableModel.get(skillsProxy.mapRowToSource(styleData.row))["id"]
var rect = skillsGroupSkills.rects[id];
if (rect) rect.height = contentHeight;
}
}
}
}
rowDelegate: Rectangle {
id: rect
Component.onCompleted: {
var id = skillsGroupsSkillsTableModel.get(skillsProxy.mapRowToSource(styleData.row))["id"]
skillsGroupSkills.rects[id] = rect;
}
}
}
In this example I'm dynamically changing second column height in each row.
Every rowDelegate rectangle is saving in the rects property.
My model has unique value in every row - id. I used it to ident rectangles because I have proxyModel. Probably you can use styleData.row directly.
P.S. Yes, I know that this question was in a year ago. May be my solution will be useful for somebody. :)

Accessing Attributes of a ListView

I am writing a QML application that loads and displays images, I have a central frame that show the image the user is currently looking at as well as a sidebar that lets the user select images. What I am having trouble with is getting the information about what the currently selected item is in the sidebar (ListView).
Additionally, I am having trouble accessing the sourceChanged signal in the delegate for the ListView and therefore can't update the images in the list without the user scrolling down and then back up to force them to reload. Is there any easy way to access these attributes even though they are nested within a ListView?
Here is the code for my ListView. The issue is that I want to be able to access the Image from outside of the ListView in order to send a sourceChanged signal but I'm not sure how you would access a specific item in the list.
//The list of frames that have been loaded
ListView {
id: frameList
anchors {top: parent.top; bottom: parent.bottom; left: frameViewer.right; leftMargin: 5; topMargin: 5}
spacing: 5
width:300
height: parent.height
Component {
id: frameDelegate
Rectangle {
id: wrapper
anchors {horizontalCenter: parent.horizontalCenter}
height: 300
width: 300
//If there is an image in that space and if it's selected, highlight it
color: frames[number] === undefined ? "white" : wrapper.ListView.view.currentIndex === index ? "yellow" : "white"
Image {
id: image
height: 280
width: 280
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: "image://images/" + frames[number]
}
MouseArea {
height: 280
width: 280
anchors.fill: image
hoverEnabled: true
onClicked: frames[index] === undefined ? console.log(""): wrapper.ListView.view.currentIndex = index
}
}
}
ListModel {
id: frameModel
ListElement {
number: 0
}
ListElement {
number: 1
}
ListElement {
number: 2
}
ListElement {
number: 3
}
ListElement {
number: 4
}
ListElement {
number: 5
}
}
model: frameModel
delegate: frameDelegate
focus: true
}
Ok so I fixed the issue, although I may not have done it in the best way possible.
My solution is to create a signal newFrame in the window which would be sent every time a new frame is loaded. Then I use a Connections in the Image to update the source, every time that signal is sent.
signal newFrame()
//Stores all the fileURLs
function loadImages()
{
for(var i = 0; i < fileDialog.fileUrls.length; i++)
{
var exists = false
for(var j = 0; j < frames.length; j++)
{
if(frames[j] === fileDialog.fileUrls[i])
{
exists = true
}
}
if(exists == false)
{
if(frames == [])
{
frames = fileDialog.fileUrls[i]
}
else
{
frames.push(fileDialog.fileUrls[i])
}
frame.sourceChanged(frame.source)
newFrame()
}
}
}
Component
{
id: frameDelegate
Rectangle
{
id: wrapper
anchors {horizontalCenter: parent.horizontalCenter}
height: 300
width: 300
//If there is an image in that space and if it's selected, highlight it
color: frames[number] === undefined ? "white" : wrapper.ListView.view.currentIndex === index ? "yellow" : "white"
Image
{
id: image
height: 280
width: 280
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: "image://images/" + frames[number]
Connections {
target: window
onNewFrame: {
image.source = "image://images/" + frames[number]
}
}
}
MouseArea
{
height: 280
width: 280
anchors.fill: image
hoverEnabled: true
onClicked: {frames[index] === undefined ? console.log(""): wrapper.ListView.view.currentIndex = index; frame.sourceChanged(frame.source)}
}
}
}

Resources