QML: TableView dynamic change row height - qt

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. :)

Related

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

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

Qml, update gridview inside listview item

I have a listview in which, each item has gridview inside it:
ListView{
id:list_roi
anchors.fill: parent
delegate: roi_item_view
model: roi_item_model
spacing: 5
clip: true
focus: true
ScrollBar.vertical: ScrollBar {}
onCurrentIndexChanged: {
valueUpdater.selected_roi_changed(list_roi.currentIndex)
}
highlight: Rectangle {
color: 'lightgray'
}
}
ListModel{
id:roi_item_model
ListElement {roi_rows: 2, roi_cols: 2, roi_subregion_model : [{_text:"555.3"},{_text:"555.3"},{_text:"555.3"}]}
}
Component{
id:roi_item_view
MouseArea {
anchors.fill: parent
onClicked: list_roi.currentIndex = index
}
GridLayout{
id:layout_wrapper
anchors.fill: parent
rows: 3
columns: 5
GridView {
id:grid_sub_region
Layout.row: 2
Layout.column: 0
Layout.columnSpan:5
Layout.alignment: Qt.AlignHCenter
width: roi_cols * 30; height: roi_rows * 30
cellWidth: 30; cellHeight: 30
visible : true
model: roi_subregion_model
delegate: contactsDelegate
focus: true
}
Component {
id: contactsDelegate
CellBox{
id: wrapper
width: 30
height: 30
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
id: contactInfo
text: _text
}
}
}
}
}
as you can see, inside model i have 3 property (roi_rows, roi_cols, roi_subregion_model) which are supposed to change the view of Gridview's rows, columns and data inside each cell, when i change them.
but when i change these values, number of cells inside Gridview does not changes. when i initialize roi_rows and toi_cols to 2 i will have 2x2 gridview after listview item is created. but after initialization i cannot change it. it seems i have to do something to refresh the UI of specific item inside Listview so that the Gridview inside that item will be redrawn.
Update
based on comments: width and height of the GridView will be set based on roi_rows, roi_cols which are part of Listview Model. and because the CellWidth and CellHeight are constant then number of cells (rows and columns of the Gridview) will be changed.
OK i found the answer. in Model,View,Delegate structure when we create a Delegate and want to bind it with a key inside Model, in some cases like 'text' for 'Label' it will be automatically bound automatically. in this example:
ListModel{
id:roi_item_model
ListElement {label_text:"Hello" ,roi_rows: 2, roi_cols: 2, roi_subregion_model : [{_text:"555.3"},{_text:"555.3"},{_text:"555.3"}]}
}
Component{
id:roi_item_view
MouseArea {
anchors.fill: parent
onClicked: list_roi.currentIndex = index
}
GridLayout{
id:layout_wrapper
anchors.fill: parent
rows: 3
columns: 5
Label{ text:label_text }
GridView {
id:grid_sub_region
Layout.row: 2
Layout.column: 0
Layout.columnSpan:5
Layout.alignment: Qt.AlignHCenter
width: roi_cols * 30; height: roi_rows * 30
cellWidth: 30; cellHeight: 30
visible : true
model: roi_subregion_model
delegate: contactsDelegate
focus: true
}
Component {
id: contactsDelegate
CellBox{
id: wrapper
width: 30
height: 30
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
id: contactInfo
text: _text
}
}
}
}
}
label_text inside Model will be bound to text property of Label automatically which means if i change label_text the content of Label will be changed automatically. but for some property we should do this binding manually. in out example add this to delegate of ListView:
Binding {
target: grid_sub_region
property: 'height'
value: roi_rows * 30
}
Binding {
target: grid_sub_region
property: 'width'
value: roi_cols * 30
}

How to have a numerical model start at 1 in a combobox from QML

