Simulate clicked QML element from QTest - c++

I'm playing around with QTest and want simulate a mouse click in my test on one of my QML elements that I have in my UI.
So in other words I would like to trigger the onClicked signal handler in my QML code from C++?
This is how my QML looks like:
import QtQuick 2.0
Rectangle
{
objectName: "MessageRectangle"
width: 360
height: 360
Text
{
objectName: "MessageText"
text: qsTr("Hello World")
anchors.centerIn: parent
}
Rectangle
{
id: simplebutton
objectName: "ChangeTextButton"
color: "grey"
width: 150; height: 75
Text
{
id: buttonLabel
anchors.centerIn: parent
text: "button label"
}
MouseArea
{
id: buttonMouseArea
objectName: "ChangeTextButtonMouseArea"
anchors.fill: parent
onClicked:
{
CppApi.setMessage( "Button Clicked" );
}
}
}
}
It is a bit hard to show all the different solutions that I have tried. But I have tried to connect an own class derived from QObject with the QML item and sending click signal to it:
QObject::connect( this, SIGNAL( clicked() ), pItem, SLOT( onClicked() ) );
emit clicked();
I have also tried to call the following way:
QMetaObject::invokeMethod( pButton, "pressedChanged", Qt::DirectConnection );
Where pButton is the QQuickItem which I get when I grab the object named ChangeTextButtonMouseArea.
I have found some similar questions here. But there seems to be something different with the onClicked, somehow. If I print out all the methods that are defined in the QMetaObject that I get from the QQuickItem, then I can't find the onClicked function or the clicked signal.
This answer is somewhat in the correct direction but the the signal I want to send is not created by myself:
C++ SIGNAL to QML SLOT in Qt
I also found this question:
How to simulate mouse clicks in QML?
But I only want to trigger a click event on a specific QML object. Do I really need to calculate the coordinates of my mouse click? Doing that seems a bit complicated since the QML items are only aware of the relative positions to their parents.
Am I trying something that is not possible? Or is the reason that I can't find any answers on the internet because the solution is so obvious?

Found out how to simulate the mouse click of an item. Posting it here in case someone else is trying to do this. It is actually quite simple. Use compute the coordinate to click and use QTest do the actual clicking.
void ClickItem( QQuickItem* pItem, QWindow* pRootWindow )
{
auto oPointF = pItem->mapToScene( QPoint( 0, 0 ) );
auto oPoint = oPointF.toPoint();
oPoint.rx() += pItem->width() / 2;
oPoint.ry() += pItem->height() / 2;
QTest::mouseClick( pRootWindow, Qt::LeftButton, Qt::NoModifier, oPoint );
}

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.

Propagating onPositionChanged events to background items in QtQuick 1.1

Background.qml
import QtQuick 1.1
Item {
MouseArea {
id: backgroundMouseArea
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
console.log("Background")
}
}
}
Foreground.qml
import QtQuick 1.1
Item {
Background {
width: 1920
height: 1080
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
console.log("Foreground")
[mouse.accepted = false] - Not working (as the docs say)
[backgroundMouseArea.onPositionChanged(mouse)] - Not working
}
}
}
I need to execute onPositionChanged event on both background and foreground items.
F.ex. for onPressed I would do it by setting mouse.accepted = false in the foreground item.
Can I call onPositionChanged of the background item manually? If yes, how do I do it?
I am not completely sure what you are trying to achieve here.
A MouseArea is meant to grab mouse events from hardware. If you really want to propagate mouse events to background from a different MouseArea, maybe what you actually want to do is give Background a simple property mousePosition instead of the MouseArea, and then set that position from the Foreground onPositionChanged handler.
Also, your Foreground code relies on an internal id parameter inside of Background. This smells really bad. It is often more useful to think about the "Public API" of the Background and Foreground "classes". If what I described above is really what you want to do, this is what it should look like IMHO:
// Background.qml
import QtQuick 1.1
Rectangle {
// an object with just x and y properties
// or the complete mouseevent, whatever you want
// Use variant for QtQuick 1/Qt4, var for QtQuick 2.0 / Qt5
property variant mousePosition
onMousePositionChanged: console.log(
"Background " + mousePosition.x + " " + mousePosition.y
)
}
//Foreground.qml
import QtQuick 1.1
Item {
// use only ids defined in the same file
// else, someone might change it and not know you use it
Background { id: background }
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
console.log("Foreground")
background.mousePosition = {x: mouse.x, y: mouse.y}
}
}
}
...........

QML QWidget container

