I am trying to implement a Qt unit test and I would like to "click" a qtquick Button in QML from C++. I am successfully able to use QCompare on the properties of one of my QML objects in test_case3 but I can't figure out how to create a click event in test_case4. My Files are below.
tst_case_1.cpp
void tst_case_1::test_case3()
{
QScopedPointer<MouseMemory> mouse(new MouseMemory);
QQmlEngine engine;
engine.rootContext()->setContextProperty("mouse", mouse.data());
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
QObject *object = component.create();
QQuickItem *clear = object->findChild<QQuickItem*>("clear");
QVariant tmp = clear->property("text");
QCOMPARE(tmp.toString(), "Clear2");
delete object;
}
void tst_case_1::test_case4()
{
QScopedPointer<MouseMemory> mouse(new MouseMemory);
QQmlEngine engine;
engine.rootContext()->setContextProperty("mouse", mouse.data());
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
QObject *object = component.create();
QQuickItem *clear = object->findChild<QQuickItem*>("clear");
/* INSERT CODE HERE
Implement QML QQuickItem Button click
/*
delete object;
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: root
visible: true
width: 500
height: 500
Row {
id: tools
Button {
id: clear
objectName: "clear"
text: "Clear"
onClicked: {
canvas.clear()
}
}
Button {
id: save
text: "Save"
onClicked: {
mouse.save()
}
}
}
Canvas {
id: canvas
anchors.top: tools.bottom
width: 500
height: 500
property int lastX: 0
property int lastY: 0
function clear() {
var ctx = getContext("2d")
ctx.reset()
canvas.requestPaint()
mouse.clear()
}
onPaint: {
var ctx = getContext("2d")
ctx.lineWidth = 2
ctx.strokeStyle = color.red
ctx.beginPath()
ctx.moveTo(lastX, lastY)
lastX = area.mouseX
lastY = area.mouseY
ctx.lineTo(lastX, lastY)
ctx.stroke()
mouse.test()
mouse.add(lastX, lastY)
}
MouseArea {
id: area
anchors.fill: parent
onPressed: {
canvas.lastX = mouseX
canvas.lastY = mouseY
}
onPositionChanged: {
canvas.requestPaint()
}
}
}
}
Related
QML:
import QtQuick 2.2
import QtQuick.Controls 1.5
import QtQml.Models 2.2
import filesystem_browser 1.0
ApplicationWindow
{
visible: true
width: 640
height: 480
ItemSelectionModel
{
id: sel
// This model is comming from C++' class DisplayFileSystemModel.
model: treeViewModel
}
TreeView {
id: view
anchors.fill: parent
anchors.margins: 2 * 12
model: treeViewModel
rootIndex: root
selection: sel
TableViewColumn
{
title: "Name"
role: "display"
resizable: true
}
itemDelegate:
Rectangle
{
id: dd
color: "pink"
height: 20
Rectangle
{
height: 20; width: 40; color: "green"; anchors.right: parent.right
border.width: 1
}
border.width: 1
Text
{
anchors.verticalCenter: parent.verticalCenter
text: styleData.value
}
}
}
}
C++
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
QStandardItemModel model;
QStandardItem *item1 = new QStandardItem("File");
item1->appendRows({new QStandardItem("New"),
new QStandardItem("Open"),
new QStandardItem("Open Recent"),
new QStandardItem("Close"),
new QStandardItem("Save..."),
new QStandardItem("Save As..."),
new QStandardItem("Import Audio File..."),
new QStandardItem("Print")
});
QStandardItem *item3 = new QStandardItem("Edit");
item3->appendRows({new QStandardItem("Undo"),
new QStandardItem("Redo"),
new QStandardItem("Cut"),
new QStandardItem("Copy"),
new QStandardItem("Paste"),
new QStandardItem("Delete"),
new QStandardItem("Select All")
});
model.appendRow(item1);
model.appendRow(item3);
qmlRegisterUncreatableType<DisplayFileSystemModel>("filesystem_browser", 1, 0,
"FileSystemModel", "Cannot create");
engine.rootContext()->setContextProperty("treeViewModel", &model);
engine.rootContext()->setContextProperty("root", model.indexFromItem(model.invisibleRootItem()));
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
This results in:
I want to customize the child items and the parent item individually.
For example:
File //Parent item
New.. // Child item
Save.. // Child item
This current code puts the same customisation on the parent as well as the children.
My previous comment about using the row or column value was close, but incorrect. A quick look at the docs shows us that there is another property that gives us the depth of an item. So I think you can achieve what you want by simply doing something like this:
color: styleData.depth ? "blue" : "pink"
I have a QML TreeView with a QStandardItemModel and use a ItemSelectionModel to manage the selection. The ItemSelectionModel wants a QModelIndex for its select function. How can I obtain the QModelIndex of children in my view?
The tree looks like this:
file 1
task 1
task 2
file 2
task 1
I want to select task2 when I click on it (I can have a MouseArea in the delegate) (so that the TreeView highlights it), and in order to do this, I must call ItemSelectionModel.select with the QModelIndex of task 2. But I don'
t know how I can get the QModelIndex of task2.
QStandardItemModel is derived from QAbstractItemModel and therefore provides an index function:
virtual QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const
but to use this function I need to know the index of the parent. How can I get it from the view?
To obtain the child you must first have the parent, so in the case of your scheme you must obtain "file1" and for this you must obtain his parent, and this parent is the rootIndex of the TreeView, so the sequence is: rootIndex -> file1 -> task1.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QStandardItemModel>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QStandardItemModel model;
QStandardItem *item1 = new QStandardItem("file1");
item1->appendRows({new QStandardItem("task1"), new QStandardItem("task2")});
QStandardItem *item2 = new QStandardItem("file2");
item2->appendRows({new QStandardItem("task1")});
model.appendRow(item1);
model.appendRow(item2);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("tree_model", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQml.Models 2.11
Window {
visible: true
width: 640
height: 480
TreeView {
id: treeView
anchors.fill: parent
model: tree_model
selectionMode: SelectionMode.MultiSelection
selection: ItemSelectionModel {
id: ism
model: tree_model
}
TableViewColumn {
title: "Name"
role: "display"
width: 300
}
Component.onCompleted: {
expandAll()
var ix1 = tree_model.index(0, 0, treeView.rootIndex)
var ix = tree_model.index(0, 0, ix1)
ism.select(ix, ItemSelectionModel.Select)
}
}
// https://forum.qt.io/topic/75395/qml-treeview-expand-method-not-working
function expandAll() {
for(var i=0; i < tree_model.rowCount(); i++) {
var index = tree_model.index(i,0)
if(!treeView.isExpanded(index)) {
treeView.expand(index)
}
}
}
}
Update:
To get the index of the item pressed you must use styleData.index:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQml.Models 2.11
Window {
visible: true
width: 640
height: 480
TreeView {
id: treeView
anchors.fill: parent
model: tree_model
selectionMode: SelectionMode.MultiSelection
selection: ItemSelectionModel {
id: ism
model: tree_model
}
TableViewColumn {
title: "Name"
role: "display"
width: 300
}
itemDelegate: Item {
Text {
anchors.verticalCenter: parent.verticalCenter
color: styleData.textColor
elide: styleData.elideMode
text: styleData.value
}
MouseArea{
anchors.fill: parent
onClicked: {
var ix = tree_model.index(0, 0, styleData.index)
ism.select(ix, ItemSelectionModel.Select)
}
}
}
}
}
I have a QStandardItemModel which I display via a QML Table view.
Here is the model:
class mystandardmodel: public QStandardItemModel
{
public:
mystandardmodel();
enum Role {
role1=Qt::UserRole,
role2
};
explicit mystandardmodel(QObject * parent = 0): QStandardItemModel(parent){}
//explicit mystandardmodel( int rows, int columns, QObject * parent = 0 )
// : QStandardItemModel(rows, columns, parent){}
QHash<int, QByteArray> roleNames() const{
QHash<int, QByteArray> roles;
roles[role1] = "one";
roles[role2] = "two";
return roles;
}
};
and this is how the model is displayed using custom delegates:
TableView {
id: tableView2
x: 69
y: 316
width: 318
height: 150
TableViewColumn {
title: "Parameter Name"
role: "one"
}
TableViewColumn {
title: "Value"
role: "two"
delegate: myDelegate
}
model: myTestModel
}
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
sourceComponent: if(typeof(roleTwo)=='boolean') {
checkBoxDelegate}
else { stringDelegate}
}
}
Component {
id: checkBoxDelegate
CheckBox{text: roleTwo}
}
Component {
id: stringDelegate
TextEdit {text: roleTwo}
}
I populated the model like this:
mystandardmodel* mysmodel=new mystandardmodel(0);
QStandardItem* it = new QStandardItem();
it->setData("data1", mystandardmodel::role1);
it->setData(true, mystandardmodel::role2);
it->setCheckable(true);
it->setEditable(true);
mysmodel->appendRow(it);
QStandardItem* it2 = new QStandardItem();
it2->setData("data2",mystandardmodel::role1);
it2->setData("teststring",mystandardmodel::role2);
mysmodel->appendRow(it2);
How can I make the model editable, so that using the checkBox or editing the text is transfered back to the model?
Edit: I tried to follow the suggestion in In QML TableView when clicked edit a data (like excel) and use set model:
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
property int thisIndex: model.index
sourceComponent: if(typeof(roleTwo)=='boolean') {
checkBoxDelegate}
else { stringDelegate}
}
}
Component {
id: checkBoxDelegate
CheckBox{text: roleTwo
onCheckedChanged: {
myTestModel.setData(0,"two",false)
console.log('called',thisIndex)
}
}
}
Component {
id: stringDelegate
TextEdit {text: roleTwo
onEditingFinished: {
myTestModel.setData(thisIndex,"two",text)
console.log('called',thisIndex)
}
}
}
The index is OK, but it seems that it does not have an effect (I added a second TableView with the same model, but the data there does not get updated if I edit it in the first TableView)
You can directly set a value to model.two and that will automatically call setData with the correct role and index:
import QtQuick 2.10
import QtQuick.Controls 2.0 as QQC2
import QtQuick.Controls 1.4 as QQC1
import QtQuick.Layouts 1.3
QQC2.ApplicationWindow {
visible: true
width: 640
height: 480
ColumnLayout {
anchors.fill: parent
Repeater {
model: 2
QQC1.TableView {
Layout.fillWidth: true
Layout.fillHeight: true
QQC1.TableViewColumn {
title: "Parameter Name"
role: "one"
}
QQC1.TableViewColumn {
title: "Value"
role: "two"
delegate: Loader {
property var modelTwo: model.two
sourceComponent: typeof(model.two) ==='boolean' ? checkBoxDelegate : stringDelegate
function updateValue(value) {
model.two = value;
}
}
}
model: myModel
}
}
}
Component {
id: checkBoxDelegate
QQC1.CheckBox {
text: modelTwo
checked: modelTwo
onCheckedChanged: {
updateValue(checked);
checked = Qt.binding(function () { return modelTwo; }); // this is needed only in QQC1 to reenable the binding
}
}
}
Component {
id: stringDelegate
TextEdit {
text: modelTwo
onTextChanged: updateValue(text)
}
}
}
And if that's still too verbose and not enough declarative for you (it is for me), you can use something like the following, where most of the logic is in the Loader and the specifics delegates just inform what is the property where the value should be set and updated from :
delegate: Loader {
id: loader
sourceComponent: typeof(model.two) ==='boolean' ? checkBoxDelegate : stringDelegate
Binding {
target: loader.item
property: "editProperty"
value: model.two
}
Connections {
target: loader.item
onEditPropertyChanged: model.two = loader.item.editProperty
}
}
//...
Component {
id: checkBoxDelegate
QQC1.CheckBox {
id: checkbox
property alias editProperty: checkbox.checked
text: checked
}
}
Component {
id: stringDelegate
TextEdit {
id: textEdit
property alias editProperty: textEdit.finishedText // you can even use a custom property
property string finishedText
text: finishedText
onEditingFinished: finishedText = text
}
}
Using setData() could be an option, but it requires an integer value that indicates the role that is not accessible in QML, or rather is not elegant.
A better option is to create a new one that is Q_INVOKABLE. As the update is given in the view it is not necessary to notify it besides causing strange events.
to obtain the row we use the geometry and the rowAt() method of TableView.
The following is an example:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QStandardItemModel>
class MyStandardModel: public QStandardItemModel
{
Q_OBJECT
public:
enum Role {
role1=Qt::UserRole+1,
role2
};
using QStandardItemModel::QStandardItemModel;
QHash<int, QByteArray> roleNames() const{
QHash<int, QByteArray> roles;
roles[role1] = "one";
roles[role2] = "two";
return roles;
}
Q_INVOKABLE void updateValue(int row, QVariant value, const QString &roleName){
int role = roleNames().key(roleName.toUtf8());
QStandardItem *it = item(row);
if(it){
blockSignals(true);
it->setData(value, role);
Q_ASSERT(it->data(role)==value);
blockSignals(false);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
MyStandardModel model;
for(int i=0; i< 10; i++){
auto item = new QStandardItem;
item->setData(QString("data1 %1").arg(i), MyStandardModel::role1);
if(i%2 == 0)
item->setData(true, MyStandardModel::role2);
else {
item->setData(QString("data2 %1").arg(i), MyStandardModel::role2);
}
model.appendRow(item);
}
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myTestModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TableView {
id: tableView2
anchors.fill: parent
TableViewColumn {
title: "Parameter Name"
role: "one"
}
TableViewColumn {
title: "Value"
role: "two"
delegate: myDelegate
}
model: myTestModel
}
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
sourceComponent: typeof(roleTwo)=='boolean'? checkBoxDelegate: stringDelegate
}
}
Component {
id: checkBoxDelegate
CheckBox{
checked: roleTwo
onCheckedChanged:{
var pos = mapToGlobal(0, 0)
var p = tableView2.mapFromGlobal(pos.x, pos.y)
var row = tableView2.rowAt(p.x, p.y)
if(row >= 0)
myTestModel.updateValue(tableView2.row, checked, "two")
}
}
}
Component {
id: stringDelegate
TextField {
text: roleTwo
onEditingFinished: {
var pos = mapToGlobal(0, 0)
var p = tableView2.mapFromGlobal(pos.x, pos.y)
var row = tableView2.rowAt(p.x, p.y)
if(row >= 0)
myTestModel.updateValue(tableView2.row, text, "two")
}
}
}
}
The complete example can be found in the following link.
I am trying to use QML with C++ in QT, but for now unsuccessfully. I cannot access my QML element from the C++ code using rootObjects() function. What am I doing wrong?
qml part:
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: window
visible: true
width: 640
height: 520
title: qsTr("My app")
Item {
anchors.fill: parent
Rectangle {
id: rectangle1
x: 0
y: 0
width: 640
height: 370
color: "#ffffff"
}
Button {
id: startButton
x: 325
y: 425
text: qsTr("Start")
}
}
}
C++ Part:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
QObject *rootObject = engine.rootObjects().first();
qDebug() << rootObject->objectName();//prints ""
QObject *qmlObject = rootObject->findChild<QObject*>("window");// or "startButton"
//qDebug() << qmlObject->objectName(); //app fails, because window was not found
QList<QObject *> allQObjects = rootObject->findChildren<QObject *>();
for(int i=0;i< allQObjects.length();++i)
{
qDebug() << allQObjects[i]->objectName(); //prints everytime ""
}
qDebug() << "len: " << allPQObjects.length(); //prints 132
return app.exec();
}
At first: If you do not set a object name there will be no!
QML:
Rectangle { id : frame; objectName : "objFrame" color : "blue" }
Qt:
QObject *pRootObject = m_pQmlView->rootObject();
QObject *pobjFrame = m_pRootObject->findChild<QObject *>("objFrame");
The other way arround:
Qt:
m_pQmlView->rootContext()->setContextProperty( "_view", this );
QML:
Component.onCompleted: {
/********************** Connections ***************************/
// connect signal MyView::retranslate() with slot retranslate
_view.retranslate.connect(retranslate)
}
Need to add objectname to QML
ApplicationWindow {
id: window
objectName: "window"
...
}
Given my thoughts below am I barking up the wrong tree? Or provided the information below am I misusing Qt API to get the error in the title?
I am trying to modify the sample at http://doc.qt.io/qt-5/qtquick-scenegraph-openglunderqml-example.html to work with the default QtQuick project generated with Qt Creator 3.3.0 (opensource)
Based on Qt 5.4.0 (GCC 4.6.1, 64 bit).
After looking through the code the first thing that stands out to me is:
The samples main.cpp uses:
qmlRegisterType<Squircle>("OpenGLUnderQML", 1, 0, "Squircle");
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.setSource(QUrl("qrc:///scenegraph/openglunderqml/main.qml"));
view.show();
With some renaming my main.cpp uses
qmlRegisterType<MainScreen>("OpenGLUnderQML", 1, 0, "MainScreen");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
I am not sure if the difference in using a QQmlApplicationEngine over a QuickView could be causing my error:
QQmlApplicationEngine failed to load component qrc:/main.qml:23
Invalid attached object assignment
Where my main.qml looks like:
import QtQuick 2.4
import QtQuick.Window 2.2
import OpenGLUnderQML 1.0
import "qmlmodel"
Window {
id: mainWindow
width: 800
height: 600
visible: true
color: "black"
title: "Army Calculator"
objectName: "mainWindow"
ListView {
id: mainListView
anchors.fill: parent
objectName: "mainListView"
}
MainScreen {
SequentialAnimation on DeltaT {
NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
loops: Animation.Infinite
running: true
}
}
}
and the sample uses:
import QtQuick 2.0
import OpenGLUnderQML 1.0
Item {
width: 320
height: 480
Squircle {
SequentialAnimation on t {
NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
loops: Animation.Infinite
running: true
}
}
Rectangle {
color: Qt.rgba(1, 1, 1, 0.7)
radius: 10
border.width: 1
border.color: "white"
anchors.fill: label
anchors.margins: -10
}
Text {
id: label
color: "black"
wrapMode: Text.WordWrap
text: "The background here is a squircle rendered with raw OpenGL using the 'beforeRender()' signal in QQuickWindow. This text label and its border is rendered using QML"
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: 20
}
}
As per request in comment below MainScreen.h is
#ifndef MAINSCREEN_H
#define MAINSCREEN_H
#include <QQuickItem>
class MainScreenRenderer;
class QQuickWindow;
class MainScreen : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(qreal DeltaT READ getDeltaT WRITE setDeltaT NOTIFY deltaTChanged)
public:
MainScreen();
~MainScreen();
qreal getDeltaT() const;
void setDeltaT(qreal deltaT);
signals:
void deltaTChanged();
public slots:
void sync();
void cleanup();
private slots:
void handleWindowChanged(QQuickWindow *win);
private:
qreal m_DeltaT;
MainScreenRenderer *m_Renderer;
};
#endif // MAINSCREEN_H
Property name should start with lowercase letter. You need to change DeltaT to deltaT.
MainScreen.h
Q_PROPERTY(qreal deltaT READ getDeltaT WRITE setDeltaT NOTIFY deltaTChanged)
main.qml
MainScreen {
SequentialAnimation on deltaT {
}
}