How to use QStatemachine to influence a ListView? - c++

I have got this Projekt which uses a QStatemachine to manage the UI, where I want to add a customized List. The UI is supposed to be only manipulated by key events. As far as I understand I need a ListView on the qml side.
The delegate of ListView only reacts on mouse input or direct key input. But I have use the QStatemachine in C++ to operate it, since it is handling all key events for the UI.
What I want to happen when I press the right arrow key is vor the list to be shifted to the left.
(The currentItem is alway in the middle of the screen.)
So my ListView is looking like this at the Moment.
Component {
id:myDelegation
Item {
x: 50
width: 80
height: 60
Rectangle {
width: 60
height: 60
Text {
text: name
anchors.centerIn: parent
}
color: parent.ListView.isCurrentItem ? "red" : "steelblue";
scale: parent.ListView.isCurrentItem ? 1.5 : 1;
}
}
}
ListView {
id: listView1
x: 0
y: 50
width: 1920
height: 214
orientation: ListView.Horizontal
spacing: 4
model: TileList{}
delegate: myDelegation
preferredHighlightBegin: width / 2 - 10
preferredHighlightEnd: width / 2 + 10
highlightRangeMode: ListView.StrictlyEnforceRange
}
The c++ Statemachine is a QStatemachine which sends Signals to qml.
How do I bind the signals to the delegate of the Listview?

The easiest way is to just have the state machine set the "currentIndex"
A common pattern is to have an interface object that bridges between QML and the QStateMachine
class StateInterface : public QObject
{
Q_OBJECT
Q_PROPERTY(int currentIndex MEMBER m_currentIndex NOTIFY currentIndexChanged)
public:
explicit StateInterface(QObject *parent = 0);
signals:
void currentIndexChanged() const;
private:
int m_currentIndex;
};
An instance of that object is exposed to QML via the "context property" mechansism
StateInterface stateInterface;
qmlEngine->rootContext()->setContextProperty("_stateInterface", &stateInterface);
And used in QML as needed
ListView {
currentIndex: _stateInterface.currentIndex
}
The QStateMachine uses the same stateInterface object as a target for state property assignments
QState *beginState = new QState(stateMachine);
beginState->assignProperty(&stateInterface, "currentIndex", 0);
// and so on.
The StateInterface object can also provide slots to be used by QML to affect state changes. E.g.
public slots:
void triggerReset() { emit trigger reset(); }
signals:
void reset();
And the QStateMachine can, for example, then react to those signals wth a signal transition into the beginState
To summarize this technique:
the QStateMachine controls the application state
all state data that is of interest to QML is exposed via one or more interface objects
the QML side uses the state data in a nice, declarative way just like it would if it did the state handling itself

Step one - expose the state machine as a context property so that it is visible to qml:
engine.rootContext()->setContextProperty("SM", stateMachinePtr);
Step two - use a Connections element to establish a connection:
Connections {
target: SM
onSomeSignal: doSomeStuff()
}

Related

Binding loop problem in an application with QML&C++

I am doing a project, basically what I want to do is to synchronize a property theProperty between 2 boxes in 2 QML files(2 boxes both own that property), I bind theProperty to a C++ Q_PROPERTY, so by binding the 2 boxes to the same C++ Q_PROPERTY, the synchronization can be achieved.
Here are my codes in Box A and B. theProperty can be independently changed by Box A and B
Box_A {
id: box_A
// sth
Binding { target:box_A; property: "theProperty"; value:model.CppModel.theProperty }
onThePropertyChanged: {
model.CppModel.theProperty = theProperty
}
}
Box_B {
id: box_B
// sth
Binding { target:box_B; property: "theProperty"; value:model.CppModel.theProperty }
onThePropertyChanged: {
model.CppModel.theProperty = theProperty
}
}
In cpp:
Class Item: QObject{
Q_OBJECT
Q_PROPERTY(bool theProperty READ theProperty WRITE theProperty NOTIFY theProperty Changed)
//sth
}
Within Box_A and B, there is a mouse area by which the theProperty can be changed:
MouseArea{
onClicked: theProperty=!theProperty
}
The problem is that once I change theProperty in either box A or B, the qt creator complains that loop binding detected for value: model.CppModel.theProperty at the other side, is there a way of walking around this problem?
Why don't you just do:
Box_A {
id: box_A
// sth
theProperty:model.CppModel.theProperty
}
Did that not work for you?

Callback on each QML Video frame

