How to run animation with blocked UI thread in Qt Widgets app? - qt

In QML you can use Animator type to "animate on the scene graph's rendering thread even when the UI thread is blocked."
How can I achieve the same thing for Qt Widgets?
Basically, I want something like:
1) start loading screen / splash-screen
2) start GUI-blocking operation
3) stop loading screen / splash-screen
It is not possible to move the ui-blocking operation to a separate thread (Widgets are being created). I cannot modify this part.
I tried QQuickWidget and QQuickView containing the splash-screen scene with an Animator inside but it didn't work - they got blocked as well.
EDIT: In the separate thread I read the file containing the UI description. Then I recursively create hundreds of Widgets (including QQuickWidgets, QLabels for images, Web views etc.).
Point of the question was to see if there is a "workaround" for that (e.g. displaying the aforementioned QML scene in some separate window with an own event loop). Unfortunately at this point not much more can be done about the overall design of the above.

Probably the widgets you're creating do too much work. You have to specify exactly how many widgets you're creating, and how. Show some example code. In general, the GUI thread is for cooperative multitasking - if you have something that "blocks", break it down into tiny chunks. For example, suppose that you're processing some XML or json file to build the UI. You could have that task do it one widget at a time, and be invoked each time the event loop is about to block (i.e. use a zero-duration "timer" and invert control).
You should also do the maximum possible amount of work outside of the gui thread. I.e. the UI description should be read and converted to an efficient representation that encapsulates the work to be done in the main thread. This conversion has to be done asynchronously.
The simplest way to accomplish that is to encapsulate each widget's creation in a lambda that refers to some context object. Such a lambda would have the signature [...](BatchContext &ctx). The vector of those lambdas would be kept by the CreationContext object as well:
class BatchContext : public QObject {
Q_OBJECT
public:
using Op = std::function<void(CreationContext &)>;
using QObject::QObject;
// useful for Op to keep track of where things go
void push(QWidget *w) { m_stack.push_back(w); }
QWidget *pop() { return m_stack.isEmpty() ? nullptr : m_stack.takeLast(); }
QWidget *top() const { return m_stack.isEmpty() ? nullptr : m_stack.last(); }
int stackSize() const { return m_stack.size(); }
bool stackEmpty() const { return m_stack.isEmpty(); }
Q_SLOT void startExec() {
if (m_execIndex < ops.size())
m_execTimer.start(0, this);
}
template <typename F>
void addOp(F &&op) { m_ops.push_back(std::forward<F>(op)); }
...
private:
QVector<Op> m_ops;
QVector<QWidget *> m_stack;
QBasicTimer m_execTimer;
int m_execIndex = 0;
void timerEvent(QTimerEvent *ev) override {
if (ev->timerId() == m_execTimer.timerId())
if (!exec())
m_execTimer.stop();
}
/// Does a unit of work, returns true if more work is available
bool exec() {
if (m_execIndex < m_ops.size())
m_ops.at(m_execIndex++)(*this);
return m_execIndex < ops.size();
}
};
After the context is created asynchronously, it can be passed to the main thread where you can then invoke startExec() and the widgets will be created one-at-a-time. The stack is but an example of how you might implement one aspect of widget creation process - the tracking of what widget is the "current parent".

Related

How to paint sequential image in efficient way in QQuickPaintedItem

