Sending C++ signals to QML - c++

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.

Related

Connecting signals from QML to Qt: var to QList<QString>

I am trying to connect a signal from QML to a SLOT from Qt. The signal passes a QList variable.
QML:
property var cases: ["A", "B", "C"]
signal casesClicked(list cases)
Qt:
d->m_view->setSource(QUrl("qrc:/qml/main.qml"));
d->m_item = d->m_view->rootObject();
QObject::connect(d->m_item, SIGNAL(casesClicked(QList<QString>)), this, SLOT(onCasesClicked(QList<QString>)));
The issue I am having is that I don't know how to declare QList from the QML side, so it is taken directly. If I declare it with:
signal casesClicked(var cases)
then, the signal is not connected, and I if I declare it as a list or an Array it says "Invalid signal parameter type: list/Array"
Any tip? I don't have any problems with single int, bool or string. Thanks,
I do not think it is appropriate to make the connection on the C ++ side since when compiling it does not know the signal created in QML, a possible solution is to make the connection on the QML side. In the following I show an example
main.cpp
#include <QGuiApplication>
#include <QQuickView>
#include <QQmlContext>
#include <QDebug>
class Test: public QObject{
QQuickView *view;
Q_OBJECT
public:
Test(QObject *parent=Q_NULLPTR):QObject(parent)
{
view = new QQuickView;
view->rootContext()->setContextProperty("Test", this);
view->setSource(QUrl("qrc:/main.qml"));
view->show();
}
public slots:
void onCasesClicked(QVariantList cases){
qDebug()<<cases;
}
};
int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
Test test;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
Item {
id: it
visible: true
width: 640
height: 480
signal casesClicked(var cases)
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: it.casesClicked(["B", 1, "D"])
}
Connections{
target: it
onCasesClicked: Test.onCasesClicked(cases)
}
// or
// onCasesClicked: Test.onCasesClicked(cases)
// if the connection is made in the same item
}
Output:
(QVariant(QString, "B"), QVariant(int, 1), QVariant(QString, "D"))
IIRC JS arrays are converted to QVariantList in C++, so try using that instead.
EDIT:
Ok, it appears that although the above is the default automatic conversion, it doesn't work for some reason when the connection is made from C++.
In this scenario the signal value is converted to a QVariant instead, which can directly be converted to a QStringList which is convenient. However that approach will not work if you have a JS array with different data types in it, which is perfectly legal and quite frequently used in JS.
QVariantList will still work if you pass the JS array as a parameter to a C++ function call thou. So you can iterate each value and handle a "polymorphic" array.
Whether or not it is recommended practice to create connections from to QML objects from C++ depends on the usage scenario, overall, I'd say that it isn't. The recommended practice is to expose the C++ core interface to QML and do the connections from there and to avoid touching any QML from C++ whatsoever. But there are a few corner cases where exceptions can be made.

QML OpenGL plugin not redrawing at 60Hz