I have a QML QuickControls 2 application with a Video component/control. I want to create a C++ callback to handle each frame in the video. The C++ callback function will process each frame, ie, find the edges in the image/frame and return that edge image for the UI to display.
How can I hook all this up? Ie, somehow tell the QML to call a c++ callback on each frame?
Video {
id: video
fillMode: VideoOutput.PreserveAspectFit
anchors.fill : parent
source: "file:///D:/cards.mp4"
muted: true
focus: true
Keys.onSpacePressed: video.playbackState == MediaPlayer.PlayingState ? video.pause() : video.play()
Keys.onLeftPressed: video.seek(video.position - 5000)
Keys.onRightPressed: video.seek(video.position + 5000)
}
My callback class, not sure if correct:
class ImageProcessor : public QObject
{
Q_OBJECT
public:
explicit ImageProcessor(QObject *parent = nullptr);
Q_INVOKABLE void processImage(QString va);
signals:
public slots:
};
You can create a VideoOutput from the video:
Rectangle {
width: 800
height: 600
color: "black"
MediaPlayer {
id: player
source: "file://video.webm"
autoPlay: true
}
VideoOutput {
id: videoOutput
source: player
anchors.fill: parent
}
}
You can add filters to the VideoOutput. For example here a faceRecognitionFilter:
VideoOutput {
...
filters: [ faceRecognitionFilter ]
}
In the C++ implemtation of the filter you can reach the frame:
QVideoFrame FaceRecogFilterRunnable::run(QVideoFrame *input, const QVideoSurfaceFormat &surfaceFormat, RunFlags flags)
{
// Convert the input into a suitable OpenCV image format, then run e.g. cv::CascadeClassifier,
// and finally store the list of rectangles into a QObject exposing a 'rects' property.
...
return *input;
}
You can collect some info here:
http://doc.qt.io/qt-5/qml-qtmultimedia-videooutput.html
http://blog.qt.io/blog/2015/03/20/introducing-video-filters-in-qt-multimedia/

Emitting signal not updating the values in GUI

I'm trying to make a very basic timetable app just to learn the basics of Qt. I've researched this quite a bit and cant seem to get an answer to my problem, I emit the signal when I change the value of my subject but the emitting signal does not update the GUI. It definitely is changing the value in the code but the QML GUI does not update it during run time.
I have a class called Day with the following Q_PROPERTY's:
Q_PROPERTY(Period *getPeriod READ getPeriod WRITE setPeriod NOTIFY periodChanged)
Q_PROPERTY(QString getDay READ getDay WRITE setDay NOTIFY dayChanged)
and a member to hold the periods
Period *m_period = new Period[10]; //Of type Period (my other class) which holds my subject string
I also have getters and setters for the days and periods(as seen in the Q_PROPERTY) and these two for setting/getting the subject:
Q_INVOKABLE QString getSubject(int t){
return m_period[t].getSub();
};
Q_INVOKABLE void setSubject(int t, QString subj){
m_period[t].setSub(subj);
emit periodChanged();
};
With the following signals:
void periodChanged();
void dayChanged();
I also have a class called Period with the following Q_PROPERTY:
Q_PROPERTY(QString getSub READ getSub WRITE setSub NOTIFY subChanged)
and a member to hold the subject names:
QString subject;
My Period class hold the functions called in day to actually change the QString subject:
QString getSub(){
return subject;
};
void setSub(QString sub){
subject = sub;
emit subChanged();
};
With the following signal:
void subChanged();
So surely when the subject gets changed using setSubject() in my QML, shouldn't it be emitting the periodChanged() and the subChanged() signals, which should update the GUI? I thought it would but it doesn't seem to be working.
For reference, this is basically how I implemented it in QML:
Label { text: monday.getSubject(2) } //How I display the Subject, the parameter being the period number
Button{
text: "Enter"
onClicked:{
monday.setSubject(2, "RANDOM_TEXT") //How I set the subject at run time, with the first argument being the period number and second the text I want to change it to
}
Here are the main and class files:
main.cpp
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qmlRegisterType<Day>("Timetable", 1,0, "Day");
qmlRegisterType<Period>("Timetable", 1,0, "Period");
QQmlApplicationEngine engine;
Day monday;
engine.rootContext()->setContextProperty("monday", &monday);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QQmlComponent component(&engine, QUrl::fromLocalFile("main.qml"));
component.create();
return app.exec();
}
Day.h
class Day : public QObject
{
Q_OBJECT
Q_PROPERTY(Period *getPeriod READ getPeriod WRITE setPeriod NOTIFY periodChanged)
private:
Period *m_period = new Period[10];
public:
Day(QObject *parent = 0);
~Day();
Period* getPeriod();
Q_INVOKABLE void setPeriod(Period *p);
Q_INVOKABLE QString getSubject(int t){
return m_period[t].getSub();
};
Q_INVOKABLE void setSubject(int t, QString subj){
m_period[t].setSub(subj);
emit periodChanged();
};
signals:
void periodChanged();
};
Period.h
class Period: public QObject
{
Q_OBJECT
Q_PROPERTY(QString getSub READ getSub WRITE setSub NOTIFY subChanged)
private:
QString subject;
public:
Period(QObject *parent = 0);
~Period();
QString getSub(){
return subject;
};
void setSub(QString sub){
subject = sub;
emit subChanged();
};
signals:
void subChanged();
};
main.qml
ApplicationWindow {
id: mainWindow
visible: true
title: "Timetable App"
property int margin: 11
width: mainLayout.implicitWidth + 2 * margin
height: mainLayout.implicitHeight + 2 * margin
minimumWidth: 800
minimumHeight: 600
ColumnLayout {
id: mainLayout
anchors.fill: parent
anchors.margins: margin
GroupBox{
id: timetable
title: "Timetable"
Layout.fillWidth: true
Layout.fillHeight: true
GridLayout {
id: gridLayout
rows: 11
flow: GridLayout.TopToBottom
anchors.fill: parent
Layout.fillWidth: true
Layout.fillHeight: true
Label { }
Label { text: "8:00" }
Label { text: ...} // Added Labels for times from 8:00 to 17:00
Label { text: "Monday" }
Label { text: monday.getSubject(0) }
Label { text: monday.getSubject(1) }
Label { text: ...} // Added Labels to display subjects for monday at different times, also did the same for the rest of the week
}
}
RowLayout{
Button{
text: "Enter"
onClicked:{
monday.setSubject(1, "RANDOM_TEXT") // Set the second period of monday to "RANDOM_TEXT"
console.log(monday.getSubject(1)) // To check if actually set
}
}
}
}
}
Your problem is that you are retrieving values via method calls in QML, not via property bindings.
A QML code like this should update
text: monday.getPeriod.getSub
Obviously calling properties "getSomething" is a bit weird.
Now, your m_period suggests that a single Day object will have more than one Period, which would suggest that you might want a list property instead of a single Period object. Something like
Q_PROPERTY(QList<Period*> periods READ getPeriods NOTIFY periodsChanged);
Using that from QML a bit like this
text: monday.periods[0].getSub
Or even with e.g. a Repeater
Label { text: "Monday" }
Repeater {
model: monday.periods
Label {
text: modelData.getSub
}
}
Not related to your update problem but also important to consider:
NOTIFY signals trigger binding updates, so you don't want to emit them unnecessarily. I.e. setters that emit those should first check if the new value is actually different than the old value and only emit if they are.

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.

