Complex models and displaying data - c++

I'm just beginning to learn C++ and Qt Framework in particular and I already have a problem right there. The question is how do I create and display data which is not just a string but rather an object, which properties I can access and display. E.g I have a list of employees and I want to display a list which looks like this:
---------------------
John Smith
Salary: 50,230
---------------------
Max Mustermann
Salary: 67,000
---------------------
The goal is that each item in the list is clickable and opens a new window with the details. Also, the important part is that I can be able to style the properties differently.

Qt provide us model and view frameworks, it is pretty flexible.
You could save your data by "model", show the data of your "model" by "view"
and determine how to play your data by "delegate"
The codes of c++ is a little bit verbose, so I use qml from the document to express the idea
import QtQuick 2.1
import QtQuick.Window 2.1
import QtQuick.Controls 1.0
Rectangle {
width: 640; height: 480
//the new window
Window{
id: newWindow
width: 480; height:240
property string name: ""
property string salaryOne: ""
property string salaryTwo: ""
Rectangle{
anchors.fill: parent
Text{
id: theText
width:width; height: contentHeight
text: newWindow.name + "\nSalaryOne : " + newWindow.salaryOne + "\nSalaryTwo : " + newWindow.salaryTwo
}
Button {
id: closeWindowButton
anchors.centerIn: parent
text:"Close"
width: 98
tooltip:"Press me, to close this window again"
onClicked: newWindow.visible = false
}
}
}
ListModel {
id: salaryModel
ListElement {
name: "John Smith"
SalaryOne: 50
SalaryTwo: 230
}
ListElement {
name: "Max Mustermann"
SalaryOne: 67
SalaryTwo: 0
}
}
//this is the delegate, determine the way you want to show the data
Component {
id: salaryDelegate
Item {
width: 180; height: 40
Column {
Text { text: name }
Text { text: "Salary : " + SalaryOne + ", " + SalaryTwo }
}
MouseArea{
anchors.fill: parent
//set the value of the window and make it visible
onClicked: {
newWindow.name = model.name
newWindow.salaryOne = model.SalaryOne
newWindow.salaryTwo = model.SalaryTwo
newWindow.visible = true
view.currentIndex = index
}
}
}
}
ListView {
id: view
anchors.fill: parent
model: salaryModel
delegate: salaryDelegate
}
}
You could separate the window or ListView into different qml files, combine the power of c++ ,qml and javascript. Declarative langauge like qml is pretty good on handling UI.
c++ version
#include <memory>
#include <QApplication>
#include <QListView>
#include <QSplitter>
#include <QStandardItemModel>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel model(2, 1);
model.appendRow(new QStandardItem(QString("John Smith\nSalary: %1, %2\n").arg(50).arg(230)));
model.appendRow(new QStandardItem(QString("Max Mustermann\nSalary: %1, ").arg(67) + QString("000\n")));
QSplitter splitter;
QListView *list = new QListView(&splitter);
list->setModel(&model);
splitter.addWidget(list);
splitter.show();
return a.exec();
}
Enhance them by your need, c++ version also support delegate.
You could encapsulate the QListView and open a new window when the
user click on the index(you need QItemSelectionModel to detect which
item you selected).Before you can design higly customize UI,you have
to study a lot of the model and view frameworks of Qt. Since your case
are pretty simple, default QListView and QStandardItemModel is enough.
Supplement : How to detect which index you selected?
//the type of model_selected is QItemSelectionModel*
model_selected = list->selectionModel();
connect(model_selected, SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
this, SLOT(selection_changed(QItemSelection, QItemSelection)));
void imageWindow::selection_changed(QItemSelection, QItemSelection)
{
//do what you want
}

Related

Qt Create a QML Slider Sensitive to Touch Events