For some reason, I needs to wrap opencv VideoCapture in a class which will be used in Qt Quick.
There are two classes, one is Camera, other is CameraView. CameraView inheritd from QQuickPaintedItem.
Camera class will get image periodically. It achieved by QObject::startTimer(int interval). (e.g. If fps of the webcam is 30, the timer interval is 1000 / 30 - 8, 8 is deviation of time). Once Camera has get image, it notifies CameraView to repaint by calling CameraView::Update().
And in CameraView::paint(QPainter *), CameraView will get an copy of image from Camera class and paints this image by call QPainter::drawImage(...).
I got some problems in process of coding:
I try to replace time event with QThread to get image from camera periodically. When I call CameraView::Update() in QThread, CameraView doesn't repaint. What is the problem?
In my laptop, when I make the CameraView painting the image in fullscreen, I found one python program slow down. Is another way to paint image with lower cost and efficient?
How can I efficiently update QML item based on QQuickPaintedItem C++
class? I delegated some preprocesssing to dedicated thread instead of
a timer on UI thread and it does not update the image in QML UI anymore.
It is a mandatory to trigger UI update from UI thread in Qt including QML. Make that CameraView to expose public slot updateImage.
class CameraView : public QQuickPaintedItem
{
Q_OBJECT
Q_DISABLE_COPY(CameraView)
public:
CameraView(QQuickItem* parent = nullptr);
public slots:
void updateImage(const QImage&);
protected:
QImage m_image;
};
CameraView should implement updateImage and paint like that:
void CameraView::updateImage(const QImage& image)
{
m_imageThumb = image; // does shallow copy of image data
update(); // triggers actual update
}
void CameraView::paint(QPainter* painter)
{
painter->drawImage(this->boundingRect(), m_image);
}
ClassOpenCvOnWorkerThread should start its worker thread and expose signalUiUpdate:
OpenCvOnWorkerThread::OpenCvOnWorkerThread()
{
this->moveToThread(&m_workerThread);
// the below will allow communication between threads
connect(this, SIGNAL(signalUiUpdate(QImage)), m_cameraView, SLOT(updateImage(QImage)));
m_workerThread.start();
}
void OpenCvOnWorkerThread::cvRead()
{
QImage image;
// OpenCV details available in your code
// cv::read
// make QImage from frame
// deliver QImage to another thread
emit signalUiUpdate(image);
}
UPDATE: In my own code for similar QML output from "camera" thread I also take care of handling the UI thread stall when it is unable to process video frames so the signal sender knows when not to post video frames. But that worth another question. Or this whole example can be reimplemented without signal and slot but with condition variable.

deleteLater vs removeItem on a QGraphicsObject

