How to remove QML binding on property from C++? - qt

With QML a property value can be based on another object property's value, it is called binding and it will update your value everytime the property you depend on is updated.
Like in this example, the implicitWidth of CppItem is half of the parent's width and the Text will fill the layout. If you resize the window, the CppItem width is updated.
Window
{
RowLayout {
anchors.fill: parent
CppItem { implicitWidth: parent.width * 0.5 }
Text { Layout.fillWidth: true }
}
}
In my problem CppItem is a QQuickItem with some C++ code, and in some specific cases the C++ code will set the implicitWidth with the following code.
I know I could use setImplicitWidth but I am not sure it would make a difference for my problem, and anyway you can not do that if the property is not declared in C++ but in a loaded QML file.
setProperty("implicitWidth", 100);
The property value is set but the QML binding is not removed. So the C++ code above is not equivalent to the QML code :
cppItem.implicitWidth = 100
With the C++ code, any change on parent.width will trigger an update on cppItem.implicitWidth and set again the value cppItem.implicitWidth.
How can I remove this binding from C++ ?

Using QObject::setProperty in C++ does not break the binding previously made in QML.
But using QQmlProperty::write does break the binding.
// setProperty("implicitWidth", 100); --> Does not break binding
QQmlProperty::write(this, "implicitWidth", 100);

Related

Sharing data across QML files (properties)