I am creating a game for touch screens that requires 2-4 players to each have access to a pair of slider controls. The problem is that the QML Slider control responds to touch as a mouse event and seizes the focus. Then only one player can access a single control at a time. I need multiple sliders to respond to touch events simultaneously. My question is how to do that?
With the help of a variety of stack overflow posts, I have been able to create my own answer that so far seems to work. I detail the answer below in the answer section to save other newbies like me the trouble.
There is a pure qml solution to this problem. The TouchSlider C++ object in my first answer (elsewhere in this thread) was unnecessary. Here I have modified the code to the TouchSlider qml code to eliminate references to touchslider (the TouchSlider C++ object).
TouchPoint.qml:
import QtQuick 2.0
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
property string sliderTitle
property real sliderMin
property real sliderMax
property real sliderVal
ColumnLayout{
id: column1
Label {
text: qsTr(sliderTitle)
font.pointSize: 10
}
Slider {
id: touchSlider1
minimumValue: sliderMin
maximumValue: sliderMax
orientation: Qt.Vertical
value: sliderVal
onValueChanged: function(){
sliderVal = Math.round(this.value);
labelSliderValue.text = qsTr(JSON.stringify(sliderVal));
}
}
Label {
id: labelSliderValue
text: qsTr(JSON.stringify(sliderVal))
font.pointSize: 10
}
function sliderSetValueFromTouch(position){
// Assume qs a vertical slider
var minvalue = touchSlider1.minimumValue;
var maxvalue = touchSlider1.maximumValue;
// Since this is a vertical slider, by assumption, get the height
var height = touchSlider1.height;
// Compute the new value based on position coordinate
var newvalue = (height-position)/height * (maxvalue-minvalue);
if (newvalue<minvalue) newvalue = minvalue;
if (newvalue>maxvalue) newvalue = maxvalue;
//qDebug() << newvalue;
// Set the value of the slider
touchSlider1.value = newvalue;
}
MultiPointTouchArea{
anchors.fill: touchSlider1
touchPoints: [
TouchPoint {
id: point1
onPressedChanged: function(){
if(pressed){
//console.log("pressed");
//console.log(touchslider.testStringReturn());
//touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
column1.sliderSetValueFromTouch(point1.y);
}
}
}
]
onTouchUpdated: function(){
//touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
column1.sliderSetValueFromTouch(point1.y);
}
}
}
}
The touchslider.h and touchslider.cpp files add no value.
I could not find a pure QML way to solve the problem but I wanted to minimize the use of C++. Using C++, I create an object TouchSlider and add it to my qml scene. The TouchSlider object has a simple function to update the value of a vertical slider according to a position argument. Then in the QML code, I add a MultiPointTouchArea on top of a regular slider and respond to the touch events by calling C++ function.
Here are all my files for a project called SliderPair.
SliderPair.pro:
QT += qml quick widgets
QT += quickcontrols2
QT += core
CONFIG += c++11
SOURCES += main.cpp \
touchslider.cpp
RESOURCES += \
qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH += qml
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
touchslider.h
DISTFILES +=
main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
// add following includes for exposing new class TouchSlider to QML
#include <QQmlEngine>
#include <QQmlContext>
#include "touchslider.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
//Create an object of type TouchSlider
//When a scoped pointer goes out of scope the object is deleted from memory. Good housekeeping:
QScopedPointer<TouchSlider> touchslider (new TouchSlider);
QQmlApplicationEngine engine;
engine.addImportPath(QStringLiteral("qml"));
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
//QML can now refer to the TouchSlider object using the handle "touchslider":
engine.rootContext()->setContextProperty("touchslider",touchslider.data());
return app.exec();
}
touchslider.h:
#ifndef TOUCHSLIDER_H
#define TOUCHSLIDER_H
#include <QObject>
#include <QDebug>
#include <QtQuickControls2>
class TouchSlider : public QObject
{
Q_OBJECT
public:
explicit TouchSlider(QObject *parent = 0);
//call Q_INVOKABLE macro to set up functions for QML
Q_INVOKABLE void testDebug(); //hello world from C++
Q_INVOKABLE QString testStringReturn(); //hello world to javascript
Q_INVOKABLE void sliderSetValueFromTouch(QQuickItem *qs,int position );//use touch event to set slider value
signals:
public slots:
};
#endif // TOUCHSLIDER_H
touchslider.cpp:
#include "touchslider.h"
TouchSlider::TouchSlider(QObject *parent) : QObject(parent)
{
}
void TouchSlider::testDebug()
{
qDebug() << "Hello from C++";
}
QString TouchSlider::testStringReturn()
{
QString message = "Hi from C++";
return message;
}
void TouchSlider::sliderSetValueFromTouch(QQuickItem *qs, int position)
{
// Assume qs a vertical slider
// Get its properties (its slider properties are accessible even though it is declared as QQuickItem)
// minimumValue and maximumValue are of type QVariant so we need to cast them as double
double minvalue = qs->property("minimumValue").value<double>();
double maxvalue = qs->property("maximumValue").value<double>();
// Since this is a vertical slider, by assumption, get the height
double height = qs->property("height").value<double>();
// Compute the new value based on position coordinate
double newvalue = (height-position)/height * (maxvalue-minvalue);
if (newvalue<minvalue) newvalue = minvalue;
if (newvalue>maxvalue) newvalue = maxvalue;
//qDebug() << newvalue;
// Set the value of the slider
qs->setProperty("value",newvalue);
}
TouchSlider.qml:
import QtQuick 2.0
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
property string sliderTitle
property real sliderMin
property real sliderMax
property real sliderVal
ColumnLayout{
Label {
text: qsTr(sliderTitle)
font.pointSize: 10
}
Slider {
id: touchSlider1
minimumValue: sliderMin
maximumValue: sliderMax
orientation: Qt.Vertical
value: sliderVal
onValueChanged: function(){
sliderVal = Math.round(this.value);
labelSliderValue.text = qsTr(JSON.stringify(sliderVal));
}
}
Label {
id: labelSliderValue
text: qsTr(JSON.stringify(sliderVal))
font.pointSize: 10
}
MultiPointTouchArea{
anchors.fill: touchSlider1
touchPoints: [
TouchPoint {
id: point1
onPressedChanged: function(){
if(pressed){
//console.log("pressed");
//console.log(touchslider.testStringReturn());
touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
}
}
}
]
onTouchUpdated: function(){
touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
}
}
}
}
PlayerControls.qml:
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
// These properties act as constants, useable outside this QML file
property string playerName
property real priceMin
property real priceMax
property real qualityMin
property real qualityMax
property real priceValue
property real qualityValue
property int sliderWidth
ColumnLayout{
id: columnLayout1
width: 640
height: 480
Layout.minimumWidth: 640
Layout.fillWidth: true
anchors.fill: parent
spacing: 10.2
Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
id: labelPlayer1
text: qsTr(playerName)
font.pointSize: 10
}
RowLayout{
ColumnLayout{
Label {
text: qsTr("")
font.pointSize: 10
width: 50
}
}
TouchSlider {
width: sliderWidth
sliderTitle: "Price"
sliderMin: priceMin
sliderMax: priceMax
sliderVal: priceValue
}
TouchSlider {
width: sliderWidth
sliderTitle: "Quality"
sliderMin: qualityMin
sliderMax: qualityMax
sliderVal: qualityValue
}
}
}
}
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("SliderPair Test")
Item {
PlayerControls{
playerName: "Player 1"
priceMin: 0
priceMax: 200
priceValue: 100
qualityMin: 0
qualityMax: 50
qualityValue: 25
sliderWidth: 200
}
}
}
The result should look like this:
On a touch screen like my Surface Pro, I can control each slider simultaneously with two fingers. Since Windows supports up to 10 simultaneous touches that should mean I can have 2-4 players without a problem. We shall see.