Although I'm using Qt from Python via PyQt, this question is equally applicable to pure Qt, just the syntax is a bit different, the issue is the same:
When we want to dispose of a QGraphicsItem object in our scene, we call scene.removeItem(item). When we want to dispose of a QGraphicsObject object in our scene, we call scene.removeItem(item) because it derives from QGraphicsItem, but we ALSO call item.deleteLater() because it derives from QObject and that is the recommended way of disposing of QObjects (so that pending signals to and from the item are properly handled).
PROBLEM is that slots in the object item may can get called AFTER the item has been removed from the scene, due to how deleteLater() functions. This requires that we test for self.scene() being None in slots. But this is error prone as it is easy to forget to do that, and forgetting this leads to exception if slot is called.
Another approach is to not call deleteLater() before removing the item from the scene, but this requires manually disconnecting the item from other objects. This has similar disadvantage to testing for self.scene() being None in slots, and its easy to forgot to disconnect a slot.
A better way of mitigating this source of error (if there are no hidden gotchas) would be to NOT call scene.removeItem(item) when item is a QGraphicsObject, and JUST call its deleteLater(): it seems, based on some simple tests, that the scene automatically removes item from its list when it eventually gets destroyed. HOWEVER, I can't find any Qt documentation that states this, and I might have just been lucky; perhaps in a more realistic scenario I would get a memory leak or a crash.
So I'm leaning towards calling deleteLater() without calling removeItem() when item is a QGraphicsObject, do you think this is safe?
Below is the source code for the QGraphicsItem destructor (taken from qt-5.7/qtbase/src/widgets/graphicsview/qgraphicsitem.cpp). As you can see, it does a whole load of cleanup, as well as calling the scene's internal removeItemHelper function (which is also called by removeItem). Thus, it seems well designed to handle removal via deletion.
QGraphicsItem::~QGraphicsItem()
{
if (d_ptr->isObject) {
QGraphicsObject *o = static_cast<QGraphicsObject *>(this);
QObjectPrivate *p = QObjectPrivate::get(o);
p->wasDeleted = true;
if (p->declarativeData) {
if (static_cast<QAbstractDeclarativeDataImpl*>(p->declarativeData)->ownedByQml1) {
if (QAbstractDeclarativeData::destroyed_qml1)
QAbstractDeclarativeData::destroyed_qml1(p->declarativeData, o);
} else {
if (QAbstractDeclarativeData::destroyed)
QAbstractDeclarativeData::destroyed(p->declarativeData, o);
}
p->declarativeData = 0;
}
}
d_ptr->inDestructor = 1;
d_ptr->removeExtraItemCache();
#ifndef QT_NO_GESTURES
if (d_ptr->isObject && !d_ptr->gestureContext.isEmpty()) {
QGraphicsObject *o = static_cast<QGraphicsObject *>(this);
if (QGestureManager *manager = QGestureManager::instance()) {
const auto types = d_ptr->gestureContext.keys(); // FIXME: iterate over the map directly?
for (Qt::GestureType type : types)
manager->cleanupCachedGestures(o, type);
}
}
#endif
clearFocus();
setFocusProxy(0);
// Update focus scope item ptr.
QGraphicsItem *p = d_ptr->parent;
while (p) {
if (p->flags() & ItemIsFocusScope) {
if (p->d_ptr->focusScopeItem == this)
p->d_ptr->focusScopeItem = 0;
break;
}
p = p->d_ptr->parent;
}
if (!d_ptr->children.isEmpty()) {
while (!d_ptr->children.isEmpty())
delete d_ptr->children.first();
Q_ASSERT(d_ptr->children.isEmpty());
}
if (d_ptr->scene) {
d_ptr->scene->d_func()->removeItemHelper(this);
} else {
d_ptr->resetFocusProxy();
setParentItem(0);
}
#ifndef QT_NO_GRAPHICSEFFECT
delete d_ptr->graphicsEffect;
#endif //QT_NO_GRAPHICSEFFECT
if (d_ptr->transformData) {
for(int i = 0; i < d_ptr->transformData->graphicsTransforms.size(); ++i) {
QGraphicsTransform *t = d_ptr->transformData->graphicsTransforms.at(i);
static_cast<QGraphicsTransformPrivate *>(t->d_ptr.data())->item = 0;
delete t;
}
}
delete d_ptr->transformData;
if (QGraphicsItemCustomDataStore *dataStore = qt_dataStore())
dataStore->data.remove(this);
}
Another approach is to not call deleteLater() before removing the item from the scene, but this requires manually disconnecting the item from other objects. This has similar disadvantage to testing for self.scene() being None in slots, and its easy to forgot to disconnect a slot.
First of all, there's no reason to manually remove an item from the scene if your goal is to destroy the item. The scene tracks the item lifetime. So all you need to do is to destroy the item by appropriate means.
If none of the item's methods are on the call stack, simply delete item.
If the item's methods may be on the call stack, use the QObject::deleteLater method.
Qt's classes are mostly well designed and thus follow the Liskov Substitution Principle. The QGraphicsObject is-substitutable-for-a QObject and you can treat it as if it was, indeed, a QObject, without worrying that it happens to be something a QGraphicsItem too.
That's all there's to it. It will solve all your problems in one go.
You almost never have to call scene.removeItem directly: manage the lifetime of the items, and the scene will follow it for you. It's just like the interaction between QWidget and QLayout: widgets that are managed by layouts are still destructible and the layout will forget about the widget when the widget gets destroyed.

How can I pass a QML object reference into Qt C++?