In the combobox from qml we can give them a model with a numerical value.
The options of the combobox then start from 0 to the model value minus 1.
What i want is to show the values on the combobox plus 1.
But still, if i try to access, with qml javascript, the value that is selected in that combobox is the original value, what i mean is, if it's the first option it's the value 0. The numbers shall only start from 1 in the display part of combobox.
I'm going to give an example (with a model=32):
display of the combobox
the options:
what i want is them to start at one, like this:
The code used was the following:
import QtQuick 2.9
import QtQuick.Controls 2.0
ApplicationWindow {
id: window
title: "Stack"
visible: true
height: 200
width: 400
Item {
id: page
anchors.fill: parent
width:parent.width
height: parent.height
Column{
width:parent.width
spacing:10
ComboBox {
id:comboBox
model: 32
objectName: "test"
implicitHeight: 30
displayText: currentText
// delegate:
// Button {
// id:buttonCombo
// width: parent.width
// text: index+1
// height:40
// contentItem: Text {
// text: buttonCombo.text
// font: comboBoxCustom.font
// leftPadding: 5
// horizontalAlignment: Text.AlignLeft
// verticalAlignment: Text.AlignVCenter
// elide: Text.ElideRight
// }
// background: Rectangle {
// color: buttonCombo.hovered ? (buttonCombo.pressed ? "#d8d8d8" : "#e8e8e8" ) : "#fff"
// }
// }
anchors.topMargin: 10
}
}
}
}
Maybe this could be done:
displayText: Number(currentText)+1
but i don't know if its the best solution...

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

How to limit the size of drop-down of a ComboBox in QML