I'm beginning with QML in Qt Creator and I like too much everything I've read about it but now I found a complication.
See following code:
BLCMainWidget::BLCMainWidget(QWidget *parent) : BLCBaseWidgetControler(parent) {
QQuickView view;
view.setSource(QUrl("qrc:///main.qml"));
QWidget *container = QWidget::createWindowContainer(&view);
QHBoxLayout *layout = new QHBoxLayout;
layout->setSpacing(10);
layout->setAlignment(Qt::AlignHCenter);
layout->setContentsMargins(1, 1, 1, 1);
parent->setStyleSheet("background:QColor(200,100,150);");
layout->addWidget(container);
parent->setLayout(layout);
}
Where parent is my QWidget on QMainWindow of my application, but this code not show my QQuickView container. Obviously the parent in question has a setCentralWidget signed in main() method and I'm already using that concept for non-QML widgets perfectly. How can I fix that to show my QML object containers?
My QML is just a simple concept example:
import QtQuick 2.1
Item {
id: box
width: 640
height: 480
Rectangle {
id: redSquare
width: 30; height: 30
anchors.top: parent.top; anchors.left: parent.left; anchors.margins: 10
color: "green"
Text { text: "!"; font.pixelSize: 16; anchors.centerIn: parent }
}
}
Thanks
If you are using a recent version of Qt, QWidget::createWindoContainer is depricated. Create a QQuickWidget instead, and use it a a normal QWidget.
Take a look at this: QML C++ Integration
and this: Interact QML from C++

QML signals connecting

I started writing application in QML (using QtQuick 1.1 with Qt 4.8.1) and I have a few questions about signals. In my project there are following files:
main.qml:
Rectangle {
signal sigExit()
width: 800
height: 600
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
sigExit();
Qt.quit();
}
}
Button
{
x: 10
y: parent.height-height-5
text: "someText"
}
}
Button.qml:
Rectangle {
signal buttonsig()
width: 60
//(...)
MouseArea
{
anchors.fill: parent
onClicked: buttonsig();
}
}
When I want to connect signal from main.qml to C++ slot, I do:
main.cpp :
QmlApplicationViewer viewer;
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/MyProject/main.qml"));
viewer.showExpanded();
MyClass* obj = new MyClass;
QObject* item = qobject_cast<QObject*>(viewer.rootObject());
QObject::connect(item, SIGNAL(sigExit()), obj, SLOT(onExitWindow()));
and it works. But what when I want to connect sigbutton() from Button.qml to C++ slot? It will be something like that?
QObject *rect = item->findChild<QObject*>("Button");
QObject::connect(rect, SIGNAL(buttonsig()), obj, SLOT(onExitWindow()));
And the second issue: how can I connect sigbutton() to main.qml (for example, I want to change position of my buttons after clicking them)?
You will also need to have the objectName property of your Button item if you want to access it :
Button {
id: myButton
objectName: "myButton"
x: 10
y: parent.height-height-5
text: "someText"
}
Now you can access it by :
QObject *rect = item->findChild<QObject*>("myButton");
Regarding the second question, you can use Connections object to connect buttonsig() to some QML signal handler in main.qml :
Rectangle {
signal sigExit()
width: 800
height: 600
Connections{
target: myButton
onButtonsig :
{
...
}
}
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
sigExit();
Qt.quit();
}
}
Button
{
id: myButton
x: 10
y: parent.height-height-5
text: "someText"
}
}
Note that the signal handler's name should be on<Signal> (First letter of signal letter Capital). Also Button should have an id to address it in Connections.
Accessing loaded qml elements, casting them and connecting their signals to your C++ slots is perferctly possible. But this method should be avoided in your production code.
See this warning from Qt docs.
So what's the way to call a C++ slot from qml side? You can register the object whose slot needs to be called with qml engine as a context property. Once registered, these context properties can be accessed anywhere from QML side.
Slots of objects registered as context properties can be called directly in your signal handler in QML example: onClicked:{<contextPropertyName>.<slotName>()}
Or, you can connect a QML signal with context property object's slot directly using Connections type. Please see this documentation
For details about registering context properties, please see Embedding C++ objects into QML with context properties.
If you want to see some examples, see my answers to these questions.
Qt Signals and Slots - nothing happens and Setting object type property in QML from C++

How to do an action in Qml every time a user touches the screen?

I am writing an application for an embedded linux touchscreen device using Qt and Qml.
I need to implement a lock screen that appears after 30 seconds of inactivity.
To do this I've implemented a timer in C++, that changes the state of the program after timeout.
Is it possible to refresh this timer on a global scale every time the user touches the screen, so that I do not have to call the timers' start() slot in every touchable element in my program?
I would also like to refresh this timer even when the user touches a part of the screen where there are no buttons/interactive elements.
Something like this main.qml:
Rectangle {
id: mainRect
width: 800
height: 480
//something like this (oversimplified) pseudo code:
onGlobalUserTouch {
timers.startLockTimer()
}
//end pseudocode
Loader {
id: mainLoader
anchors.fill: parent
source: "FirstPage.qml"
Behavior on opacity {PropertyAnimation{duration:250}}
onLoaded: secondLoader.source = ""
}
states:[
State {
name:"SecondPage"
when: (mainCppClass.state == PanelStates.SECOND_PAGE)
PropertyChanges {
target: mainLoader
source: "SecondPage.qml"
}
}
]
}
All I was able to find on the web was how to implement this in either iOS or Android.
If mainRect is your root component, you can just start your timer in its MouseArea.
// touch area of your root component
MouseArea {
anchors.fill: parent
onClicked: {
timers.startLockTimer();
}
// child component
Rectangle {
color: "yellow"
x: 50; y : 50
width: 100; height: 100
}
}
This does not override descendant MouseAreas. Thus, if you touch area where there are no buttons/interactive elements, your startLockTimer will be called.