I am attempting to create something like jQuery's autocomplete as a widget in QML and Qt C++. Toward that end, I created a C++ AutoCompleteListener child of QObject and then register it with:
qmlRegisterType<AutoCompleteListener>(
"foo.AutoCompleteListener",0,1,"AutoCompleteListener");
Then, I instantiate the listener and the AutoCompleteForm like:
import QtQuick 2.5
import com.foo.AutoCompleteListener 0.1
Item {
AutoCompleteForm { id: autocomplete_form }
AutoCompleteListener { id: listener }
}
How can I pass a reference to the QML object AutoCompleteForm into AutoCompleteListener?
I tried passing the autocomplete_form field into:
Q_INVOKABLE void set_autocomplete_form(QQmlComponent *autocomplete_form);
on the onCompleted signal:
Item {
AutoCompleteForm {
id: autocomplete_form
Component.onCompleted: {
console.log("AutoCompleteForm completed");
listener.set_autocomplete_form(autocomplete_form);
}
}
AutoCompleteListener {
id: listener
Component.onCompleted: {
console.log("AutoCompleteListener completed");
}
}
}
However, the reference is a nullptr even though both AutoCompleteListener and AutoCompleteForm have been instantiated:
Instantiating AutoCompleteListener and parent is QObject(0x0)
qml: AutoCompleteListener completed
qml: AutoCompleteForm completed
Setting autocomplete_form = QObject(0x0)
How can I get a reference to the AutoCompleteForm or AutoCompleteListener's QML parent? I want to avoid crawling down the entire QML hierarchy with something like:
QObject* f = mView.rootObject();->findChild<QObject *>("AutoCompleteForm");
I plan to support having multiple AutoComplete widgets instantiated in parallel so a relative path (../AutoCompleteForm) to manipulate the QML objects seems better than having to crawl through the tree.
How can I pass a QML object reference into Qt C++?
You can't, the language was not designed for that. But you can get references from within C++ using findChild and findChildren. But read below for your real solution.
Also, I think your question is about an XY Problem. You have a problem X and you think Y solves it, so you ask for Y.
Correct solution for your original problem:
I am attempting to create something like jQuery's autocomplete as a
widget in QML and Qt C++.
In order to solve your real problem you need to use property bindings correctly. QML is a declarative language and self obsession with imperative programming makes it difficult to be straightforward.
Use this pattern for QML:
AutocompleteForm{
id: form
text: "Search here..."
suggestedTerms: helper.results
}
AutoCompleteHelper{
id: helper
searchFor: form.text
}
And for C++ implement
class AutoCompleteHelper : public QObject
{
Q_OBJECT
Q_PROPERTY(QString searchFor READ searchFor WRITE setSearchFor NOTIFY searchForChanged)
Q_PROPERTY(QStringList results READ results NOTIFY resultsChanged)
public:
AutoCompleteHelper() {}
virtual ~AutoCompleteHelper() {}
QString searchFor() const { return m_searchFor; }
QStringList results() const { return m_results; }
public slots:
void setSearchFor(QString searchFor)
{
if (m_searchFor == searchFor)
return;
m_searchFor = searchFor;
emit searchForChanged();
// 1. Search for it...
// 2. Some time later fill m_results
// 3. Then: emit resultsChanged()
}
signals:
void searchForChanged();
void resultsChanged();
private:
QString m_searchFor;
QStringList m_results;
};
Then you will see it magically works, because as soon as you change the form.text the binding sets the value in helper.searchFor, which then immediately fires the C++ slot where you can react even instantaneously, and in C++ the emission of resultsChanged() magically fills form.suggestedTerms.
You will find this pattern is extremely efficient so you'll even want to delay it by restarting a timer in C++. Furthermore, it is also beautifully declarative and clean.
How can I get a reference to the AutoCompleteForm or
AutoCompleteListener's QML parent?
The only alternative to rootObject()->findChild() appears to be QQmlProperty::read. There is several of read() function overloads so you can specify the context more precise. You have to provide the name for the object you would like to fetch and make it a property of some root object to start with. I like the general article on this subject of interfacing between QML and C++.
To accomplish precisely what you want or access the parent of certain known property you can try QQmlContext::parentContext together with QQmlProperty::read that accepts the context and see if the empty object name allows to resolve the object then.
You can pass it as a QVariant:
Q_INVOKABLE void QmlLink::pass_object(QVariant v)
{
YourObject* tempObject = (YourObject*) v.value<void *>();
...
}
qml:
my_bound_property.pass_object(the_object);

Qt can not respond key Press event immediately