I am using a ComboBox in QML and when populated with a lot of data it exceeds my main windows bottom boarder. From googling I have learned that the drop-down list of a ComboBox is put on top of the current application window and therefore it does not respect its boundaries.
Ideally I would want the ComboBox to never exceed the main applications boundary, but I can not find any property in the documentation.
A different approach would be to limit the number of visible items of the drop-down list so that it do not exceed the window limits for a given window geometry. I was not able to find this in the documentation either and I have run out of ideas.
Take a look to the ComboBox source code, the popup is of a Menu type and it doesn't have any property to limit its size. Moreover, the z property of the Menu is infinite, i.e. it's always on top.
If you Find no way but to use the ComboBox of Qt you can create two models one for visual purpose, I will call it visual model, you will show it in your ComboBox and the complete one , it will be the reference model. Items count in your VisualModel wil be equal to some int property maximumComboBoxItemsCount that you declare . you'll need o find a way that onHovered find the index under the mouse in the visualmodel if it's === to maximumComboBoxIemsCount you do visualModel.remove(0) et visualModel.add(referenceModel.get(maximum.. + 1) and you'll need another property minimumComboBoxIemsCount, same logic but for Scroll Up , I dont know if it will work. but it's an idea
I think there is no solution using the built-in component and you should create your own comboBox. You can start from the following code.
ComboBox.qml
import QtQuick 2.0
Item {
id: comboBox
property string initialText
property int maxHeight
property int selectedItem:0
property variant listModel
signal expanded
signal closed
// signal sgnSelectedChoice(var choice)
width: 100
height: 40
ComboBoxButton {
id: comboBoxButton
width: comboBox.width
height: 40
borderColor: "#fff"
radius: 10
margin: 5
borderWidth: 2
text: initialText
textSize: 12
onClicked: {
if (listView.height == 0)
{
listView.height = Math.min(maxHeight, listModel.count*comboBoxButton.height)
comboBox.expanded()
source = "qrc:/Images/iconUp.png"
}
else
{
listView.height = 0
comboBox.closed()
source = "qrc:/Images/iconDown.png"
}
}
}
Component {
id: comboBoxDelegate
Rectangle {
id: delegateRectangle
width: comboBoxButton.width
height: comboBoxButton.height
color: "#00000000"
radius: comboBoxButton.radius
border.width: comboBoxButton.borderWidth
border.color: comboBoxButton.borderColor
Text {
color: index == listView.currentIndex ? "#ffff00" : "#ffffff"
anchors.centerIn: parent
anchors.margins: 3
font.pixelSize: 12
text: value
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: {
listView.height = 0
listView.currentIndex = index
comboBox.selectedItem = index
tools.writePersistence(index,5)
comboBoxButton.text = value
comboBox.closed()
}
}
}
}
ListView {
id: listView
anchors.top: comboBoxButton.bottom
anchors.left: comboBoxButton.left
width: parent.width
height: 0
clip: true
model: listModel
delegate: comboBoxDelegate
currentIndex: selectedItem
}
onClosed: comboBoxButton.source = "qrc:/Images/iconDown.png"
Component.onCompleted: {
var cacheChoice = tools.getPersistence(5);
listView.currentIndex = tools.toInt(cacheChoice)
selectedItem = listView.currentIndex
comboBoxButton.text = cacheModel.get(selectedItem).value
}
}
ComboBoxButton.qml
import QtQuick 2.0
Item {
id: container
signal clicked
property string text
property alias source : iconDownUp.source
property string color: "#ffffff"
property int textSize: 12
property string borderColor: "#00000000"
property int borderWidth: 0
property int radius: 0
property int margin: 0
Rectangle {
id: buttonRectangle
anchors.fill: parent
color: "#00000000"
radius: container.radius
border.width: container.borderWidth
border.color: container.borderColor
Image {
id: image
anchors.fill: parent
source: "qrc:/Images/buttonBackground.png"
Image {
id: iconDownUp
source: "qrc:/Images/iconDown.png"
sourceSize.height:20
sourceSize.width: 20
anchors.verticalCenter: parent.verticalCenter
}
}
Text {
id:label
color: container.color
anchors.centerIn: parent
font.pixelSize: 10
text: container.text
font.bold: true
}
MouseArea {
id: mouseArea;
anchors.fill: parent
onClicked: {
container.clicked()
buttonRectangle.state = "pressed"
startTimer.start()
}
}
Timer{
id:startTimer
interval: 200
running: false;
repeat: false
onTriggered: buttonRectangle.state = ""
}
states: State {
name: "pressed"
when: mouseArea.pressed
PropertyChanges { target: image; scale: 0.7 }
PropertyChanges { target: label; scale: 0.7 }
}
transitions: Transition {
NumberAnimation { properties: "scale"; duration: 200; easing.type: Easing.InOutQuad }
}
}
}
I've used it in some software of mine, hence it is possible that It could not work "out of the box". I use it like this:
ComboBox{
id:cacheChoice
initialText: "None"
anchors.top: baseContainer.top
anchors.topMargin: 2
anchors.right: baseContainer.right
maxHeight: 500
listModel: cacheModel
onExpanded: {
cacheChoice.height = 500
}
onClosed: {
cacheChoice.height = 20
}
}
In case you are working with ComboBox from Qt Quick Controls 2, here's the source code for it:
https://github.com/qt/qtquickcontrols2/blob/5.12/src/imports/controls/ComboBox.qml
Based on that, this override of the behavior works to limit the height to something reasonable:
myComboBox.popup.contentItem.implicitHeight = Qt.binding(function () {
return Math.min(250, myComboBox.popup.contentItem.contentHeight);
});
It is possible to access the hidden MenuStyle within the ComboBoxStyle component. There you can use all the things and hidden things you have within a MenuStyle, including its maximum height.
The thing looks roughly like this.
Not pretty but it works well enough.
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.3
import QtQuick.Window 2.2
ComboBox {
id: comboBox
style: ComboBoxStyle {
// drop-down customization here
property Component __dropDownStyle: MenuStyle {
__maxPopupHeight: 400
__menuItemType: "comboboxitem" //not 100% sure if this is needed
}
}
As it came up resonantly in our team, here is a updated version of the idea shown above. The new version restricts the size automatically to the size of your application.
ComboBox {
id: root
style: ComboBoxStyle {
id: comboBoxStyle
// drop-down customization here
property Component __dropDownStyle: MenuStyle {
__maxPopupHeight: Math.max(55, //min value to keep it to a functional size even if it would not look nice
Math.min(400,
//limit the max size so the menu is inside the application bounds
comboBoxStyle.control.Window.height
- mapFromItem(comboBoxStyle.control, 0,0).y
- comboBoxStyle.control.height))
__menuItemType: "comboboxitem" //not 100% sure if this is needed
} //Component __dropDownStyle: MenuStyle
} //style: ComboBoxStyle
} //ComboBox

Resources