The Situation
My company has a QML-based application which displays some content using a custom OpenGL-based render plugin (MyGame). This plugin has a few critical needs:
To be able to effect changes in the renderer in response to QML-based signals.
(e.g. change the position of an object rendered by the game)
To only process these changes at a specific spot in MyGame's redraw loop.
(This is very important; MyGame is very sensitive about when changes are allowed.)
To have the plugin redraw at 60Hz (at least).
The Problem
The code we have right now honors (1) and (2), but fails (3); the plugin does not get visually updated consistently. (The updates are erratic, at an estimated 5-10Hz.) I believe that the plugin we have created—based on QQuickFramebufferObject—is not taking proper advantage of how Qt/QML intended the scene graph to be updated.
How can I re-structure my plugin so that I get all three of the above?
The Code
Overview:
The plugin creates a QQuickFramebufferObject (MyPlugin) and a QQuickFramebufferObject::Renderer (MyRenderer).
When MyRenderer::render() is called it calls MyGame::Redraw() itself, and then calls update().
MyGame::Redraw() does what it needs to, and at the right spot where changes can be accepted, emits a timeToMakeChanges QML signal on MyPlugin.
QML listens for the onTimeToMakeChanges signal and invokes methods on the plugin that affect MyGame.
To workaround the problem of low-frequency visual updates, I've found that if I overlay a QML Canvas over my plugin and redraw the canvas frequently using a Timer, my plugin starts to get visually updated at what appears to be around 60Hz. Clearly this is a gross hack.
Following is a summary of the code setup. Please forgive missing/incorrect code; I'm trying to distill thousands of lines of glue code down to the essentials for this question.
MyPlugin.h
#include <QOpenGLFramebufferObject>
#include <QQuickFramebufferObject>
class MyPlugin : public QQuickFramebufferObject {
Q_OBJECT
public:
MyPlugin();
virtual ~MyPlugin();
virtual QQuickFramebufferObject::Renderer* createRenderer() const;
signals:
void timeToMakeChanges();
public slots:
void makeChanges(QVariant inValue);
void HandleWindowChanged(QQuickWindow *inWindow);
private:
MyGame* GetGame() { ... }
};
MyPlugin.cpp
#include "MyPlugin.h"
#include <MyGame.h>
// ******************************************************************
class MyRenderer:
public QObject,
public QQuickFramebufferObject::Renderer,
protected QOpenGLFunctions
{
Q_OBJECT
public:
virtual void render();
private:
static void RequestGameChanges();
};
void MyRenderer::render() {
if ( !m_Initialized ) {
QOpenGLFramebufferObject *theFbo = this->framebufferObject();
InitializeGl( theFbo ); // Not shown
m_MyGame = &MyGame::Create();
m_MyGame->RegisterCallback(
reinterpret_cast<qml_Function>(MyRenderer::RequestGameChanges)
);
m_Initialized = true;
}
m_MyGame->RestoreState();
m_MyGame->Redraw();
m_MyGame->SaveState();
m_PluginItem->window()->resetOpenGLState();
// Tell QML that we want to render again as soon as possible
update();
}
// This gets invoked in the middle of m_MyGame->Redraw()
void MyRenderer::RequestGameChanges() {
emit m_PluginItem->timeToMakeChanges();
}
// ******************************************************************
MyPlugin::MyPlugin() {
setMirrorVertically(true);
connect(
this, SIGNAL(windowChanged(QQuickWindow*)),
this, SLOT(HandleWindowChanged(QQuickWindow*))
);
}
void MyPlugin::HandleWindowChanged(QQuickWindow *inWindow) {
inWindow->setClearBeforeRendering(false);
}
void MyPlugin::makeChanges(QVariant inValue) {
MyGame *theGame = GetGame();
// Send the requested changes to theGame
}
QQuickFramebufferObject::Renderer* MyPlugin::createRenderer() const {
m_Renderer = new MyRenderer( *this );
}
MyApp.qml
import MyPlugin 1.0
Window {
MyPlugin {
property var queuedUpChanges: ([])
onSomeOtherSignal: queueUpChangesToMake();
onTimeToMakeChanges: makeChanges( queuedUpChanges );
}
Canvas { id:hack }
Timer {
interval:10; running:true; repeat:true
onTriggered: hack.changeWhatYouShow();
}
}
Bonus Points
The main question is "How do I modify my code so that I get 60Hz updates?" However, as seen in the QML, the setup above requires me to queue up all changes in QML so that they are able to be applied during the right spot in the MyGame::Render().
Ideally, I'd prefer to write QML without timeToMakeChanges, like:
MyPlugin {
onSomeOtherSignal: makeChanges( ... );
}
If there's a way to accomplish this (other than queuing up the changes in C++ instead)—perhaps something related to synchronize() I'd love to know about it.
I'd make a timer in QML that calls the makeChanges regularly. But store all the state in MyPlugin. Then, in Renderer::synchronize(), copy from MyPlugin to MyRenderer, so it can be used by the MyGame.
(although, I wouldn't do any gamelogic-related calculations in QML ever in the first place)

Qt: onChildrenChanged not launched when QML components are created at runtime from C++ code

For a project, I need to create QML components at runtime from C++.
My general architecture is the following:
Project1:
Engine.h
Engine.cpp
CustObject.h
CustObject.cpp
Plugin.h
Plugin.cpp
Dummy.qml
Project2:
main.cpp
main.qml
What I want to do is instantiate Engine.cpp as a QML object (possible since I registered it in the Plugin class and made it available to Project2) and then create dynamically CustObject instances (which are also registered to be used by Project2) from Engine. In the end I want that if I write:
ApplicationWindow{
id: window
visible: true
Engine{
id: eng1
CustObject{
id: custObj1
}
}
}
This will be the same as writing something like
ApplicationWindow {
id: window
visible: true
Button {
text: "add new child"
onClicked: {
console.log("QML: Number children before", eng1.children.length);
eng1.addNewChildren();
console.log("QML: Number children after", eng1.children.length);
}
}
Engine{
id: eng1
onChildrenChanged: console.log("Changed")
}
}
And I should see that the number of children is incremented and onChildrenChanged from eng1 should be launched.
The problem is that neither the number of children is incremented, nor the signal onChildrenChanged is launched.
I had also the other problem that in order to add a children to the parent, eng1 in my case, I used the function QQmlComponent(QQmlEngine *engine, const QUrl &url, QObject *parent = 0) of QQMLComponent class. But I cannot find a way to transform my CustObject class into a QUrl since it is not a .qml file.
Therefore I first tried to add a dummy qml object called: Dummy.qml instead of CustObject object. Dummy.qml looks like this:
import QtQuick 2.0
Item {
property int nb: 1
}
The code of my Engine class looks like this:
Engine.h:
#ifndef ENGINE_H
#define ENGINE_H
#include <QQuickItem>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QQmlContext>
class Engine : public QQuickItem{
Q_OBJECT
public:
explicit Engine(QQuickItem* parent = 0);
virtual ~Engine();
Q_INVOKABLE QObject* addNewChildren();
};
#endif // ENGINE_H
Engine.cpp:
#include "Engine.h"
Engine::Engine(QQuickItem* parent) :
QQuickItem(parent)
{
}
Engine::~Engine(){ }
QObject* Engine::addNewChildren(){
qDebug() << "CPP: Number children before " << this->children().size();
QObject* parentEntity = this;
QQmlComponent* childrenEntity;
QQmlComponent component(qmlEngine(this), QUrl("qrc:///src/Dummy.qml"));
QQuickItem *childrenItem = qobject_cast<QQuickItem*>(component.create());
QQmlEngine::setObjectOwnership(childrenItem, QQmlEngine::CppOwnership);
childrenItem->setParent(parentEntity);
childrenItem->setProperty("nb", 2);
qDebug() << "CPP: Number children after" << this->children().size();
//qDebug() << "Property value:" << QQmlProperty::read(childrenItem, "nb").toInt();
return childrenItem;
}
But my output when I run main.qml is the following:
qml: QML: Number children before 0
CPP: Number children before 0
CPP: Number children after 1
qml: QML: Number children after 0
And I commented the line corresponding to QQmlProperty::read due to the following error: "incomplete type 'QQmlProperty' used in nested name specifier
qDebug() << "Property value:" << QQmlProperty::read(childrenItem, "nb").toInt();"
^
I have therefore the following questions:
Why is the number of children incrementation not seen from qml (but visible from cpp)?
Why is onChildrenChanged not launched from qml?
How can I add dynamically CustObject class (which is visible as a qml object from Project2 point of view since it is registered)
instead of Dummy.qml?
How to read a property of a dynamically added object in C++ just after its creation (i.e. how to use QQMlProperty::read)?
Thank you a lot in advance for any help you could give me!
Why is the number of children incrementation not seen from qml (but visible from cpp)?
QML doesn't use QObject::children(), instead it uses QQuickItem::childItems(). Yes, that's right, there are two different list of children, one from QObject, and one from QQuickItem. Both serve different purposes: The one from QObject is mainly for memory management (children get deleted when parent gets deleted), while the one from QQuickItem is for the 'visual hierachy', e.g. children get drawn on top of their parent. More details are available in the docs.
Why is onChildrenChanged not launched from qml?
Because the onChildrenChanged is only emitted when QQuickItem::childItems() changes, which it doesn't. Call setParentItem() in addition to setParent() to fix that.
How can I add dynamically CustObject class (which is visible as a qml object from Project2 point of view since it is registered) instead of Dummy.qml?
By simply creating the object yourself and setting the parent and parentItem. There is no need to use QQmlComponent here.
QObject childrenItem = new CustObject();
childrenItem->setParent(parentEntity);
childrenItem->setParentItem(parentEntity);
How to read a property of a dynamically added object in C++ just after its creation (i.e. how to use QQMlProperty::read)?
Calling QQuickItem::childItems() should do the trick, no need to read a property. FWIW, there probably was an #include <QQmlProperty> missing in the code that didn't work.

Triggering QML animation for property changes

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.

Draw pixel based graphics to a QWidget

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.