Use the size of a model in QML - qt

I am looking for a way to access the number of elements that are to be found in a model in QML.
For example:
Item {
id: root
Row {
id: row
height: root.height
width: root.width
spacing: 5
Repeater {
id: rep
width: root.width
height: root.height
model: [5, 3, 3, 1, 12]
delegate: myDelegate
Text {
id: myDelegate
text: "Element " + index + " of " size + ":" + modelData
}
}
}
But I can't figure out how to retrieve the size of the model.
In the documentation I can find a property called count, but no hint how to access it.

It depends on the model you're using. In your case, the model is a plain old JavaScript array, so you'd use model.length. Every other Qt type related to models or views has a count property: ListModel, Repeater, ListView, etc. So, you could also use rep.count, in your case.

Generic way is to request repeater for it's underlying model count - in your case it would be rep.count.

Related

How is ComboBox component able to determine if the popup needs to be above of control?

I've seen that with the following code:
Window {
width: 440
height: 280
visible: true
ComboBox {
id: control
model: ["First", "Second", "Third"]
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
delegate: ItemDelegate {
width: control.width
contentItem: Text {
text: modelData
color: "#21be2b"
font: control.font
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
highlighted: control.highlightedIndex === index
}
indicator: Canvas {
id: canvas
x: control.width - width - control.rightPadding
y: control.topPadding + (control.availableHeight - height) / 2
width: 12
height: 8
contextType: "2d"
Connections {
target: control
function onPressedChanged() { canvas.requestPaint(); }
}
onPaint: {
context.reset();
context.moveTo(0, 0);
context.lineTo(width, 0);
context.lineTo(width / 2, height);
context.closePath();
context.fillStyle = control.pressed ? "#17a81a" : "#21be2b";
context.fill();
}
}
contentItem: Text {
leftPadding: 0
rightPadding: control.indicator.width + control.spacing
text: control.displayText
font: control.font
color: control.pressed ? "#17a81a" : "#21be2b"
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
border.color: control.pressed ? "#17a81a" : "#21be2b"
border.width: control.visualFocus ? 2 : 1
radius: 2
}
popup: Popup {
y: control.height - 1
width: control.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: control.popup.visible ? control.delegateModel : null
currentIndex: control.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator { }
}
background: Rectangle {
border.color: "#21be2b"
radius: 2
}
}
}
}
(The ComboBox example from Qt documentation, at the bottom of the window)
If you click on the ComboBox, the popup its shown above the control (because its out of space below). I would like to know which signal or variable makes this automatic behaviour, so that i can capture it and trigger a different action.
I'm not sure I fully understand the question, but hopefully this answers it.
The code within popup uses a ternary to navigate the popup visibility. See this post regarding QML conditional bindings (ternary operators)
model: control.popup.visible ? control.delegateModel : null
"If popup visible, set model equal to delegate model. Else set popup model null"
Lets talk about signals and slots. If you want to easily view all of the signal/slots on a qml object type go within the block and type 'on'. Then view all of the code fillins from there. You can check the QT documentation as well.
If I were to implement this, I may have done it differently using the popup signals: open(), close(). It will add more lines of code, but improve readability and utilize the signal/slot mechanism. The current method creates very tight coupling between QML components.
Hey, thanks for your answer! Basically what I need to do is work with
popup y-coordinate. More specifically evaluate a condition to assign
the y property of popup, depending on how much space is left to open
it below the control... like this: popup.y = some_condition?
control.height - 1 : popup.implicitHeight + 1 QML already has some way
to know if the space is enough... and then readjust the popup
y-coordinate. I would like to know which inner-mechanism handles this.
Three ways to tackle it come to mind:
Use Layouts
Use Component attributes/member data
Use anchors
Layouts
Wrap all of your components inside of a column layout. Have your column layout fill up the space of both components combined. Then you can set minimum, preferred, and maximum width/heights of each component. In addition, you could set the preferred size for one component. Then call Layout.fill width/column to have it automatically take up the rest of the space.
Component attributes/member data
Mathmatically calculate the .y data using all of your other components.
popup.y = appWindow.y - componentWindow.y
or
popup.y = doMath(some property var of component X)
Anchors
Anchor your popup component to another component. So suppose you wanted a popup underneath some rectangle component.
Anchors.top = myRect.bottom
I'm a huge fan of using nested layouts to create dnyamic screens that always fill up spaces in the way I expect them to. It prevents tightly coupled components and lets Qt do the hard work.

How to programmatically append text to a QML TextArea?

I am trying to pass log data to my QML front end, one line at a time, and have it append to the end of a TextArea. I've considered several approaches. The following is the most promising. I have created a QAbstractListModel (in Python) and pass this model into a repeater where it arrives as a single item (rowCount =1) which I append to the TextArea using the line
text: terminal_text.text + display
This works but I get this warning everytime the text is updated.
file://.../TextArea.qml:728:9: QML QQuickTextEdit*: Binding loop detected for property "text"
See below for the code of the repeater.
Repeater {
model: TerminalFeed { }
delegate: TextArea {
id: terminal_text
font.family: "Courier"
width: parent.width
height: parent.height
readOnly: true
selectByMouse: true
wrapMode: TextEdit.NoWrap
horizontalScrollBarPolicy: Qt.ScrollBarAsNeeded
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
text: terminal_text.text + display
}
}
How can I stop this happening? Alternatively does anyone have a better way of achieving the same result?
Technically, that is indeed a binding loop because text is dependent on its own value. If QML didn't detect it and break it, an infinite loop of updating would result.
Instead of using a binding, you can do something like this:
Repeater {
model: TerminalFeed { }
delegate: TextArea {
id: terminal_text
font.family: "Courier"
width: parent.width
height: parent.height
readOnly: true
selectByMouse: true
wrapMode: TextEdit.NoWrap
horizontalScrollBarPolicy: Qt.ScrollBarAsNeeded
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
onDisplayChanged: {
text = text + display;
}
}
}
With the original binding approach, it will try and update whenever either display or text changes. With this approach, it will only try and update whenever display changes – which is what you really want.
I had a similar problem where I wanted to show logged data in a QML window.
I use the insert() method, which is inherited from QML TextField. The insertion position is the length of the TextArea.
TextArea {
id: outputTextArea
}
Component.onCompleted: {
data = "dummyString"
outputTextArea.insert(outputTextArea.length, data)
}

How do you select a character from text in Qt?

I've uploaded a text file and parsed the content into a text object. Eventually I will be passing each character in the string to a rectangle of its own. I am trying to figure out how to pick a character from the string in qml?
For example:
//Assume the text is already parsed from the file and it is stored in the object below
Text {
id:myText
text: "The quick brown fox jumps over the lazy dog"
visible:false
}
// And now i would like to transfer the first letter into a rectangle
Rectangle{
id: firstRect
width:rectText.width
height:rectText.height
color: "yellow"
Text {
id:rectText
text: // How do I set this as the first character 'T'
font.pixelSize: 14
color: "black"
}
}
How can set the rectangle with the character from myText to rectText ?
Eventually I will be setting each character in a rectangle of its own.
It is close to impossible. At least not by using what little font metrics functionality is provided to QML. There is TextMetrics available since Qt 5.4, but for some reason it didn't report the text size accurately, at least for the fonts that I've been using. It may have to do with this issue. I ended up getting accurate results by appending the 字 character to the queried text, and I don't even want to go into detail how I figured that out.
And then, if that happens to work, you only have the text dimensions, but no way to determine the position of that rectangle, since QML text elements can only give you the cursor position, but not the position of any particular character. If you have a single line of text it could be doable - just calculate the width of the preceding text, but for multi line it is a no-go.
You will probably have to take a very different approach. Maybe implement an adapter that presents strings as list models, and you represent every individual character as a QML element in something like a flow view.
But having a discrete visual item for every character will be huge overhead for long text, so if you are going to have such text, you will also have to handle a model proxy that only displays a particular portion of the text.
I can currently think of no other way to get accurate information about the position and size of text character. The API simply doesn't have that functionality.
There is a simple example that is perfectly applicable for short text:
ApplicationWindow {
id: main
width: 640
height: 480
visible: true
property var charlist: textfield.text.split('')
property int currentChar: 0
Column {
TextField {
width: main.width
id: textfield
text: "example text"
}
Flow {
width: main.width
height: 200
Repeater {
model: charlist
delegate: Rectangle {
Text { id: tt; text: modelData; font.pointSize: 20 }
width: tt.width
height: tt.height
color: "red"
border.color: index === currentChar ? "black" : "red"
MouseArea {
anchors.fill: parent
onClicked: {
currentChar = index
var pos = mapToItem(main.contentItem, 0, 0)
info.text = "rectangle x y w h: " + pos.x + ", " + pos.y + ", " + width + ", " + height
}
}
}
}
}
Text {
id: info
}
}
}
You can enter arbitrary text in the text field, which will be represented by a model view that creates an item for every character. Clicking a character will "select" it and will also give you the rectangle values corresponding to its position in the application window.

Add elements to a ListView inside another ListView

I need to insert elements in a ListView inside another ListView (via JS code inside my QML file) but when I try to access the inner ListView I get the error :
TypeError: Cannot call method 'insert' of undefined
Here is an example code to show my problem :
Item{
id:list
width: parent.width-210
height: parent.height
x:105
Component{
id:listDelegate
Item {
id:elem
height: 100
width: parent.width
Item{
id:titre_liste
height: 50
width: parent.width
Text{
anchors.left: parent.left
color:"white"
text:titre_txt
font.pixelSize: 25
font.bold: false
}
}
Item{
id:listInList
width: parent.width-100
height: parent.height
Component{
id:listInListDelegate
Item{
id:element_liste
height: parent.height
width: parent.width/5
Text{
anchors.left: parent.left
color:"white"
text:element_txt
font.pixelSize: 25
font.bold: true
}
}
}
ListView {
id: viewin
anchors.fill: parent
model: ListModel{
id:listModel_in
}
delegate: listInListDelegate
}
}
}
}
ListView {
id: viewglobal
anchors.fill: parent
model: ListModel{
id:listModel
}
delegate: listDelegate
}
}
And here is my JS code, at the end of the QML file :
function addItem(){
var i;
var numListe = -1;
var liste = "titre"
var item = "item"
for(i = 0;i<listModel.count;i++)
{
if(listModel.get(i).titre_txt === liste)
{
numListe = i;
}
}
if(numListe === -1)//if the list doesn't exist
{
listModel.append({titre_txt:liste});
numListe = listModel.count-1;
}
listModel.get(numListe).listModel_in.insert(0,{element_txt:item});
}
The error come from the last line of the JS code, when I try to insert a new element in the inner list. I verified that the value of "numListe" is 0 so it is not just a problem of wrong index.
How can I add elements to the inner list ?
There is a lot of stuff wrong with that code.
For starters - it is a mess, which is a very bad idea for someone who is obviously new at this stuff. Keep it clean - that's always a good idea regardless of your level of expertise.
listModel_in is an id and as such cannot be accessed outside of the delegate component.
That object however happens to be bound to the view's model property, so as long as the model doesn't change, you can access listModel_in via the model property. However, the view itself doesn't look like it is the delegate root object, so you have to interface it, for example by using an alias.
However, the inner model doesn't exist in the outer model, it only exists in the outer model's delegate item.
So you cannot possibly get it from listModel. You can get it from the viewglobal view, however ListView doesn't provide access by index. So you will have to set currentIndex for every index and use currentItem.
So it will look like this:
viewglobal.currentItem.modelAlias.insert(0,{element_txt:item});
But it should go without saying, you are putting data in the GUI layer, which is conceptually wrong. But it gets worse than conceptually wrong - you might not be aware of this, but ListView only creates items that it needs to show, meaning that it creates and destroys delegates as necessary. Meaning if your item falls out of view, it will be destroyed, and when it comes back into view, a new one will be created, and all the data you had in the model of the old delegate item will be lost. The view should never store data, just show it.
The inner model should be inside the outer model. However, last time I checked, QMLs ListModel didn't support model nesting, neither using declarative nor imperative syntax. If you want to nest models, I have provided a generic object model QML type you can use.

Custom attached properties in QML

I'm creating a custom QML component (a specialization of ListView that allows multiple selection). I'd like to provide attached properties to objects provided to my component. I see how to create attached properties using C++. However, I cannot find information on adding custom properties in pure QML. Is this possible using QML?
Is this possible using QML?
No.
There is an alternative, easy and clean way to this in QML - just use an adapter object that implements the desired properties. Then instead of attaching just nest into the adapter - use it as as a parent / container. You can also nest objects into the adapter, getting another C++ exclusive - grouped properties. A possible way to minimize the overhead of this is to use JS objects and properties, with a downside - no change notifications, which you can somewhat mitigate by emitting manually.
An example:
// Adapter.qml - interface with attached properties
Item {
id: adapter
property int customInt : Math.random() * 1000
property var group : {"a" : Math.random(), "b" : Math.random() }
default property Component delegate
width: childrenRect.width
height: childrenRect.height
Component.onCompleted: delegate.createObject(adapter)
}
// usage
ListView {
width: 100
height: 300
model: 5
delegate: Adapter {
Row {
spacing: 10
Text { text: index }
Text { text: customInt }
Text { text: group.a }
Text { text: group.a }
}
}
}
It is fairly painless and convenient compared to some other QML workarounds. You don't even have to do parent.parent.customInt - the properties are directly accessible as if they are attached, this works because of dynamic scoping. The default property allows to avoid setting the inner delegate as a property you just nest the delegate you want directly in the adapter.
In many cases those acrobatics are overkill, you can just wrap in place:
ListView {
width: 100
height: 300
model: 5
delegate: Item {
width: childrenRect.width
height: childrenRect.height
property string custom1: "another"
property string custom2: "set of"
property string custom3: "properties"
Row {
spacing: 10
Text { text: index }
Text { text: custom1 }
Text { text: custom2 }
Text { text: custom3 }
}
}
}
The only key part really is the binding for the size of the adapter object so that the view can properly layout the objects. I routinely use a Wrap element which essentially does the same but is implemented in C++, which is much more efficient than a QML binding.

Resources