Animate Binding Change in Qt

I'm trying to find a way to do a transition on a QML element, when a binding changes. Say you have a Text element, with the text property bound to something. What I want is when the data in the binding changes, the element fades out (Still displaying old data), switches and fades back in with the new data (the actual transition occurring while the element isn't visible.)
I've been searching everywhere for a way to do this but I can figure it out. I've tried using Qt Quick animations within QML, but the data itself changes before the animation runs, leaving the animation unnecessary. I've tried creating a custom QDeclarativeItem object that calls an animation within the QDeclarativeItem::paint() but I can't figure out how to get it to actually run.
I should note here that I know my bindings are working fine as the displayed data changes, I just can't get these animations to run at the proper time.
Here is what I tried with QML:
Text {
id: focusText
text: somedata
Behavior on text {
SequentialAnimation {
NumberAnimation { target: focusText; property: "opacity"; to: 0; duration: 500 }
NumberAnimation { target: focusText; property: "opacity"; to: 1; duration: 500 }
}
}
}
And here is what I tried in implementing a custom QDeclarativeItem:
// PAINTER
void AnimatedBinding::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
// Setup the pen
QPen pen(m_color, 2);
painter->setPen(pen);
painter->setOpacity(this->opacity());
// Draw the item
if (m_bindingType == QString("text")) {
QPropertyAnimation animation(this, "opacity");
animation.setDuration(1000);
animation.setStartValue(1);
if (drawn) {
animation.setStartValue(1);
animation.setEndValue(0);
animation.start();
} else drawn = true;
painter->drawText(boundingRect(), m_data.toString());
animation.setEndValue(0);
animation.start();
} else {
qCritical() << "Error unknown binding type!";
return;
}
}
But like I said, the animation that I start within the painter never actually fires.
Any tips? Anyone ever done this before? I've been banging my head on this for about a week.
How about doing it in qml only this ways :
Define a custom element of your own type, that behaves the way you want it to.
Use this element instead of traditional element to be animated.
eg. I have create a custom 'AnimatedText' type to have the fading in and fading out behavior on the text elements whenever text related to them changes.
File 1 : AnimatedText.qml
import QtQuick 1.0
Item
{
id: topParent
property string aText: ""
property string aTextColor: "black"
property int aTextFontSize: 10
property int aTextAnimationTime : 1000
Behavior on opacity { NumberAnimation { duration: aTextAnimationTime } }
onATextChanged:
{
topParent.opacity = 0
junkTimer.running = true
}
Timer
{
id: junkTimer
running: false
repeat: false
interval: aTextAnimationTime
onTriggered:
{
junkText.text = aText
topParent.opacity = 1
}
}
Text
{
id: junkText
anchors.centerIn: parent
text: ""
font.pixelSize: aTextFontSize
color: aTextColor
}
}
and in your main.qml
import QtQuick 1.0
Rectangle
{
id: topParent
width: 360
height: 360
AnimatedText
{
id: someText
anchors.centerIn: parent
aText: "Click Me to change!!!.."
aTextFontSize: 25
aTextColor: "green"
aTextAnimationTime: 500
}
MouseArea
{
anchors.fill: parent
onClicked:
{
someText.aText = "Some random junk"
}
}
}