Passing QList <QObject *> from C ++ to QML - c++

Good evening,
We are programming a DaVinci DM8168 board with Qt5.7.1 and QtQuick2.7
we would like to pass a list of type:
QList from C ++ to QML
to be able to draw it asynchronously and above all efficiently. The list is modified by a thread on C ++.
We cannot state the update frequence of this list at all: it can be just one time per second or dozens! It depends by the target enviroment.
our solution (partially working) is the following:
On the C++ side we are using QPROPERTIES:
an extract of our code:
MyClass.h
class MyClass: public Observer
{
Q_OBJECT
Q_PROPERTY(QList<QObject*> getFailuresHistory READ getFailuresHistory NOTIFY failuresHistoryListChanged)
QList<QObject*>getFailuresHistory(){return failuresHistory_;}
signals:
void failuresHistoryListChanged();
}
then my .cpp we emit the signal every time the list changes ( not true at all: if more elements of the list changes at the same time we emit the signal only once in order to optimize the application)
emit failuresHistoryListChanged();
Then on QML "side" we do the following:
Item
{
// ... other code
//DELAGATE
Component
{
id: failuresDelegateHistory
Item
{
id: listHistoryItem
width: listHistory.width
height: 10
anchors.leftMargin: 5
Row
{
Text{
id: failureIdHistory
width: 30
text: qsTr(modelData.codeFailure) + mainSettingsHandler.emptyString
color: mainWindow.currentStyleColor.colorRed
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
}
Text {
id: descriptionFailIdHistory
width: (listHistoryItem.width-failureIdHistory.width)*0.5
color:mainWindow.currentStyleColor.colorWhite
maximumLineCount: 1
text: qsTr(modelData.descriptionFail) + mainSettingsHandler.emptyString
wrapMode: Text.WrapAnywhere
horizontalAlignment: Text.AlignJustify
font.pixelSize: 12//16
}
Text
{
id: columnTimeStampFailureHistory
width: (listHistoryItem.width-descriptionFailIdHistory.width-failureIdHistory.width- 2)
maximumLineCount: 1
text: modelData.dateFail + mainSettingsHandler.emptyString
wrapMode: Text.WrapAnywhere
color:mainWindow.currentStyleColor.colorWhite
font.pixelSize: 12
horizontalAlignment: Text.AlignJustify
}
}
}
}
//LIST
ListView
{
id: list1
y: labelHistory.y + labelHistory.height+5
width: (FileConfiguration.widthMenuArea - space - 5)*0.5-1-space
height: (270 - space) *itemsPerPage
model: myClass.getFailuresHistory//failuresHistoryTable.getFailuresHisory
delegate: failuresDelegateHistory
spacing: 10
enabled: false
}
// ... other code
}
OK;
as I said before this solution is partially working because is very, very slow!
When the:
emit failuresHistoryListChanged();
on c++ side is called, the list is drawn but the software is "slow". We have keepalive message that shall be set every 300 ms and that is not sent anymore for a couple of seconds.
We need a more efficent way to pass the list to the QML.
if we comment the emit the software works fine within its deadlines.
So our purpose is: we would like to pass in a very efficent way this list to QML. Do you have solutions that involve a modification of the code that we have shown you or new architectures? do you have any examples? we are not experts in qml.
If you need more information ask us.
We thank you in advance, we have been trying to solve the problem for a long time.
thanks

Related

Changing the model does not redraw objects in QML sometimes