Environment: Ubuntu, Qt Creator
In my Qt app, I found that sometimes Qt doesn't respond to my key press event immediately, but if I wait a while, it eventually responds.
I think something is blocking the UI.
As I know, if a Qt's component (QWidget etc.) is being destroyed, the Qt UI will be blocked. I have checked my code, there is no component being destroyed at the time I'm pressing the up/down key.
I really want to know is there any other things can block Qt UI.
{
...
connect(webViewWidget, SIGNAL(loadfinished()), this, SLOT(addItem()));
...
}
void addItem()
{
delete webViewWidget; // will this delete block UI?
mListWidget = new ScrollWidget();
mScrollArea = new ScrollArea(this);
for(int i=0; i<Datalen; i++)
{
mListWidget->addSubItem(itemWidget);
}
}
void keyPressEvent(QKeyEvent *event)
{
switch(event->key)
{
case UP_KEY:
scroll up;
break;
case DOWN_KEY:
scroll down;
break;
default:
break;
}
}
In general, your key press event will not be processed before all other events which were put into the application's event queue before pressing your key are processed.
Therefore it could be any kind of event which has not finished processing. Maybe you can figure out if there are any events, e.g. by using QApplication::hasPendingEvents or by inheriting from QApplication and adding debug output whenever an event is added or fully processed.
Destruction of objects is usually not a concern, unless you are doing a lot of work in the destructor. Destroying a webview may take long. You probably should not be destroying it like you do. Instrument that delete (see code below) and see how long it takes.
Your own code may be calling APIs that block. Are you calling any third party libraries? Are you calling any wait... methods in Qt's own API?
If you're unsure, you can instrument every slot and every reimplemented virtual method like xxxEvent(...). You'd need to instrument only slots and reimplemented QObject/QWidget methods, not every method in your code.
You may be producing an event storm, perhaps by posting lots of events in a loop, or by sending a lot of signals that are hooked up to slots connected via a Qt::QueuedConnection. Make sure you're not calling repaint() from within paintEvent() for example.
The instrumentation example below uses RAII and is very easy to apply. Alternatively, you can use a profiler.
#include <QElapsedTimer>
#define INSTRUMENT() Instrument instr__ument(__FUNCTION__)
#define INSTRUMENTLIM(lim) Instrument instr__ument(__FUNCTION__, (lim))
class Instrument {
QElapsedTimer timer;
int limit;
const char * function;
public:
Instrument(const char * name, int timeLimitMs = 20) :
function(name), limit(timeLimitMs) { timer.start(); }
~Instrument() {
if (timer.elapsed() > limit) {
qDebug("%s was slow, took %d ms", function, timer.elapsed());
}
}
}
void slot(...)
{
INSTRUMENT();
...
}
void addItem()
{
INSTRUMENT();
delete webViewWidget; // will this delete block UI?
mListWidget = new ScrollWidget();
mScrollArea = new ScrollArea(this);
for(int i=0; i<Datalen; i++)
{
mListWidget->addSubItem(itemWidget);
}
}

Help me understand the QThread usage

I have the MainWindow w windows and TestThread testThread as a member of w. I know it i simple, but I cannot run the testThread.foo() method in testThread thread (not in window thread). In another words: I don't understand the QThread behavior.
Please help correct the next test application. There is a QProgressBar *MainWindow::ui::progressBar and QPushButton *MainWindow::ui::startButton (write simply). I want to start (by startButton click) TestThread::foo(int* progress) which will increment int progress each second.
MainWindow:
MainWindow::MainWindow(QWidget *parent) : // ...
{
// ...
ui->progressBar->setRange(0, 5);
progress = 0; // int MainWindow::progress
this->connect(ui->startButton, SIGNAL(clicked()), SLOT(startFoo()));
connect(this, SIGNAL(startFooSignal(int*)), &testThread, SLOT(foo(int*)));
// TestThread MainWindow::testThread
testThread.start();
}
// ...
void MainWindow::timerEvent(QTimerEvent *event)
{
ui->progressBar->setValue(progress);
}
void MainWindow::startFoo() // this is a MainWindow SLOT
{
startTimer(100);
emit startFooSignal(&progress);
// startFooSignal(int*) is a MainWindows SIGNAL
}
TestThread:
void TestThread::foo(int *progress) // this is a TestThread SLOT
{
for (unsigned i = 0; i < 5; ++i) {
sleep(1);
++*progress; // increment MainWindow::progress
}
}
I know, this is simple. I am doing something wrong :)
P.S. I want to run the simpliest (as possible) example to understand the QThread behavior.
Thanks!
The critical issue is to have the object containing the foo()-function be owned by that thread, so that slot calls are dispatched from the right thread's event-loop.
(Note that there's no need to actually have foo() on the TestThread object. You can use separate objects for QThread and WhatEver::foo() function. It might be easier too, I'm not sure..)
IIUC, this is what you have to do:
Use QObject::moveToThread() to assign the object containing the foo-function to TestThread (that means that Qt::AutoConenction (the default) signal/slots calls will run correctly across thread, being dispatched from each thread's own event loop).
By having the object "owned" by the right thread, slots calls will be scheduled on that thread's event loop, rather than executed directly.
Hope it helps. :)
One alternative solution: If you just want to run a function in another thread, and don't insist using QThread, you should check out the QT Concurrent Namespace.
The following example will run the function foo() in separate thread and will not block on the line where calling the function. Of course there are mechanisms to understand when a function ends, to get a result, to wait for it, to control execution.
void foo(int &progress) {...}
int progress;
QtConcurrent::run(foo, progress);
Hope this helps
See QThread::start and QThread::run.

Resources