Binding Checkbox 'checked' property with a C++ object Q_PROPERTY - c++

I'm learning QtQuick and I'm playing with data binding between C++ classes and QML properties.
In my C++ object Model, I have two properties :
Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
Q_PROPERTY(bool status READ getStatus WRITE setStatus NOTIFY statusChanged)
And in my .qml file :
TextEdit {
placeholderText: "Enter your name"
text: user.name
}
Checkbox {
checked: user.status
}
When I change the user name with setName from my C++ code, it is automatically reflected in the view.
When I check/uncheck the checkbox, or when I call setStatus() from my C++ code, nothing happens. It seems the property checked of checkboxes haven't the same behavior as TextEdit components.
I don't want to bind my properties in a declarative way. Doesn't Qt Quick support property binding ?
Thank you for your help.

As leemes points out, user clicking the check box breaks the binding you've created. So, don't create the binding, but instead connect to the change signal directly to handle the "get" case. Use "onClicked" to handle the "set" case. This solution requires you also initialize in Component.onCompleted(). For example...
CheckBox {
id: myCheck
onClicked: user.status = checked
Component.onCompleted: checked = user.status
Connections {
target: user
onStatusChanged: myCheck.checked = user.status
}
}

A way around this is to restore the binding (that gets removed by the user clicking the checkbox) in onClicked, by something like:
CheckBox {
checked: user.status
onClicked: {
user.status = checked;
checked = Qt.binding(function () { // restore the binding
return user.status;
});
}
}
This avoids problems if you don't have the possibility to access your model at the time Component.onCompleted is invoked.

I find it more natural to make checkbox only emit signal on click, not change its state:
// MyCheckBox.qml
CheckBox {
id: control
property bool changeOnClick: true // or just emit clicked()
MouseArea {
anchors.fill: parent
enabled: !control.changeOnClick
onClicked: control.clicked();
}
}
Then you can bind it once and request change of the source on click:
MyCheckBox {
changeOnClick: false
checked: user.state
onClicked: {
user.state = !user.state;
}
}

Related

A bug about Keyboard Focus in Qt Quick in QT 5.10 document?

I am reading the Qt doc to learn the Keyboard Focus in Qt Quick! I run the code which from the document. However, the result is different form the document! The codes are as follows.
main.qml
//Window code that imports MyWidget
Rectangle {
id: window
color: "white"; width: 240; height: 150
Column {
anchors.centerIn: parent; spacing: 15
MyWidget {
focus: true //set this MyWidget to receive the focus
color: "lightblue"
}
MyWidget {
color: "palegreen"
}
}
MyWidget.qml
Rectangle {
id: widget
color: "lightsteelblue"; width: 175; height: 25; radius: 10; antialiasing:
true
Text { id: label; anchors.centerIn: parent}
focus: true
Keys.onPressed: {
if (event.key == Qt.Key_A)
label.text = 'Key A was pressed'
else if (event.key == Qt.Key_B)
label.text = 'Key B was pressed'
else if (event.key == Qt.Key_C)
label.text = 'Key C was pressed'
}
}
The pic1 is the result form doc. And the pic2 is the result of my running which i just copy the code from doc and run it.
pic1
pic2
Is it a bug? Why the result are different?
The doc said:
We want the first MyWidget object to have the focus, so we set its focus property to true. However, by running the code, we can confirm that the second widget receives the focus.
Looking at both MyWidget and window code, the problem is evident - there are three types that set the focus property to true. The two MyWidgets set the focus to true and the window component also sets the focus. Ultimately, only one type can have keyboard focus, and the system has to decide which type receives the focus. When the second MyWidget is created, it receives the focus because it is the last type to set its focus property to true.
My question:
1.Why the result are different?In my result the first widget receives the focus.
2.Also, what is the meaning of"because it is the last type to set its focus property to true"in doc?
The doc you can get from here!
Qt doc
When I run the code I get the same result as you, but it does not matter. The point here is to understand that without FocusScope you have no control on which object receives the focus from the system (you have no guarantee in the order of creation of QML objects).
Did you try to set focus: true on the second MyWidget instead? If you try, the keyboard events will still be received by the first widget, except if you use a FocusScope.
Note that you can also remove focus: true from MyWidget.qml and you won't even need to manually add a FocusScope (it will automatically be managed by the application window).
try run with the following modification:
MyWidget { //the focus is here
color: "palegreen"
}
MyWidget {
focus: true //set this MyWidget to receive the focus
color: "lightblue"
}
MyWidget {
color: "palegreen"
}
I tested many times. The result is the same. Maybe the docs are outdated!

Qt/QML: WebEngineView and ScrollView

I have desktop app in which I have a ScrollView that contains a ListView, in which the delegates each contain multiple widgets, including a WebEngineView:
ScrollView
{
id: myScrollView
anchors.fill: parent;
ListView
{
id: myListView
delegate: Item
{
Rectangle
{
Text ...
Text ...
// other stuff
WebEngineView
{
id: myWebEngineView
Component.onCompleted:
{
loadHtml(model.modelData.someHTMLData);
}
}
}
}
}
}
The problem I am having is with scrolling. On Mac, if I use the touchpad to scroll, the ListView only scrolls if the mouse is hovered over one of the non-WebEngineView widgets.
I suspect the WebEngineView widgets are trapping the mouse messages but I cannot find a way stop this from happening. How can I do this?
One thing that I know about Qt Quick is that it has input focus.
According to this, you can play with FocusScopes and focus property.
For example set ListView's focus to true, lay delegate into FocusScope with focus: false. Or set WebEngineView's focus to false.
Hope this helps =)