QtQuick, Dynamic Images and C++

I'm new to Qt, and from what I've read on qt-project.org and other places; QtQuick seems like an attractive option because of its ability to work on both pointer and touch based devices. My problem is getting it to work well with c++.
I decided to write a variant of Conway's Game of Life as a next step after "Hello World". I am thoroughly mystified as to how to get the "board" -- a [height][width][bytes-per-pixel] array of char -- integrated into the scene graph.
Basically, the process is that the "LifeBoard" iterates through its rules and updates the char*/image. I've got this simple QML:
:::QML
ApplicationWindow {
id: life_app_window
visible: true
title: qsTr("Life")
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Quit")
onTriggered: Qt.quit();
}
}
}
toolBar: ToolBar {
id: lifeToolBar;
ToolButton {
id: toolButtonQuit
text: qsTr("Quit")
onClicked: Qt.quit()
}
ToolButton {
id: toolButtonStop
text: qsTr("Stop")
enabled: false
//onClicked:
}
ToolButton {
id: toolButtonStart
text: qsTr("Start")
enabled: true
//onClicked: //Start life.
}
ToolButton {
id: toolButtonReset
text: qsTr("Stop")
// onClicked: //Reset life.
}
}
Flow {
id: flow1
anchors.fill: parent
//*****
// WHAT GOES HERE
//*****
}
statusBar: StatusBar {
enabled: false
Text {
// Get me from number of iterations
text: qsTr("Iterations.")
}
}
}
I want to image to come from a class with a api kinda like this:
class Life {
public:
QImage getImage() {}
// Or
char* getPixels(int h, int w, QImage::Format_ARGB8888) {}
}
I have no clue, and hours wading through tutorials did not help. How does one link a char* image in c++ to a ??? in QML so that the QML can start/stop the "Life" loop and so that the "Life" loop and update the char array and notify QML to redraw it?
Note: I've looked at subclassing QQuickImageProvider based on the info here. The problem with this approach is that I cannot see how to let c++ "drive" the on screen image. I wish to pass control from QML to c++ and let c++ tell QML when to update the display with the changed image. Is there a solution with this approach? Or another approach entirely.
First way to do that would be creating a Rectangle for each game pixel in QML, which might be fancy for a 8x8 board, but not for a 100x100 board, since you need to write the QML code manually for each pixel.
Thus I'd go for images created in C++ and exposed to QML. You call them via an image provider to allow asynchronous loading. Let Life do the logic only.
The image is called from QML like this:
Image {
id: board
source: "image://gameoflife/board"
height: 400
width: 400
}
Now gameoflife is the name of the image provider and board the so-called id you can use later.
Register gameoflife in you main.cpp
LifeImageProvider *lifeIP = new LifeImageProvider(life);
engine.addImageProvider("gameoflife", lifeIP);
where engine is your main QQmlApplicationEngine and life an instance of your Life game engine.
LifeImageProvider is your class to create pixeldata. Starts somehow like
class LifeImageProvider : public QQuickImageProvider
{
public:
LifeImageProvider(Life *myLifeEngine);
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize);
private:
Life *myLifeEngine_;
};
The important method is requestPixmap, which is called from QML. You need to implement it.
To refresh the game board when Life sends a stateChanged() signal, expose life as a global object to QML:
context->setContextProperty("life", &life);
You can bind the signal to QML
Image {
id: board
source: "image://gameoflife/board"
height: 400
width: 400
}
Connections {
target: life
onStateChanged: {
board.source = "image://gameoflife/board?" + Math.random()
// change URL to refresh image. Add random URL part to avoid caching
}
}
Just for fun, and at the risk of downvotes for a completely tangential answer, here's a GameOfLife implemented entirely in QML, just put it in a .qml file and run it with qmlscene. Works on Qt 5.3.0, and runs surprisingly (to me) fast on an old Core 2 Duo lappy. I'm sure it'll never be as fast/efficient as a C++ QQuickImageProvider based solution though, but it does make the point it's possible to do quite a lot in QML without resorting to C++.
import QtQuick 2.2
Rectangle {
id: main
width: 640
height: 640
color: '#000088'
Timer {
interval: 1000/60
running: true
repeat: true
onTriggered: {advance();display();}
}
Component {
id: cellComponent
Rectangle {
objectName: 'cell'
property int row: 0
property int col: 0
x: main.width/2+width*col
y: main.height/2+height*row
width: 5
height: 5
radius: 2
smooth: true
color: '#ffcc00'
}
}
property var cells: null
Component.onCompleted: {
cells=[[-1, 0],[-1, 1],[ 0,-1],[ 0, 0],[ 1, 0]];
display();
}
function display() {
// Just completely regenerate display field each frame
// TODO: might be nicer to do differential updates, would allow birth/death animations
// Nuke all previously displayed cells
for (var i=0;i<children.length;i++) {
if (children[i].objectName=='cell') {
children[i].destroy();
}
}
// Show current set of cells
for (var i=0;i<cells.length;i++) {
var c=cellComponent.createObject(
main,
{'row':cells[i][0],'col':cells[i][1]}
);
}
}
function advance() {
// Build a hash of the currently alive cells and a neighbour count (includes self)
var a=new Object;
var n=new Object;
for (var i=0;i<cells.length;i++) {
var p=cells[i]
var r=p[0];
var c=p[1];
if (!(r in a)) a[r]=new Object;
a[r][c]=1;
for (var dr=r-1;dr<=r+1;dr++) {
for (var dc=c-1;dc<=c+1;dc++) {
if (!(dr in n)) n[dr]=new Object;
if (!(dc in n[dr])) n[dr][dc]=0;
n[dr][dc]+=1;
}
}
}
// For all live cells, assess viability
var kill=[];
var stay=[];
for (var r in a) {
for (var c in a[r]) {
if (n[r][c]-1<2 || n[r][c]-1>3)
kill.push([Number(r),Number(c)]);
else
stay.push([Number(r),Number(c)]);
}
}
// For neighbours of live cells, assess potential for births
var born=[];
for (var r in n) {
for (var c in n[r]) {
if (!((r in a) && (c in a[r]))) {
if (n[r][c]==3)
born.push([Number(r),Number(c)]);
}
}
}
cells=stay.concat(born)
}
}
And for a pure QML version using GLSL (via a recursive QML ShaderEffect) to compute the Game of Life rules on GPU see here.