Repeater {
model: myModel.buttonParameters
delegate: Button
{
width: 47
height: 47
contentItem: Text {
id: content
text: modelData.name
font.family: MyStyle.fontFamily
fontSizeMode: Text.Fit
font.pixelSize: 30
font.styleName: "Bold"
topPadding: height / 6
color: modelData.visibility ? MyStyle.colorFromSeriesName(this.text) : MyStyle.dividerColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle
{
anchors.fill: parent
radius: 4
color: MyStyle.backgroundColor
border.color:MyStyle.dividerColor
border.width: 2
}
onClicked: {
cntModel.visibilityOfChartChanged(modelData.name, "plot");
}
}
}
On the C++ side.
myModel.buttonParameters is a QList<MyModel*> , where MyModel is a class inherited from QObject.
Q_PROPERTY(QVariant buttonParameters READ buttonParametersList NOTIFY buttonParametersChanged)
QVariant buttonParametersList()
{
return QVariant::fromValue(m_buttonParametersList );
}
The problem is that with a certain change in the model
(the signal buttonParametersChanged is sent), namely,
if the number of objects was equal to one and after the update there
is also one object, but with different characteristics, no redrawing
takes place, the old button remains. Moreover, it somehow depends on
the runtime. Also, if I remove the line with color, the model will update.
The issue is that buttonParametersChanged is a signal that triggers on the assignment of a new container (QList<>) to buttonParameters. It doesn't trigger on changes to the contents of an existing QList<> assigned to that property.
However, you can always manually trigger buttonParametersChanged when you know you've modified the contents of the QList<> which should give you the effect you want.
Note, a QML ListModel or C++ equivalent would likely be more appropriate for this use case. Many QML components are designed to specifically integrate with them and handle the cases of container contents changing.

Simple keyboardless touchscreen widgets in Qt

I'm looking for a simple way to make widgets for a touch-screen that will allow users to set the time and IP address on the computer running the code and provide a simple (uppercase Latin-alphabetic) name.
This question is not about how to actually set the system time or IP address; I'm just looking for information about how to make the graphical widgets themselves.
What I want is for each editable property (time, address, and name) to be divided into "scrollable" fields, where the fields for "time" are hours, minutes, possibly seconds, and AM/PM/24-hr, and the fields for address/name are the individual characters. Each field would have an arrow above and below it, and touching on an arrow would scroll through the valid values for that field.
I think this is a pretty common UX pattern, especially in meatspace (e.g. on alarm clocks), but just in case it's not clear what I'm trying to describe, here's an example with a user editing the "name" property:
^^^
BN
vvv
User presses "down" below the "N":
^^^
BO
vvv
User presses "down" below the empty space:
^^^^
BOA
vvvv
...and again on the same down-arrow:
^^^^
BOB
vvvv
I'm writing this using C++14 with Qt 5. (If worst comes to worst, I'd be open to writing a separate app using a different language and/or framework, but I'm not asking for framework suggestions here; if you have one, let me know and I'll open a corresponding question on Software Recommendations SE.)
I don't see anything in the Qt 5 widget library like this; most of the input widgets are text fields. QSpinBox looks somewhat promising, but the arrows are probably too small for my touchscreen, and using a separate spinbox for each letter would probably be confusing and ugly.
I don't really know enough about Qt or GUI-programming in general to feel confident trying to write my own widgets from scratch, but this interface looks simple enough that I would expect a couple lines of QML would get me well on my way.
ListView as well as PathView can produce the desired result with slightly different behaviors and slightly different performances. Differently from ListView, PathView is circular, i.e. elements can be iterated continuously by using just one of the selection controls. It is also easier to fully customize the behavior of the path in PathView via the PathAttribute type. Anyhow path customization seems not to be a required feature, according to the question.
If you implement the solution via a ListView you should ensure that just one element is shown and that any model is processed.
Component {
id: spinnnnnnnner
Column {
width: 100
height: 110
property alias model: list.model
property string textRole: ''
spacing: 10
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "-"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.decrementCurrentIndex() }
}
ListView {
id: list
clip: true
width: 100
height: 55
enabled: false // <--- remove to activate mouse/touch grab
highlightRangeMode: ListView.StrictlyEnforceRange // <--- ensures that ListView shows current item
delegate: Text {
width: ListView.view.width
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 50
font.bold: true
text: textRole === "" ? modelData :
((list.model.constructor === Array ? modelData[textRole] : model[textRole]) || "")
}
}
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "+"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.incrementCurrentIndex() }
}
}
}
The checks over the model ensure that any type of model can be passed to the component. Here is an example using three very different models:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
ApplicationWindow {
visible: true
width: 400
height: 300
ListModel {
id: mod
ListElement {texty: "it1"}
ListElement {texty: "it2"}
ListElement {texty: "it3"}
}
Row {
Repeater {
id: rep
model: 3
delegate: spinnnnnnnner
Component.onCompleted: {
rep.itemAt(0).model = mod // listmodel
rep.itemAt(0).textRole = "texty"
rep.itemAt(1).model = 10 // number model
//
rep.itemAt(2).model = ["foo", "bar", "baz"] // array model
}
}
}
}
PathView implementation is not so different from the ListView one. In this case it is sufficient to define a vertical path and specify that just one one element is visible at a time via pathItemCount. Finally, setting preferredHighlightBegin/preferredHighlightEnd ensures that the visible element is centered in the view. The revisited component is the following:
Component {
id: spinnnnnnnner
Column {
width: 100
height: 110
property alias model: list.model
property string textRole: ''
spacing: 10
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "-"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.decrementCurrentIndex() }
}
PathView {
id: list
clip: true
width: 100
height: 55
enabled: false // <--- remove to activate mouse/touch grab
pathItemCount: 1
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
path: Path {
startX: list.width / 2; startY: 0
PathLine { x: list.width / 2; y: list.height }
}
delegate: Text {
width: PathView.view.width
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 50
font.bold: true
text: textRole === "" ? modelData :
((list.model.constructor === Array ? modelData[textRole] : model[textRole]) || "")
}
}
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "+"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.incrementCurrentIndex() }
}
}
}