How to update QML ListView after appending an element into ListModel?

I have a ListView in my QML:
ListView {
id: listView1
width: parent.width
height: parent.height
interactive: true
clip: true
visible: true
property int elementH: 200
Component.onCompleted: {
mSingals.setPlotList(listView1);
}
function addImage(src) {
listModel.append({"imageSrc": src});
}
function clear() {
listModel.clear();
}
function redraw() {
// ?????
//listView1.update();
}
model: ListModel {
id: listModel
objectName: lModel
}
delegate: Rectangle {
id: delegateItem
width: listView1.width; height: listView1.elementH
color: "blue"
Image {
anchors.left: parent.left
source: imageSrc
visible: true
}
}
}
At first I add some items using an image provider and function addImage(src):
QVariant qv(QString("image://plots/").append(imageName));
QMetaObject::invokeMethod(plotList, "addImage", Q_ARG(QVariant, qv));
Everything works fine, the ListView updates automatically.
Then I clear all items from the ListModel and add some new ones:
QMetaObject::invokeMethod(plotList, "clear");
QVariant qv(QString("image://plots/").append(someNewImage));
QMetaObject::invokeMethod(plotList, "addImage", Q_ARG(QVariant, qv));
The ListView doesn't update even with something like:
QMetaObject::invokeMethod(plotList, "redraw");
So I have to scroll up and down several times to make old elements disappear and new ones appear, but anyway the first element of 20 remains the same, whatever I do.
What should I use in redraw() to force the ListView to update?
Maybe somehow emit a dataChanged signal of ListModel?
Thank you for your answers! In my case the issue was in images' names. The names remained the same, and, though the image provider could provide new images by this names, the ListView didn't request the new images. So now I just change the names for every update.
Well, you shouldn't need to do anything to get the correct behavior. I suppose the problem has to do with doing things from C++ which happens in a blocking manner and doesn't give the objects the time for events to be processed.
Try using a queued connection for invokeMethod().
QMetaObject::invokeMethod(plotList, "addImage", Qt::QueuedConnection, Q_ARG(QVariant, qv));
However, accessing QML functions from C++ spells "bad design", you shouldn't really be doing that, QML should access C++, not vice versa. If you need to compose the actual strings in C++ that much, you can use a signal and emit the string with it, and connect it to QML handlers, this way you will interact with the model from QML.
This should be solved by setting the cache property of the QML Image to false. From the Image caching section of http://doc.qt.io/qt-5/qquickimageprovider.html:
Images returned by a QQuickImageProvider are automatically cached, similar to any image loaded by the QML engine. When an image with a "image://" prefix is loaded from cache, requestImage() and requestPixmap() will not be called for the relevant image provider. If an image should always be fetched from the image provider, and should not be cached at all, set the cache property to false for the relevant Image

How to change the color of text in a declarative way in Qt QML

I have a really basic GUI that I am making using Qt Quick Designer. When a button is clicked I want some text to change color. So here is what I have for the code:
onClicked: {
if(displayText.color == "#ff0000") {
displayText.color = "Black";
}
else if(displayText.color == "#000000"){
displayText.color = "Red";
}
}
So this works when I run it, yet when I try to go to the Qt design view it says that "Imperative code is not supported in the Qt Quick Designer". Am I doing something wrong or is this just a Qt quirk that I am encountering? I know that there are ways to handle this in C++ but I was hoping to do it all in QML.
Just ignore the warning. Your code is okay, only that Qt Quick Designer can't yet run scripts or properly handle some script bindings. I suggest though that you use states instead of scripting the property changes. It will result in much cleaner and less redundant code:
onClicked: state = (state !== 'clicked' ? 'clicked' : '')
color: '#ff0000' // or #000000, if that's your default color
states: State [
name: 'clicked'
PropertyChanges {
target: displayText
color: '#000000'
}
]
Qt Quick Designer handles states very well (it shows them at the top), where you can select a state and change property values within the designer.
You case just use simple boolean flag indicating color:
property bool colorFlag: false
...
color: colorFlag ? "black" : "red"
...
onClicked: colorFlag = !colorFlag
Or you can use something like this:
MouseArea {
id: myMa
}
...
color: myMa.clicked ? "black" : "red"
This way is much cheaper than with states.

QML : Link MenuBar and ToolBar actions

I'm quite new to Qt Quick (and Qt in general), and i'd like to have an advice on "good way" to do this.
In an application, if I have a menubar and toolbar that have common actions, is there a way to link the buttons from menubar and buttons from toolbar ?
For instance, if I have a "save" function. This action is avaible through menubar and toolbar. How can I mutualise this action ?
At the moment, the best way i've found is to create a function "save" that is called by both buttons.
I've actually found a "good practice" for this problem on QML example : use Action items.
For instance :
FileDialog {
id: openDialog
onAccepted: myData.source= fileUrl
}
Action {
id: openFile
iconSource: "images/fileopen.png"
text: "Open"
onTriggered: openDialog.open()
}
menuBar: MenuBar {
Menu {
MenuItem { action : openFile }
// ....
toolBar : ToolBar {
ToolButton { action:openFile}