initialize error of ComboBox : Cannot read property 'constructor' of undefined

When I try to initialize the model of ComboBox, weird error pop out
test.pro
# Add more folders to ship with the application, here
folder_01.source = qml/androidTest
folder_01.target = qml
DEPLOYMENTFOLDERS = folder_01
#QMAKE_CXXFLAGS += -std=c++0x
CONFIG += c++11
QT += qml quick
# The .cpp file which was generated for your project. Feel free to hack it.
SOURCES += main.cpp
OTHER_FILES += \
qml/androidTest/main.qml
main.cpp
#include <QtGui/QGuiApplication>
#include <QQuickView>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
view.setSource(QUrl("/Users/Qt/program/experiment_apps_and_libs/test/qml/test/main.qml"));
view.show();
return app.exec();
}
main1.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
Rectangle {
width: 100
height: 62
ListModel{
id: modelA
}
ComboBox{
model: modelA
}
Component.onCompleted: {
modelA.append({"source" : "hhhh"})
}
}
error message
file:///C:/Qt/Qt5.2.0/5.2.0/mingw48_32/qml/QtQuick/Controls/ComboBox.qml:496: TypeError: Cannot read property 'constructor' of undefined
How could I fix this error?
Edit 1 :
I do not make an inline model because I want to separate the construction of the model and the ComboBox.It is hard to explain by my poor english, here is a simple example
TextCB
Column{
id: root
function appendUnitModel(units){
for(var i = 0; i != units.length; ++i){
unitModel.append({"unit": units[i]});
}
}
property alias inputText: input.text
SystemPalette{id: palette}
ListModel{
id: unitModel
}
Row{
spacing: 5
Text{
id: input
color: palette.highlight
height: root.height / 2
width: root.width * 0.6
focus: true
font.family: "Helvetica"
font.pixelSize: 16; font.bold: true
//Behavior on height{ NumberAnimation{duration: 500} }
MouseArea{
anchors.fill: parent
onClicked: {
showKeyBoard()
}
}
}
ComboBox{
id: unitSelector
model: unitModel
editable: true
height: input.height
width: root.width - input.width
}
}
}
main2.qml
TextCB{
id: inputAndClear
height: root.height * 0.2
width: root.width
Component.onCompleted: {
var units = ["meters", "decimeters", "centimeters",
"millimeters", "kilometers", "inches",
"feet", "yards", "miles", "nautical miles",
"cables"]
inputAndClear.appendUnitModel(units)
}
}
Separate the construction of the model and ComboBox, I could reuse it more easier.
Edit 2 :
For those who do not use QtCreator, here is the command line
/Users/yyyy/Qt5.2.0/5.2.0/clang_64/bin/qmake -makefile -d test.pro
make
cd androidTest.app/Contents/MacOS
lldb test
run
This commands are under OSX, you may need to tune it a little bit under different OS(ex : change lldb to gdb)
The problem is that you are trying to set the "source" property for the ListElement of the ListModel rather than 'text' what it expects. Respectively, if you change the following line:
modelA.append({"source" : "hhhh"})
to:
modelA.append({"text" : "hhhh"})
it will work. Alternatively, you could also add the following line to your ComboBox to get your custom role taking effect:
ComboBox {
model: modelA
textRole: "source"
}
See the ComboBox code for detailed explanation about it:
// No text role set, check whether model has a suitable role
// If 'text' is found, or there's only one role, pick that.
You also have other minor issues in your code, like hard coding the qml path for Windows as showed below. You could change to either "main.qml" simply, or use the resource system.
view.setSource(QUrl("/Users/Qt/program/experiment_apps_and_libs/test/qml/test/m‌​ain.qml"));
I personally changed it locally simply to:
view.setSource(QUrl("m‌​ain.qml"));
Also, you seem to have unnecessary qmake options for this experiment set up as follows:
CONFIG += c++11
and
QT += qml quick
For the latter, you do not need to specify qml explicitly.