I am writing an App and I already managed to resolve some issues, with the latest one being the Screen.Width/Height for adjusting the window size dynamically on different monitors (I use laptop, phone, PC, it's simply convenient).
To write the code efficiently and nicely, I want to obtain that specific information and put it into a single set of 2 "variables", that would then hold this information.
I tried assigning the ApplicationWindow object an id: mainWindow, in order to call upon it from a different QML file, to obtain the property value as:
mainWindow.height, mainWindow.width
I then was told to use another approach, custom QML properties, that are declared like:
property (type) (name): (value)
I then followed the advice and declared those properties in Main.qml (with AppWindow) and it does work. The properties of AppWindow (ApplicationWindow) contain the width and height of the Screen multiplied by a specific coefficient.
Then those variables are accessed by the object itself, drawing the App Window as I want it to be.
The problem is that this approach was meant to solve the issue of sharing code across .QML files, and it doesn't
[go down]
ApplicationWindow {
id: mainWindow
//Wide screen support
//Screen.desktopAvailableWidth / 4
//Screen.desktopAvailableHeight / 6
//The below is monitor cross-compatible (phone, PC, laptop)
property int globalWidth: Screen.width / 2
property int globalHeight: Screen.height / 3
width: globalWidth
height: globalHeight
visible: true
title: qsTr("Redacted")
//setWindowIcon(QIcon(":/path/to/icon.png"));
The piece of code below resides in main.qml. It is called upon from that file. Just ignoring the IDs (IDs supposedly [as stated by users/docs] can't be accessed outside of local scope...), the properties (especially custom properties) should be accessible inside Page1, Page2, Page3 etc.
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
Page1Form {
}
Page2Form {
}
Page3Form {
}
}
The below is the Page2Form.qml file that is called upon (as class definition) in main.qml.
The properties declared in parent objects in main.qml (imo) should be inherited by child objects (imo).
Page {
id: localPage2
width: globalWidth
height: globalHeight
Rectangle {
id: rectangle
x: (localPage2.width / 2) - (width / 2)
y: (localPage2.height / 4) - (height / 2)
width: localPage2.width / 3
height: localPage2.height / 6
color: "#ffffff"
border.color: "#a45c5c"
border.width: 2
TextInput {
id: textInput
x: 0
y: 0
width: localPage2.width / 3
height: localPage2.height / 6
text: qsTr("Redacted")
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.weight: Font.Normal
focus: true
}
}
Ok, so what is the problem?
I can try calling those properties as:
mainWindow.globalHeight
globalHeight
mainWindow.height
etc.
They won't be accessed. The form editor will provide me preview of Page object that has 0 size.
The page does render eventually (when compiled and ran), but there is an issue in passing (accessing) the value of that property.
As you can notice, both IDs and custom properties seem to work just fine locally.
Update:
I haven't fixed that issue, I also tried using aliases (references) and the "foreign" QML file will still fail to be assigned proper size (Page width and height).
I then put another custom property of string type with some text in it, I then managed to access that property in Page2.qml and the property is originally in main.qml.
It's bugged, or I have no idea what it is.
I tried 3 approaches:
ID (it's not global as it turns out)
Custom property (kind of works, just not with Screen size...)
Aliases on object's default properties
(property alias globalWidth: mainWindow.width
property alias globalHeight: mainWindow.height)

Repater: Using C++ property as model

I have a C++ property
Q_PROPERTY(QList<qreal> XTickPos MEMBER _xTickPos);
which I want to use in a Repeater. In the same QML file, the c++ class has been given the id
id: pw
The repeater looks like this
Item {
anchors.fill: parent
visible: true
Repeater {
model: pw.XTickPos.length
Rectangle{
height: 50
width: 2
x: pw.XTickPos[index]
y:10
visible: true
color: "black"
border.width: 2
}
}
}
However, nothing is drawn on the screen. If instead I make property in the QML file:
var xTickPos = []
and set it via a Q_Invokable function in c++
Q_INVOKABLE QList<qreal> getXTickPositions();
and in QML
root.xTickPos=pw.getXTickPositions();
and use the QML property xTickPos as model in the above repeater it is working. I checked that pw.XTickPos is correctly filled via a console.log
What am I missing here?
This one is kinda tricky.
The documentation states that you can use a JS array as a model, and it does state that QList<qreal> is automatically converted to a JS array when returned to QML.
But it seems that you can't use a QList<qreal> that is automatically converted to a JS array as a model. Go figure...
Naturally, it is preferable to have a proper model with its proper notifications for top efficiency. But in case you really want to go for the list property, it appears you will have to do the conversion manually in a getter:
QVariantList model() {
QVariantList vl;
for (auto const & v : yourList) vl.append(v);
return vl;
}
Amazingly, although Qt supposedly makes that conversion automatically, it doesn't seem to be able to make a QVariantList from a QList<qreal>.
That's Qt for you...

Qt/QML: Trying to create a log file view with TextEdit, result is really slow

I am working on an Android App. I need to display a console like type of log, which will be written to by the C++ back end. I have tried doing this combining a TextEdit and ScrollView, but the result is really slow. As soon as my log goes beyond ~50 lines, adding a few lines slows down (locks) the interface for a few seconds.
Trimming down the source code, this is the log view section:
property int logMaxLines: 50
ScrollView {
id: logScrollView
anchors.fill: parent
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
TextEdit {
id: logTextEdit
anchors.fill: parent
readOnly: true
color: "darkgreen"
property int linesTrimmed: 0
}
}
Connections{
target: gate
onNewMessageLineAdded :
{
logTextEdit.append(gate.newMessageLine)
if (logTextEdit.lineCount > logMaxLines) {
while (logTextEdit.lineCount >= logMaxLines) {
logTextEdit.text = logTextEdit.text.slice(logTextEdit.text.indexOf('\n')+2)
logTextEdit.linesTrimmed++
}
logTextEdit.insert(0, "[... trimmed " + logTextEdit.linesTrimmed + " lines ...]\n")
}
}
}
I picked a ScrollView as I'd like to have the vertical scroll bar. Lines are added one at a time by the C++ code, when it emits the newMessageLineAdded signal. This is coming from a class which includes this Q_PROPERTY, used to pass the new line content:
Q_PROPERTY(QString newMessageLine READ newMessageLine NOTIFY newMessageLineAdded)
the signal is declared as:
void newMessageLineAdded();
I have added the small bit of java to trim the log when it grows too long, as the issue is there even when this trimming code is not present.
Am I doing something very clunky here? Should I use another type of object to replace the TextEdit, knowing that it is not used at all to edit text, but only as a display?
Thanks.
I recommend you to use ListView instead of TextEdit. And use QStringListModel as model declared in C++ code and added to QML as context property. Read Embedding C++ Objects into QML with Context Properties. It is recommended for better perfomance to have most of logic in C++ code.

Unable to set the parent of a dynamically created QML component

I am creating dynamic component in QML as follows:
var component = Qt.createComponent("PlayerWindow.qml")
if (component.status != component.errorString())
console.log(component.errorString())
var playerWin = component.createObject(rootWindow);
Here rootWindow is my main application window. Now, the PlayerWindow is quite simple as:
Window {
id: playerWindow
width: parent.width
height: parent.height
Component.onCompleted: {
console.log(parent.width)
console.log(rootWindow.height)
}
}
The thing is that the values for parent.width and rootWindow.width are really different and this is also evident when the window is displayed. However, rootWindow is set as the parent in the createObject call. So, I am not sure what is happening there but I wanted to know if this is the correct way to set the component parent when they are being dynamically created.
Try to add console.log(parent) in the code. You will see something like qml: QQuickRootItem(0x1e3e4e0). If you check the Qt doc you will find that Item.parent() returns Item but Windows is not Itemdescendant but QQuickWindow. Also from documentation:
A QQuickWindow always has a single invisible root item ...
So, in your case parent and rootWindow are different objects.
P.S. The dynamic object creation in your code can produce an error since component.createObject will be executed although Qt.createComponent returns error. Just copy the code from Qt documentation.

Get index of the delegate currently displayed - QML ListView

I created a ListView, which displays a couple of pages of content defined by the user (plain text). The page displayed is a delegate. Only one page is visible at a time. I decided to use it to get snapping to one item, in the same way the iOS' launcher works. The user simply flicks between the pages. (this is to be used on touch screens)
I need to have the index of the currently displayed page for some operation. currentIndex of the ListView always stays == 0. How can I get it?
For those who prefer code:
ListView
{
onCurrentIndexChanged: console.log(currentIndex) // this gets called only once - at startup
delegate: Column
{
// The page displayed, only one page at a time
}
}
Thanks
There are many ways to get the index of current item that is displayed in the screen. If you can get the x-y coordinate of current page, you can use indexAt method in ListView.
And in each delegate, you can find the index using index role within the scope of the delegate. The index is like a role you declared in your model, and is automatically assigned by ListView. For example,
ListView
{
delegate: Column
{
property int indexOfThisDelegate: index
//...
}
}
The index role is introduced here:
A special index role containing the index of the item in the model is also available to the delegate. Note this index is set to -1 if the item is removed from the model...
Another way is to explicitly assign value to the currentItem property in ListView, so the view can scroll by itself. Here is an simple example in Qt documentation, which is similar to your application.
I know this is quite old but I had the same problem and spend some time trying to find a way to get currentIndex that would work for me. In my case sometimes I need to change the width of my ListView so I have to recalculte currentIndex manualy every time I resize it.
But I found a highlightRangeMode property. When it's set to ListView.StrictlyEnforceRange then currentIndex is always updated automaticly and contains correct index of the currently visible item.
ListView {
highlightRangeMode: ListView.StrictlyEnforceRange
// ...
}
You can do like that:
QModelIndex index =this->indexAt(event->pos());
this ->setCurrentIndex(index);
You can use attached properties of ListView class (ListView). They are attached to each instance of the delegate.
See ListView.isCurrentItem or ListView.view example:
ListView {
width: 180; height: 200
Component {
id: contactsDelegate
Rectangle {
id: wrapper
width: 180
height: contactInfo.height
color: ListView.isCurrentItem ? "black" : "red"
Text {
id: contactInfo
text: name + ": " + number
color: wrapper.ListView.isCurrentItem ? "red" : "black"
}
}
}
model: ContactModel {}
delegate: contactsDelegate
focus: true
}

Resources