I'm implementing an app with some CPU intensive code. This code must update the interface designed in QML to display its results.
The interface is made of a grid and a few blocks with some contents inside. The Grid is fully C++ implemented, and the Block is only QML.
Block.qml
import QtQuick 2.3
Item {
id: root
// lot of non-related stuff here.
Behavior on x {
NumberAnimation {
duration: 1000
easing.type: Easing.Linear
onRunningChanged: {
console.log("behavior on x");
}
}
}
Behavior on y {
NumberAnimation {
duration: 1000
easing.type: Easing.Linear
onRunningChanged: {
console.log("behavior on y");
}
}
}
}
Each of the blocks are created in C++ and positioned accordingly.
The problem is that some times I must move the blocks and I would like to trigger an animation. To accomplish this I've included the Behavior elements as you have probably noticed.
But when I change the position of the block from C++ code, the behavior is not triggered. The Block simply changes its position, without any animation.
The code I'm using to change the block position is:
void Grid::setBlockPosition(QQuickItem *block, unsigned i, unsigned j)
{
float x, y;
x = GRID_MARGIN_LEFT + j * this->_blockSize;
y = GRID_MARGIN_TOP + (GRID_ROWS - i - 1) * this->_blockSize;
block->setPosition(QPointF(x, y));
}
I've tried also changing x and then y instead of changing both at the same time, but it gave the same result.
How can I trigger the behavior on x and y properties from C++?
This is a good question. I took a look into the source of QQuickItem, and setX(), setY() and setPosition() all have very similar code paths, with all of them emitting geometryChanged(). You might think that that would be enough, but it turns out that it's not:
You should always use QObject::setProperty(), QQmlProperty or QMetaProperty::write() to change a QML property value, to ensure the QML engine is made aware of the property change. For example, say you have a custom type PushButton with a buttonText property that internally reflects the value of a m_buttonText member variable. Modifying the member variable directly [...] is not a good idea [...].
Since the value is changed directly, this bypasses Qt's meta-object system and the QML engine is not made aware of the property change. This means property bindings to buttonText would not be updated, and any onButtonTextChanged handlers would not be called.
Using an example specific to your code: if you change the x property of your block in QML, for example, the meta-object system is aware of the change and the Behavior gets notified of it and sets the property directly, in increments, until it reaches its target. When you set the property directly yourself, the Behavior has no idea that anything has changed, and so it does nothing.
So, you should call setProperty("x", x) and setProperty("y", y):
#include <QApplication>
#include <QtQuick>
#include <QtQml>
class Grid : public QQuickItem
{
Q_OBJECT
public:
Grid(QQuickItem *parent) {
setParentItem(parent);
}
public slots:
void addBlocks() {
QQmlComponent *blockComponent = new QQmlComponent(qmlEngine(parentItem()), "Block.qml");
for (int i = 0; i < 4; ++i) {
QQuickItem *block = qobject_cast<QQuickItem*>(blockComponent->create());
Q_ASSERT(!blockComponent->isError());
block->setParentItem(this);
block->setProperty("x", i * 100);
block->setProperty("y", i * 100);
mBlocks.append(block);
}
}
private:
QList<QQuickItem*> mBlocks;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QQuickItem *contentItem = engine.rootObjects().first()->property("contentItem").value<QQuickItem*>();
Grid *grid = new Grid(contentItem);
grid->addBlocks();
return a.exec();
}
#include "main.moc"
I've added this information to the detailed description of QQuickItem's documentation; hopefully others see this if they run into this problem.
Related
I have an int available via WiringPiI2C from an ADC at a max rate of 680 times per second. I'd like to sample and forward that value at regular intervals to various GUI objects, at 200-500Hz. I've tried a couple strategies for getting C++ data types into QML, and I always seem to fall just short.
I'm close to success with a custom Handler class and Q_PROPERTY macro, but the value only appears once; it does not update on screen. I can call the data using myData class all day in QML(console.log) or directly from the acquisition function in C++ (qDebug) and it updates flawlessly - I can watch the value as the ADC's analog input voltage varies ever-so-slightly. Every time I run the program, the single frozen value that shows on my screen is different than the last, thus real data must be making it to the screen. But why doesn't it update?
Do I somehow have to run the
emit pressureChanged
line and point the AppEngine
engine.rootContext()->setContextProperty("myData",myData.data());
with the same instance of DataHandler? How would I do this?
UPDATE Indeed, the emit line and AppEngine pointer must be within the same instance of DataHandler. But I'm failing to see how I can do both with one instance. Seems something is always out of scope. I tried running emit via a QTimer in main.qml and it works, but it performs terribly. I need much faster refresh rate than 5-6Hz and it slowed down the rest of the GUI functions a lot. Any help getting myData class's pressureChanged signal sent out to QML at 60+ Hz?
Application output
qrc:/main.qml:31: ReferenceError: myData is not defined
qrc:/main.qml:31: ReferenceError: myData is not defined
I'm sampling, why isn't anyone getting this???
I'm sampling, why isn't anyone getting this???
25771
I'm sampling, why isn't anyone getting this???
25686
I'm sampling, why isn't anyone getting this???
25752
I'm sampling, why isn't anyone getting this???
qml: 25763 <--- this is a manual button push on screen
I'm sampling, why isn't anyone getting this???
qml: 25702 <--- this is a manual button push on screen
I'm sampling, why isn't anyone getting this???
25751
Why does QML allow me to use myData to send data to console, but not allow me to use myData as a property to manipulate QML objects?
Is there a much easier way to get simple data types (in my case I will have two integers) into QML constantly updating various text objects on screen?
This post came close to helping me understand what's going on, and I suspect my problem is closely related to what's said there: that is, somehow my binding isn't valid and thus only calls the function DataHandler::getPressure 1 time.
I tried following this tutorial, but it's a different situation (creating a class object in QML from C++, all I want is to move 1 data type to QML), so I wasn't capable enough to apply it to my problem very well...
I've tried for days now... 3 ways of instantiating myData, tried with/without QScopedPointer, tried various ways of accessing myData within QML... I'm going out of my mind, please help! I appeal to the Gods of StackOverflow, for my stack doth truly overflow with ignorance...
datahandler.h
#ifndef DATAHANDLER_H
#define DATAHANDLER_H
#include <QObject>
#include <QPoint>
#include <QDebug>
class DataHandler : public QObject
{
Q_OBJECT
Q_PROPERTY(int pressure READ getPressure NOTIFY pressureChanged)
public:
explicit DataHandler(QObject *parent = 0);
void setupPressure();
int getPressureSample();
int getPressure();
void publishPressure();
signals:
void pressureChanged();
};
#endif // DATAHANDLER_H
important bits of
datahandler.cpp
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include "datahandler.h"
#define SAMPLES 10
DataHandler::DataHandler(QObject *parent) : QObject(parent)
{
}
int DataHandler::getPressure() {
int totalSum = 0;
for (int i = 0; i < SAMPLES; i++){
totalSum += getPressureSample();
delay(5); // Sampling at ~200Hz, the ADC itself maxes at 680Hz so don't sample faster than that.
}
qDebug() << "I'm sampling, why isn't anyone getting this update???";
return totalSum/SAMPLES;
}
void DataHandler::publishPressure() {
emit pressureChanged();
}
important bits of main.cpp
#include <QCursor>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include "functions.h"
#include "datahandler.h"
PI_THREAD(updatePressure)
{
DataHandler pressureData(new DataHandler);
while (true){
delay(500);
pressureData.publishPressure();
qDebug() << pressureData.getPressure();
}
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
wiringPiSetup();
DataHandler().setupPressure();
app.setOverrideCursor( QCursor( Qt::BlankCursor ) ); //Hide the cursor, no one needs that thing showing!
QScopedPointer<Functions> myFunctions(new Functions);
QScopedPointer<DataHandler> myData(new DataHandler);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
engine.rootContext()->setContextProperty("myFunctions",myFunctions.data());
engine.rootContext()->setContextProperty("myData",myData.data());
piThreadCreate(updatePressure);
return app.exec();
}
important bits of main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.4
import QtQuick.Extras 1.4
//DECLARATIVE CONTENT
Window {
id: myWindow
visible: true
width: 800
height: 480
title: qsTr("Hello World")
Item {
focus: true
Keys.onEscapePressed: myWindow.close()
Keys.onSpacePressed: console.log("HOW?")
}
MainForm {
id: root
anchors.fill: parent
property var shiftArray: 0
property var tumblerArray: noteSelector.array
Text {
id: testText
z: 9
anchors.fill: parent
color: "#FF0000"
text: myData.pressure
}
customPressureClick.onClicked: {
console.log(myData.pressure)
toggleCustomPressureState()
attemptCustomPressure()
}
}
}
EXTRA INFO
As you can see above, I created a PI_THREAD to constantly call the publishPressure function which is just my way of emitting the signal from outside the class. I suppose I could somehow use QTimer or some other method if that's what's screwing this up.
I used qDebug() to prove that the PI_THREAD is indeed calling publishPressure regularly, slowed it down to 500ms for sanity sake. I know the C++ data acquisition is successful because I've watched it crank out data to console at 500Hz. I know the emit function is reached, but maybe it is somehow not being executed?
I also found it very strange that QML bindings with Keys class worked fine in Window, but not in MainForm. I wonder if that somehow clues to the problem
Some problems I spot right off the bat:
When you call setupPressure, you do so on a temporary object. Even if it works, I doubt that's what you want to do.
You create another two DataHandlers (one in main, which you correctly set (albeit too late) as context property for QML. The other you create in your PI_THREAD... thing, which you then manipulate. This is not the object QML has registered.
You're also binding a string QML property (Text.text) to an int C++ Q_PROPERTY. I'm not sure this works correctly. In any case, I'd suggest trying to match types better.
Fix these problems, and you'll be on your way.
In another question you tell me to use QStateMachine.
I'm new to Qt and it's the first time i use the objects so I make a lot of logical mistake, so using QStateMachine it's a big problem...
It's the only way to do thath ? I try to explain my program:
I want to create a card's game and in the previous version I've used an old graphics library with this sequence of commands:
-> print cards on the scene
-> wait for a mouse input (with a do-while)
-> if(isMouseClick(WM_LBUTTONDOWN))
-> if(mouse position is on the first card)
-> select that card. So i wish to do the same thing with QGraphics.
In this way I tell the program:
-> print cards
-> wait for a mouse event
-> print the card that I've selected with that event.
Now I want to change the program graphics and I've introduced QGraphics.
I've created a scene and print all the objects "card" on it so now i want to tell the program:
-> print the object and wait the mouse input
-> if a card is to selected with the left clik
-> print that card in scene, wait 1/2 second and go ahead with the program
The problem is that I use a for 1 to 20 (I must run that 20 times in a match).
I've tried to lauch the program with a random G1 and COM play but the application freeze until the last execution of the for and I print on the scene only the last configuration of cards.
That is the reason because previously I said I want the program to stop...
It is possible to do without QStateMachine ?
Simply telling him: "pause", print this situation, wait for mouse and go ahead ?
The below is a complete example, 71 lines long, presented in the literate programming style. It is also available on github. The example consists of a qmake .pro file, not shown, and main.cpp, shown in the entirety below. The example has the following structure:
Header
Card Item
State Machine Behaviors
Main
Footer
Main
First, let's set up our scene:
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QGraphicsScene scene;
QGraphicsView view{&scene};
scene.addItem(new CardItem(0, 0, "A"));
scene.addItem(new CardItem(20, 0, "B"));
The state machine has three states:
QStateMachine machine;
QState s_idle{&machine}; // idle - no card selected
QState s_selected{&machine}; // card selected, waiting 1/2 second
QState s_ready{&machine}; // ready with card selected
machine.setInitialState(&s_idle);
We'll use helper functions to declaratively add behaviors to the machine. This isn't the only possible pattern, but it works and is fairly easy to apply. First, when any items are selected, the state changes from s_idle to s_selected:
on_selected(&s_idle, &scene, true, &s_selected);
Then, after a timeout, the state changes to s_ready:
on_delay(&s_selected, 500, &s_ready);
If the items are deselected, we go back to s_idle:
on_selected(&s_selected, &scene, false, &s_idle);
on_selected(&s_ready, &scene, false, &s_idle);
Since we don't have much better to do, we can simply deselect all items once the s_ready state has been entered. This makes it clear that the state was entered. Of course, it'll be immediately left since the selection is cleared, and we indicated above that s_idle is the state to be when no items are selected.
QObject::connect(&s_ready, &QState::entered, &scene, &QGraphicsScene::clearSelection);
We can now start the machine and run our application:
machine.start();
view.show();
return app.exec();
}
Note the minimal use of explicit dynamic memory allocation, and no manual memory management whatsoever.
Card Item
The CardItem class is a simple card graphics item. The item is selectable. It could also be movable. The interaction is handled automatically by the graphics view framework: you don't have to deal with interpreting mouse presses/drags/releases manually - at least not yet.
class CardItem : public QGraphicsObject {
Q_OBJECT
const QRect cardRect { 0, 0, 80, 120 };
QString m_text;
QRectF boundingRect() const Q_DECL_OVERRIDE { return cardRect; }
void paint(QPainter * p, const QStyleOptionGraphicsItem*, QWidget*) {
p->setRenderHint(QPainter::Antialiasing);
p->setPen(Qt::black);
p->setBrush(isSelected() ? Qt::gray : Qt::white);
p->drawRoundRect(cardRect.adjusted(0, 0, -1, -1), 10, 10);
p->setFont(QFont("Helvetica", 20));
p->drawText(cardRect.adjusted(3,3,-3,-3), m_text);
}
public:
CardItem(qreal x, qreal y, const QString & text) : m_text(text) {
moveBy(x, y);
setFlags(QGraphicsItem::ItemIsSelectable);
}
};
State Machine Behaviors
It is helpful to factor out the state machine behaviors into functions that can be used to declare the behaviors on a given state.
First, the delay - once the src state is entered, and a given number of millisconds elapses, the machine transitions to the destination state:
void on_delay(QState * src, int ms, QAbstractState * dst) {
auto timer = new QTimer(src);
timer->setSingleShot(true);
timer->setInterval(ms);
QObject::connect(src, &QState::entered, timer, static_cast<void (QTimer::*)()>(&QTimer::start));
QObject::connect(src, &QState::exited, timer, &QTimer::stop);
src->addTransition(timer, SIGNAL(timeout()), dst);
}
To intercept the selection signals, we'll need a helper class that emits a generic signal:
class SignalSource : public QObject {
Q_OBJECT
public:
Q_SIGNAL void sig();
SignalSource(QObject * parent = Q_NULLPTR) : QObject(parent) {}
};
We then leverage such universal signal source to describe the behavior of transitioning to the destination state when the given scene has a selection iff selected is true, or has no selection iff selected is false:
void on_selected(QState * src, QGraphicsScene * scene, bool selected, QAbstractState * dst) {
auto signalSource = new SignalSource(src);
QObject::connect(scene, &QGraphicsScene::selectionChanged, signalSource, [=] {
if (scene->selectedItems().isEmpty() == !selected) emit signalSource->sig();
});
src->addTransition(signalSource, SIGNAL(sig()), dst);
}
Header and Footer
The example begins with the following header:
// https://github.com/KubaO/stackoverflown/tree/master/questions/sm-cards-37656060
#include <QtWidgets>
It ends with the following footer, consisting of moc-generated implementations of the signals and object metadata for the SignalSource class.
#include "main.moc"
In qt you don't need to actively wait for an event (and usually shouldn't). Just subclass the event handling method of a widget which is part of the main interface.
For instance this is the code which use a subclass of a QGraphicsItem to change the game state. You could do the same with the scene itself, widgets, etc... but it should usually be like this.
void CardGameGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
if(event->button() == Qt::RightButton)
{
makeQuickChangesToGameState();
scene()->update(); //ask for a deffered ui update
}
QGraphicsItem::mousePressEvent(event);
}
even if you are somehow using a state machine, makeQuickChangesToGameState() should just trigger the machine state change, and go back asap.
I have defined macros for members that I want to access from a structure. I don't want to type cast to any another data type.
Example:
#define LABLE ui->lable->setText("NumbVal")
#define LABLE1 ui->lEditCaliCLFltBst->setText("UNDER PROCESS")
if (EditMode[LOC_04]!=0) { LABLE; } else { LABLE1; }
I want to access this LABLE variable from a structure. But what if I have a larger number of EditMode array entried - I can't make my program lenthy I just want to access through them through a structure.
What you are showing should be, at the very least, functions.
For example:
class Foo : public QWidget {
QScopedPointer<Ui::Foo> ui; // Don't use a raw pointer!
enum { LOC_04, LOC_END };
int m_editMode[LOC_END];
void lable1() { ui->lable->setText("NumbVal"); }
void lable2() { ui->lEditCaliCLFltBst->setText("UNDER PROCESS"); }
...
void f() {
...
if (EditMode[LOC_04]!=0) lable1(); else lable2();
...
}
}
With the little code you've shown, I infer that you have an interface that can be in various states, and those states are indicated through multiple user interface elements. This is what QStateMachine is for.
The example below demonstrates the following:
The use of a state machine to control the appearance of the user interface in each state.
The user interface has two parallel states: the m_editState and the m_boldState. The states are parallel, meaning that the state machine is in both of those states at the same time. Imagine this was in a text editor of some sort.
The edit state can be in one of two substates: m_edit1 and m_edit2. Similarly, the bold state can be in two states: m_boldOn and m_boldOff.
Clicking the buttons switches the states, and modifies the indications on the labels.
Concise setup of a user interface without using the UI designer.
Direct use of QObject members in a QObject, without explicit heap storage. Note the absence of a single explicit new and delete in the entire code. This shouldn't be an end unto itself, but it certainly helps avoid some pitfalls of unmanaged pointers, and it halves the number of heap allocations per each object. This pattens also works great when you put all the members in a pimpl class.
A reasonably concise way of repeating some code for elements of a constant list, created in place. This is pre-C++11 code.
Referring back to your original code, perhaps the EditMode could be represented by a set of states. If there are multiple aspects of the EditMode, they'd be represented by parallel states - perhaps each entry in EditMode would be a parallel state. Without knowing anything else about what you intend to achieve, it's hard to tell.
#include <QApplication>
#include <QLabel>
#include <QPushButton>
#include <QStateMachine>
#include <QGridLayout>
class Widget : public QWidget {
QGridLayout m_layout;
QLabel m_label1, m_label2, m_label3;
QPushButton m_button1, m_button2, m_button3;
QStateMachine m_machine;
QState m_editState, m_boldState, m_edit1, m_edit2, m_boldOn, m_boldOff;
public:
Widget(QWidget * parent = 0) : QWidget(parent), m_layout(this),
m_label1("--"), m_label2("--"), m_label3("--"),
m_button1("Edit State 1"), m_button2("Edit State 2"), m_button3("Toggle Bold State"),
m_editState(&m_machine), m_boldState(&m_machine),
m_edit1(&m_editState), m_edit2(&m_editState),
m_boldOn(&m_boldState), m_boldOff(&m_boldState)
{
m_layout.addWidget(&m_label1, 0, 0);
m_layout.addWidget(&m_label2, 0, 1);
m_layout.addWidget(&m_label3, 0, 2);
m_layout.addWidget(&m_button1, 1, 0);
m_layout.addWidget(&m_button2, 1, 1);
m_layout.addWidget(&m_button3, 1, 2);
m_edit1.assignProperty(&m_label1, "text", "Edit State 1");
m_edit2.assignProperty(&m_label2, "text", "Edit State 2");
m_boldOn.assignProperty(&m_label3, "text", "Bold On");
m_boldOff.assignProperty(&m_label3, "text", "Bold Off");
m_editState.setInitialState(&m_edit1);
m_boldState.setInitialState(&m_boldOff);
foreach (QState * s, QList<QState*>() << &m_edit1 << &m_edit2) {
s->addTransition(&m_button1, SIGNAL(clicked()), &m_edit1);
s->addTransition(&m_button2, SIGNAL(clicked()), &m_edit2);
}
m_boldOn.addTransition(&m_button3, SIGNAL(clicked()), &m_boldOff);
m_boldOff.addTransition(&m_button3, SIGNAL(clicked()), &m_boldOn);
m_machine.setGlobalRestorePolicy(QState::RestoreProperties);
m_machine.setChildMode(QState::ParallelStates);
m_machine.start();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
I have a beginner question. I'm trying to create a maximized QFrame with the following code but I'm receiving an error which says:
error C3867: 'QWidget::showMaximized': function call missing argument list; use '&QWidget::showMaximized' to create a pointer to member
Code:
class FrameWindow{
private:
QDesktopWidget *desktop;
QFrame frame_window;
QRect frame_rect;
public:
FrameWindow(QApplication& app){
desktop = app.desktop();
desktop->showMaximized;
frame_window.setWindowTitle("QT Trainning");
frame_window.show();
}
I'm totally beginner in C++, so what I'm missing please?
functions/methods generally need argument list, even an empty one -> object->method() Try using brackets.
You eventually want:
w->setWindowState(w->windowState() | Qt::WindowFullScreen);
Edit:
or as your solution:
w->setWindowState(w->windowState() | Qt::WindowMaximized);
In addition to what others have already noticed, it's completely counterproductive to pass either the current application or to hold a pointer to the desktop. The application pointer is always available via the global qApp macro. To get the desktop, simply use
qApp->desktop()
There is absolutely no reason to "cache" this value. Get it whenever you need it, that's all.
It'd be also more idiomatic to derive from the widget type, instead of holding it as a member. The code could be simplified as below. It is a complete, self-contained example.
#include <QFrame>
#include <QApplication>
class FrameWindow : public QFrame {
public:
FrameWindow(QWidget * parent = 0, Qt::WindowFlags * flags = 0) :
QFrame(parent, flags)
{
setWindowTitle("Qt Training");
setWindowState(windowState() | Qt::WindowMaximized);
}
};
int main(int argc, char ** argv) {
QApplication app(argc, argv);
FrameWindow fw; // constructor is called here
fw.show();
return app.exec();
// FrameWindow::~FrameWindow() destructor is called first before exiting
// QApplication::~QApplication() destructor is called next
}
I have an application which needs to draw on a pixel by pixel basis at a specified frame rate (simulating an old machine). One caveat is that the main machine engine runs in a background thread in order to ensure that the UI remains responsive and usable during simulation.
Currently, I am toying with using something like this:
class QVideo : public QWidget {
public:
QVideo(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {
}
void draw_frame(void *data) {
// render data into screen_image_
}
void start_frame() {
// do any pre-rendering prep work that needs to be done before
// each frame
}
void end_frame() {
update(); // force a paint event
}
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.drawImage(rect(), screen_image_, screen_image_.rect());
}
QImage screen_image_;
};
This is mostly effective, and surprisingly not very slow. However, there is an issue. The update function schedules a paintEvent, it may not hapen right away. In fact, a bunch of paintEvent's may get "combined" according to the Qt documentation.
The negative effect that I am seeing is that after a few minutes of simulation, the screen stops updating (image appears frozen though simulation is still running) until I do something that forces a screen update for example switching the window in and out of maximized.
I have experimented with using QTimer's and other similar mechanism to have the effect of the rendering being in the GUI thread so that I can force immediate updates, but this resulted in unacceptable performance issues.
Is there a better way to draw pixels onto a widget constantly at a fixed interval. Pure Qt solutions are preferred.
EDIT: Since some people choose to have an attitude instead of reading the whole question, I will clarify the issue. I cannot use QWidget::repaint because it has a limitation in that it must be called from the same thread as the event loop. Otherwise, no update occurs and instead I get qDebug messages such as these:
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
QPainter::begin: A paint device can only be painted by one painter at a time.
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent
EDIT: to demonstrate the issue I have created this simple example code:
QVideo.h
#include <QWidget>
#include <QPainter>
class QVideo : public QWidget {
Q_OBJECT;
public:
QVideo(QWidget *parent = 0, Qt::WindowFlags f = 0) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {
}
void draw_frame(void *data) {
// render data into screen_image_
// I am using fill here, but in the real thing I am rendering
// on a pixel by pixel basis
screen_image_.fill(rand());
}
void start_frame() {
// do any pre-rendering prep work that needs to be done before
// each frame
}
void end_frame() {
//update(); // force a paint event
repaint();
}
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.drawImage(rect(), screen_image_, screen_image_.rect());
}
QImage screen_image_;
};
main.cc:
#include <QApplication>
#include <QThread>
#include <cstdio>
#include "QVideo.h"
struct Thread : public QThread {
Thread(QVideo *v) : v_(v) {
}
void run() {
while(1) {
v_->start_frame();
v_->draw_frame(0); // contents doesn't matter for this example
v_->end_frame();
QThread::sleep(1);
}
}
QVideo *v_;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QVideo w;
w.show();
Thread t(&w);
t.start();
return app.exec();
}
I am definitely willing to explore options which don't use a temporary QImage to render. It is just the only class in Qt which seems to have a direct pixel writing interface.
Try emitting a signal from the thread to a slot in the event loop widget that calls repaint(), which will then execute right away. I am doing something like this in my graphing program, which executes the main calculations in one thread, then tells the widget when it is time to repaint() the data.
In similar cases what I did was still using a QTimer, but doing several simulation steps instead of just one. You can even make the program auto-tuning the number of simulation steps to be able to get whatever frames per seconds you like for the screen update.