Qt / QML set property from c++ class for GridView

First i created a new c++ class with a member function, which is giving back a string:
#ifndef TESTNAME_H
#define TESTNAME_H
#include <QObject>
#include <QString>
#include <QVariant>
class testname : public QObject
{
Q_OBJECT;
public:
testname();
Q_INVOKABLE QString getName();
};
#endif // TESTNAME_H
#include "testname.h"
testname::testname()
{
}
QString testname::getName() {
return "sometext";
}
I have a qml file with only a text in the middle, like this:
import QtQuick 1.1
Rectangle {
width: 360
height: 360
Text {
id: text1
anchors.centerIn: parent
text: testqml
font.pixelSize: 12
}
}
Notice that the property "text" is a variable named "testqml". This variable contains the string returned by the function of the class i showed above. The code for this is in the main.cpp:
#include <QApplication>
#include "qmlapplicationviewer.h"
#include <testname.h>
#include <QDeclarativeContext>
#include <QDebug>
Q_DECL_EXPORT int main(int argc, char *argv[])
{
QScopedPointer<QApplication> app(createApplication(argc, argv));
// Create instance of "testname"
testname *test = new testname;
QmlApplicationViewer viewer;
viewer.rootContext()->setContextProperty("testqml", test->getName());
viewer.addImportPath(QLatin1String("modules"));
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/classtest/main.qml"));
viewer.showExpanded();
return app->exec();
}
Using the setContextProperty-Function, the returned string is exposed to the qml file and is correctly shown in the running program.
But i'm actually intending to inherit a model for a GridView. So i created a Gridview with one list element in QML:
import QtQuick 1.1
Rectangle {
width: 360
height: 360
GridView {
id: grid_view1
anchors.centerIn: parent
width: 140
height: 140
cellHeight: 70
delegate: Item {
x: 5
height: 50
Column {
spacing: 5
Rectangle {
width: 40
height: 40
color: colorCode
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
x: 5
text: name
anchors.horizontalCenter: parent.horizontalCenter
font.bold: true
}
}
}
model: ListModel {
ListElement {
name: testqml
colorCode: "grey"
}
}
cellWidth: 70
}
}
The variable "testqml" is now in the "name" Field of the List, which is in the example a string. If i use a string "likethis", it is correctly displayed. But if i run my program (main.cpp and class are remaining unchanged) i get this error:
ListElement: cannot use script for property value
name: testqml
^
Now i'm stuck. I found an reported bug with a similar issue #QTBUG-16289, but i don't know how to solve my problem. Any ideas, how-tos, tutorials or something to solve my problem?
Thanks and regards :)
I ran your code and reproduced your error but what are you ultimately trying to do?
Your delegate indicates that you want to inject a number of model items with name and color from C++, otherwise, why use a GridView, correct?
If that is not the case then perhaps what follows will not be that useful, or perhaps some variant of it might be. So I went ahead and constructed an example of what I thought you might be trying to accomplish.
In summary, on my system, after creating an arbitrary number of model items (in this case 20), the scrollable GridView delegate (midway along the scroll range) looks like this:
As I said, it seems that you want to inject a number of QString items from a C++ model into a QML GridView, noting that using a GridView implies that you would like to have a number of items. In most cases you will want to inherit from a pre-defined Qt Model, which automatically takes care of several important details like keeping the QML views in-sync with the model, and automatically updating the views when items are removed or new ones are added.
With this in mind, the QAbstractListModel is a convenient Class from which to base your model (this is not the only option though, see the help files). At first glance, setting up this model can appear complicated, so I went ahead and defined a minimal version which I hope illustrates what you want to do.
Below is the code for the model (note: I put all of the code in the .h file so no .m file is needed). I also created some "Items" to inject into the model, for simplicity, a struct Item is used as defined below, but these could easily be instances of another suitably defined Class:
#include <QString>
#include <QColor>
#include <QDebug>
#include <QAbstractListModel>
// Create an Item so we have something to put in the model:
struct Item {
QString name;
QString color;
};
class testname : public QAbstractListModel
{
Q_OBJECT
public:
explicit testname(QObject *parent = 0) : QAbstractListModel(parent)
{
// Create some items and then add to the model:
int N = 20;
QStringList colorNames = QColor::colorNames();
Item* items = new Item[N];
for (int i = 0; i < N; i++) {
items[i].name = QString("item"+QString::number(i));
items[i].color = colorNames[i];
//qDebug() << items[i].name << "; " << items[i].color;
_model<<items[i];
}
}
// enum DataRoles for QAbstractListModel:
enum DataRoles {
NameRole = Qt::UserRole + 1,
ColorRole
};
// addData() method for QAbstractListModel:
void addData(const Item& entry) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
_model << entry;
endInsertRows();
}
// rowCount() method for QAbstractListModel:
int rowCount(const QModelIndex & parent = QModelIndex()) const {
return _model.count();
}
// data() required for QAbstractListModel:
QVariant data(const QModelIndex & index, int role) const {
if ( !index.isValid() || index.row() < 0 || index.row() >= _model.count() )
return QVariant();
Item modelEntry = _model[index.row()];
if (role == NameRole) {return modelEntry.name;}
if (role == ColorRole) {return modelEntry.color;}
return QVariant();
}
// roleNames() method for QAbstractListModel:
QHash<int,QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "Name";
roles[ColorRole] = "Color";
return roles;
}
private:
// Below are the model items:
QList<Item> _model;
};
Next is the QML code which uses the C++ model defined above and registered as "testqml" in main.cpp, and then defined through the property, model: in GridView.
Note that in the delegate that the Color and Name properties of the model are defined as role names in the class above (these could be any label you like). To help visualize what is going on, the model roles are very similar to the columns of a table, with the row entries corresponding to the model items:
import QtQuick 1.1
Rectangle {
width: 360
height: 360
/* ------------------- */
GridView {
id: grid_view1
anchors.centerIn: parent
width: 140; height: 140
cellHeight: 70
delegate: delegateItem
model: testqml // the C++ model is set here
cellWidth: 70;
}
/* ------------------- */
Component {
id: delegateItem
Item {
x: 5; height: 50
Column {
spacing: 5
Rectangle {
width: 40; height: 40;
color: Color // Note: this a role defined in the C++ model
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
x: 5;
text: Name // Note: this is another role defined in the C++ model
anchors.horizontalCenter: parent.horizontalCenter
font.bold: true
}
}
}
} // end delegateItem
} // end Rectangle
And then my main.cpp is nearly the same as yours, I'll go ahead and post it to avoid any confusion:
#include "qtquick1applicationviewer.h"
#include <QApplication>
#include "testname.h"
#include <QDeclarativeContext>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
testname *test = new testname();
QtQuick1ApplicationViewer viewer;
viewer.rootContext()->setContextProperty("testqml",test);
viewer.addImportPath(QLatin1String("modules"));
viewer.setOrientation(QtQuick1ApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/QMLSetProperty/main.qml"));
viewer.showExpanded();
return app.exec();
}
Hope this helps!