Binding C++ to QML code when using Components

---edited url and changed dynamic part to something compilable----
(Using Qt 5.3)
I tried to create a compact sample, but its still too big to post all the files here separately, so i added a link to "uploaded.to" as i cannot seem to attach a zip file here :-((
(warning, spam links and / or waiting time, any better fileshare site you recommend ?
Here is a link to "bindtest.zip" via uploaded.com, beware of spam/ugly pix:
http://ul.to/lqemy5jx
Okay, i will try to post the essence of the files here anyways:
I tried to create a simple Class in C++ containing a StringList and an index.
I Instantiated two Objects of this Class and exposed them via "setContextProperty"
This should be used in QML to initialize a ListView and to be in sync with it.
So whenever a User changes the index in QML, C++ should be notified AND vice versa.
So when i create two Component qml files using the hardwired names set in "setContextProperty" it seems to work fine.
But for the life of me i cannot create a single component file and pass the DataObject to it as a parameter, i simply do not know how to do it, although i tried.
My "final" target ist to create a QML Object dynamically and pass the DataObject to it, this does not work either :-(
So here it comes, code snippets of my sample Project:
Declaring my oh-so-simple Class (DataObject.h)
#ifndef DATAOBJECT_H
#define DATAOBJECT_H
#include <QObject>
#include <QDebug>
class DataObject : public QObject
{
Q_OBJECT
Q_PROPERTY( int index MEMBER m_index NOTIFY indexChanged )
public slots:
int count() const { return m_Elements.count(); }
QString at(int idx) const { return m_Elements.at(idx); }
public:
void setIndex(int theInt) { m_index = theInt; }
signals:
void indexChanged(int);
public: // too lazy to write accessors for this sample, so make it public
QStringList m_Elements;
private:
int m_index;
};
#endif // DATAOBJECT_H
Registering it in main.cpp:
qmlRegisterType<DataObject>("bindtestTypes", 1, 0, "DataObject");
Here is the part of "dialog.cpp" that initializes and exposes two DataObects:
//preparing first list
m_firstDO.m_Elements = QStringList() << "A" << "B" << "C" << "D";
m_firstDO.setIndex(0);
//preparing second list
m_secondDO.m_Elements = QStringList() << "a" << "b" << "c" << "d";
m_secondDO.setIndex(3);
//publish the 2 Dataobjects
m_engine.rootContext()->setContextProperty( "cppDataList_1", &m_firstDO);
m_engine.rootContext()->setContextProperty( "cppDataList_2", &m_secondDO);
Here is the QML file "ShowLists.qml" that should simply show the 2 ListVies on Top of each other, i commented the 2 NOT working approaches that i would love to work, especially the dynamic one:
import QtQuick 2.2
import QtQuick.Window 2.1
import bindtestTypes 1.0
Window {
visible: true
width: 200
height: 400
Rectangle{
anchors.fill: parent
//dynamic: does not work :-(
// need to click on it to create it
// Rectangle{
// id:upperList
// anchors.top: parent.top;
// anchors.left: parent.left
// width:200
// height:200
// MouseArea{
// anchors.fill: parent
// onClicked: {
// var component = Qt.createComponent("SimpleList.qml");
// var dyncbb = component.createObject(parent, {"theDO": cppDataList_1});
// }
// }
// }
// Rectangle{
// id:lowerList
// anchors.bottom: parent.bottom;
// anchors.left: parent.left
// width:200
// height:200
// MouseArea{
// anchors.fill: parent
// onClicked: {
// var component = Qt.createComponent("SimpleList.qml");
// var dyncbb = component.createObject(parent, {"theDO": cppDataList_2});
// }
// }
// }
//static: would not be my first choice but isnt working anyways...
// SimpleList {
// id:upperList
// property DataObject theDO: cppDataList_1
// anchors.top: parent.top;
// anchors.left: parent.left
// }
// SimpleList {
// id:lowerList
// property DataObject theDO: cppDataList_2
// anchors.bottom: parent.bottom;
// anchors.left: parent.left
// }
//hardwired works, but its not workable for my rather complex project...
SimpleList1 {
id:upperList
anchors.top: parent.top;
anchors.left: parent.left
}
SimpleList2 {
id:lowerList
anchors.bottom: parent.bottom;
anchors.left: parent.left
}
}
}
Here is the first hardwired SimpleList1.qml that works fine, as well as the second:
import QtQuick 2.2
ListView {
id: list_view
width: 200
height: 200
currentIndex: cppDataList_1.index
model: cppDataList_1.count()
delegate: Rectangle {
height: 20
width: 200
Text { text: cppDataList_1.at(index); color: (list_view.currentIndex === index)?"red":"black" }
MouseArea{ anchors.fill: parent; onClicked: list_view.currentIndex = index }
}
onCurrentIndexChanged: cppDataList_1.index = currentIndex;
}
This is the "SimpleList.qml" that i cannot seem to get to work:
import QtQuick 2.2
import bindtestTypes 1.0
Rectangle {
ListView {
id: list_view
property DataObject theDO
width: 200
height: 200
currentIndex: theDO.index
model: theDO.count()
delegate: Rectangle {
height: 20
width: 200
Text { text: theDO.at(index); color: (list_view.currentIndex === index)?"red":"black" }
MouseArea{ anchors.fill: parent; onClicked: list_view.currentIndex = index }
}
onCurrentIndexChanged: theDO.index = currentIndex
}
}
So, can anyone of you help me to get this solved ??
IF you dare to follow the uploaded link and run my sample you can see one more glitch.
It displays 2 Windows, one QQQuickWIndow and a Widget.
In the Widget i can change the indexes as well as in the QML Window.
At first they are in sync but then the QML Window does not get updated anymore by changing the index in the widget, i hope its a glitch and not another general error i made.
Greetings & thanks for any help !
Nils
Argh, i found the problem, i did a very simple mistake:
The property i want to set in the SimpleList Component has to be in the root Object, so instead of this:
Rectangle {
ListView {
id: list_view
property DataObject theDO
...
It has to be done this way:
Rectangle {
property DataObject theDO
ListView {
id: list_view
...
Wow, thats an easy solution for a (seemingly) complex Problem.
Greetings,
Nils

Autocomplete and suggesstion in QML textInput element

I have a QML textInput element like this:
TextBox.qml
FocusScope {
id: focusScope
property int fontSize: focusScope.height -30
property int textBoxWidth: parent.width * 0.8
property int textBoxHeight: 45
property string placeHolder: 'Type something...'
property bool isUserInTheMiddleOfEntringText: false
width: textBoxWidth
height: textBoxHeight
Rectangle {
width: parent.width
height: parent.height
border.color:'blue'
border.width: 3
radius: 0
MouseArea {
anchors.fill: parent
onClicked: {
focusScope.focus = true
textInput.openSoftwareInputPanel()
}
}
}
Text {
id: typeSomething
anchors.fill: parent; anchors.rightMargin: 8
verticalAlignment: Text.AlignVCenter
text: placeHolder
color: 'red'
font.italic: true
font.pointSize: fontSize
MouseArea {
anchors.fill: parent
onClicked: {
focusScope.focus = true
textInput.openSoftwareInputPanel()
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
focusScope.focus = true
textInput.openSoftwareInputPanel()
}
}
TextInput {
id: textInput
anchors {
right: parent.right
rightMargin: 8
left: clear.right
leftMargin: 8
verticalCenter: parent.verticalCenter
}
focus: true
selectByMouse: true
font.pointSize: fontSize
}
Text {
id: clear
text: '\u2717'
color: 'yellow'
font.pointSize: 25
opacity: 0
visible: readOnlyTextBox ? false : true
anchors {
left: parent.left
leftMargin: 8
verticalCenter: parent.verticalCenter
}
MouseArea {
anchors.fill: parent
onClicked: {
textInput.text = ''
focusScope.focus = true;
textInput.openSoftwareInputPanel()
}
}
}
states: State {
name: 'hasText'; when: textInput.text != ''
PropertyChanges {
target: typeSomething
opacity: 0
}
PropertyChanges {
target: clear
opacity: 0.5
}
}
transitions: [
Transition {
from: ''; to: 'hasText'
NumberAnimation {
exclude: typeSomething
properties: 'opacity'
}
},
Transition {
from: 'hasText'; to: ''
NumberAnimation {
properties: 'opacity'
}
}
]
}
I want to add autocomplete and suggestions like google search to this text box. Autocomple get data from database and database return a list of dictionaries by a pyside SLOT.(or c++ slot)
How I can do this work?
Take a look at this code: https://github.com/jturcotte/liquid/blob/master/qml/content/SuggestionBox.qml
I bet it will do the job.
Edit:
Code that linked above is somewhat complicated and requires C++ backend, so I simplified it and made pure Qml example application, that you can play with, edit a little and apply to your needs. Sources can be found here. Most important things there are:
This implementation of SuggestionBox that uses some sort of model as it's source for completing/suggesting something
Its signal itemSelected(item) will be emitted every time user clicks on item
Main component of application that binds its LineEdit component to SuggestionBox
Note that code is quite rough and written for a sake of example.
I was looking for something very similar: a QML autocomplete component built around QML TextField, rather than the lower-level, more flexible but also more work intensive TextInput as in the question.
Since I could not find that, I implemented it. If anyone wants to use it: it's licensed under MIT and available as part of an application I am developing. You find the component in src/qml/AutoComplete.qml, and the application may serve as usage example. Features:
highlighting of autocompleted characters in bold, as in Google Search
Key bindings (navigating with arrow keys, Return / Enter, Esc to close completion box, Esc Esc to unfocus)
uses a simple QStringList as model for now, with the application showing how to update the model with live SQL database queries when the next key is pressed
heavily documented code, so it should be easy enough to adapt
Let me know if this is useful, I might then package it as a Qt QPM package or even try to make it mature enough to be added to the QML UI library KDE Kirigami.

qml + master-detail

I want to use qml with master-detail interface, but i don't know how to pass current item to detail view right way. The ListView in master view uses C++ model (add-on of QSQLTableModel, it's work fine) and I see two ways to pass item:
Create C++ classes with fields with static name like QSqlRecord field names and pass it to qml with w->rootContext()->setContextProperty() (w is QDeclarativeView *), but now i don't use any classes like this and can change my database and qml views without changing c++ code, I would like to save it
Create a lot of properties in any detail qml like
Rectangle {
id: mainRect
property alias myFieldName: txt_nominal.text
Column {
width: parent.width
Text {
id: txt_nominal
font.bold: true
}
}
}
and set this properties from c++ code by setting w->rootContext()->setContextProperty(record.fieldName(i),record.field(i).value()); (record - QSqlRecort at current row)
Is there any easier way to solve my problem?
PS The code I wrote above is not checked for accuracy, and is written to make it more clear what I mean
UPD
Maybe it will be useful for somebody, I discovered 3-rd way, rather, the modification of second - you can wrap fields into QVariantMap and pass only one object to qml. This is exactly what I wanted
in cpp:
QVariantMap testObject;
testObject["testField"]="first string from cpp";
testObject["testField2"]="second string from cpp";
rootContext()->setContextProperty("testObject",testObject);
in qml:
Text {
id: simpleTextt
text: testObject.testField
anchors.centerIn: parent
}
You could use the isCurrentItem property of the delegate to pass the data from ListView delegate to your details qml. That way you could get away without have to add additional c++ code. This is basically your second approach but without c++. You also do not need to add many properties as long as each of your QML elements that you want to change have an id.
If you have a number of different QML for different details views you would also have to use the Loader to load the appropriate details QML.
Just a toy example assuming that you have only one details template for all of your elements in the list (as mentioned above if that is not the case than you can use loader instead of detailsRect):
Rectangle {
width: 300; height: 400
Rectangle {
id: detailsRect
anchors.right: parent.right
width: 100
height: 500
color: "blue"
Text {
id: detailsText
text: ""
}
}
ListView {
id: list
anchors.fill: parent
model: 20
delegate: Rectangle {
color: ListView.isCurrentItem ? "red" : "green"
width: 40
height: 40
Text {
text: index
}
ListView.onIsCurrentItemChanged: {
if(ListView.isCurrentItem)
{
detailsRect.color = "yellow"
detailsText.text = index
}
}
MouseArea {
anchors.fill: parent
onClicked: {
list.currentIndex = index
}
}
}
}
}