QML ListView multiselection

How can I select a few elements in the QML ListView and send its indices to C++ code?
Do something like that: if an element is clicked, set its property selected (or however you call it), and set in delegate that if selected is true, then it should be formatted differently. Plus add it to some list, to work with it.
I am pretty sure there is no way to make a QML ListView multi-selectable. Qt Declarative is focused on touch screen use and there is no meaningful way to multiselect in a pure touch UI.
i had the same issue and i found the best way to implement it, is to create a new role to the listview. Lets assume it is firstname and selected. you need to use both onCurrentIndexChanged and onClicked, because if you scroll, this will change the item but it is not a click. In both of them change the role selected into true, or adjust as it suits you, may be you don't need scroll to select and thus use only the onClicked. When clicked you can change the role selected into true
onCurrentIndexChanged:
{
mListModel.append({"firstName": newEntry,"selected":true})
}
and
onClicked:
{
mListModel.append({"firstName": newEntry,"selected":true})
}
then you may use a highlight in the deligate, this will change the color based on the state of the selected.
Here is a full code that is tested to work
//copyright: Dr. Sherif Omran
//licence: LPGL (not for commercial use)
import QtQuick 2.12
import QtQuick.Layouts 1.12
Item {
property string addnewitem:""
property int removeitemindex: -1
property string appenditemstring: ""
property int appenditemindx:-1
property int fontpoint: 20
property int radiuspoint: 14
property int spacingvalue: 0
property string delegate_color:"beige"
property string delegate_border_color:"yellowgreen"
property string highlight_color:"deeppink"
signal selectedvalueSignal (string iTemstring, string stateval)
property string sv: ""
property int indexcopy:0
id:lstmodelitem
width: parent.width
height: parent.height
ListModel {
id : mListModel
// ListElement {
// firstName : "John"
// }
}
ColumnLayout {
anchors.fill: parent
ListView{
id : mListViewId
model:mListModel
delegate :delegateId
Layout.fillWidth : true
Layout.fillHeight: true
clip: true
snapMode: ListView.SnapToItem //this stops the view at the boundary
spacing: spacingvalue
highlight: Rectangle
{
id: highlightid
width: parent.width
color: mListModel.selected==="true"?"blue":highlight_color
border.color: "beige"
z:3
opacity: 0.2
}
highlightRangeMode: ListView.StrictlyEnforceRange
highlightFollowsCurrentItem:true
onCurrentIndexChanged:
{
console.log("olistdynamic Indexchanged" + currentIndex)
mListViewId.currentIndex=currentIndex
lstmodelitem.selectedvalueSignal(currentIndex, mListModel.selected)
indexcopy=currentIndex
}
}
}
function getindex()
{
console.log("current index = " + indexcopy)
return mListViewId.currentIndex
}
function setindex(index)
{
//console.log("olistdynamic set index"+index)
mListViewId.currentIndex=index
}
function add2Item(newEntry,statev){
console.log("added item with value = " + newEntry + "state " + statev)
mListModel.append({"firstName": newEntry,"selected":statev})
}
function deleteItem(index){
mListModel.remove(index,1)
}
function appendIdem(index,valueEntry,newselectedsate)
{
console.log("append item")
mListModel.set(index,{"firstName": valueEntry,"selected":newselectedsate})
}
Component {
id : delegateId
Rectangle {
id : rectangleId
width : parent.width // Remember to specify these sizes or you'll have problems
height: textId.implicitHeight*1.2
color: selected==="true"?"blue":delegate_color
border.color: delegate_border_color
radius: radiuspoint
Text {
id : textId
anchors.centerIn: parent
text : firstName
font.pointSize: fontpoint
}
MouseArea {
anchors.fill: parent
onClicked: {
lstmodelitem.selectedvalueSignal(mListModel.firstName,mListModel.selected)
mListViewId.currentIndex=index
console.log("current index = " + index)
indexcopy=index
appendIdem(index,firstName,"true")
}
onClipChanged:
{
//console.log("a")
}
}
}
}
//if the item has been changed from null to text
onAddnewitemChanged: {
console.log("added item" + addnewitem)
add2Item(addnewitem)
}
//remove item with index
onRemoveitemindexChanged: {
console.log("remove item")
deleteItem(removeitemindex)
}
//to change the item, change the index first then the string
onAppenditemstringChanged: {
appendIdem(appenditemindx,appenditemstring)
}
}
You may try to get the ListItem's data and store it to an array on odd click and remove the ListItem's data from the array on even click. May be a simple workout, instead of creating a list of check box like items.