I've been trying that for a while and seems it's not something common as I didn't find much information about.
I have a QTree in which I put item, on my Item i have a check box on the first column.
Problem: Checkbox is not optimised to be treated as so and takes quite some time as soon as there is several elements.
So i'm using a thread to create the checkbox before putting in my list, but seems impossible to get the checkbox back on the GUI thread.
void CheckItemThread::run()
{
setPriority(QThread::IdlePriority);
QCheckBox *m_check;
m_check = new QCheckBox();
emit done(m_check);
}
My main thread:
myCheckItem::myCheckItem(QTreeWidget *parent, QStringList columnNames ):
myWidgetItem(parent)
{
m_parent = parent;
m_columnNames = columnNames;
connect(&TheThread,SIGNAL(done(QCheckBox *)), this, SLOT(retThread(QCheckBox *)));
connect(&TheThread,SIGNAL(terminated()), this, SLOT(endThread()));
TheThread.setdata(columnNames,parent, this);
TheThread.start(); //run thread
}
void myCheckItem::endThread()
{
m_check->setParent(m_parent);
connect(m_check, SIGNAL(stateChanged(int)), this, SLOT(onCheckBox(int)));
}
void myCheckItem::retThread(QCheckBox *check)
{
m_check = check;
}
Maybe I'm missing something or it's simple not possible to reattach the thread ?
Thanks
You must not create, edit or work with UI elements in other threads. UI elements must be worked with in the main thread (UI thread). If you have time-consuming prerequisites before "drawing" a checkbox, do your work in a thread (eventually QtConcurrent) and send a signal to the main thread for creating the corresponding checkbox.
You can change GUI elements only in main thread. How many checkboxes do you have? Maybe you should create a limited count of checkboxes and reuse them when needed?
Related
I am listening to a topic and want to display and update the received value every time it changes.
This function creates the logging part of the GUI
QGroupBox *Window::startLoggingGroup()
{
QGroupBox *groupBox = new QGroupBox(tr("Logging"));
log_value = new QPlainTextEdit;
log_value->setReadOnly(true);
log_value->setPlaceholderText("Value will appear here \n");
QHBoxLayout *hbox = new QHBoxLayout;
hbox->addWidget(log_carrot);
groupBox->setLayout(hbox);
return groupBox;
}
This is the code executed on every value changed.
void EFISWindow::callback_value(const geometry_msgs::PoseStamped& msg){
QString qst = QString::number(msg.pose.position.z);
log_value->setPlainText(qst);
}
It works at first, I can see the GUI and some values, but after several messages like the ones I show now it crashes:
QObject::connect: Cannot queue arguments of type 'QTextBlock' (Make
sure 'QTextBlock' is registered using qRegisterMetaType().) QObject:
Cannot create children for a parent that is in a different thread.
(Parent is QTextDocument(0x227e580), parent's thread is
QThread(0x1f9db50), current thread is QThread(0x7f4ae40011d0)
How can I solve this threading issue? Maybe using a signal-slot design? I don't really understand why this is not working.
You should not access a GUI element from another thread.
Maybe using a signal-slot design?
Yes, Your worker object should have a signal that you emit at some point and you should connect that signal to update the "log_value" value.
//MainWindow.cpp
void MainWindow::threadedFunction()
{
myDialog = new MyDialogs(list,processList,this);
myDialog->show();
}
void MainWindow::createNewDialog()
{
getProcesses();
//threadedFunction(); //This works fine.
std::thread tx = std::thread(&MainWindow::threadedFunction,this);
tx.join();
}
//MyDialog.cpp
MyDialogs::MyDialogs(QList<int> lists,QStringList list,QObject *parent):QDialog(0)
{
QVBoxLayout *toplay = new QVBoxLayout(this);
listWidget = new QListWidget(this);
x<<list;
l<<lists;
listWidget->addItems(x);
toplay->addWidget(listWidget);
connect(listWidget,SIGNAL(doubleClicked(QModelIndex)),
this,SLOT(getProcessString(QModelIndex)));
}
void MyDialogs::getProcessString(QModelIndex index)
{
selectedProcessString = index.data().toString();
rowIndex = index.row();
}
Already set in pro file.
CONFIG += c++11
when I call threadedFunction directly the code works fine.
But the above mentioned code gives me runtime error.
If I use only qDebug statements in threadedFunction and remove myDialog code, the code runs fine even with the threads. What is the problem? I am using Qt5 with MingW 4.9.1 32bit. And I do not have visual studio installed.
The problem is that you're trying to create a UI object outside the GUI thread. I'm assuming your main function instantiates a QApplication object which starts your event loop. This thread is your one and only GUI thread. I'm guessing your GUI thread is the thread which is creating the thread that is trying instantiate the QListWidget.
If my assumptions are correct then you need to create the QListWidget in the main/GUI thread and then call your thread function. I can't say what you should do in your thread function since I'm not sure what you want to do but there are multiple ways to handle it.
You may want to look at QThread. That class will allow you to use signals and slots, which may make things easier for you. You can use traditional threading constructs (e.g., mutex , wait conditions, etc.) and shared state. There are also ways to post events which go through the message loop (i.e. get sent back into the main/GUI thread).
i have an multithreaded qt application. when i am doing some processes in mainwindow.cpp, at the same time, i want to update mainwindow.ui from other thread.
i have mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include "mainwindow.h"
class mythread : public QThread
{
public:
void run();
mythread( MainWindow* ana );
MainWindow* ana;
private:
};
#endif // MYTHREAD_H
mythread.cpp
mythread::mythread(MainWindow* a)
{
cout << "thread created" << endl;
ana = a;
}
void mythread::run()
{
QPixmap i1 (":/notes/pic/4mdodiyez.jpg");
QLabel *label = new QLabel();
label->setPixmap(i1);
ana->ui->horizontalLayout_4->addWidget(label);
}
but the problem is that, i cannot reach the ana->ui->horizontalLayout_4->addWidget(label);
how can i do that?
but the problem is that, i cannot reach the
ana->ui->horizontalLayout_4->addWidget(label);
Put your UI modifications in a slot in your main window, and connect a thread signal to that slot, chances are it will work. I think only the main thread has access to the UI in Qt. Thus if you want GUI functionality, it must be there, and can be only signaled from other threads.
OK, here is a simple example. BTW, your scenario doesn't really require to extend QThread - so you are better off not doing it, unless you really have to. That is why in this example I will use a normal QThread with a QObject based worker instead, but the concept is the same if you subclass QThread:
The main UI:
class MainUI : public QWidget
{
Q_OBJECT
public:
explicit MainUI(QWidget *parent = 0): QWidget(parent) {
layout = new QHBoxLayout(this);
setLayout(layout);
QThread *thread = new QThread(this);
GUIUpdater *updater = new GUIUpdater();
updater->moveToThread(thread);
connect(updater, SIGNAL(requestNewLabel(QString)), this, SLOT(createLabel(QString)));
connect(thread, SIGNAL(destroyed()), updater, SLOT(deleteLater()));
updater->newLabel("h:/test.png");
}
public slots:
void createLabel(const QString &imgSource) {
QPixmap i1(imgSource);
QLabel *label = new QLabel(this);
label->setPixmap(i1);
layout->addWidget(label);
}
private:
QHBoxLayout *layout;
};
... and the worker object:
class GUIUpdater : public QObject {
Q_OBJECT
public:
explicit GUIUpdater(QObject *parent = 0) : QObject(parent) {}
void newLabel(const QString &image) { emit requestNewLabel(image); }
signals:
void requestNewLabel(const QString &);
};
The worker object is created and moved to another thread, then connected to the slot that creates the labels, then its newLabel method is invoked, which is just a wrapper to emit the requestNewLabel signal and pass the path to the image. The signal is then passed from the worker object/thread to the main UI slot along with the image path parameter and a new label is added to the layout.
Since the worker object is created without parent in order to be able to move it to another thread, we also connect the thread destroyed signal to the worker deleteLater() slot.
First and foremost, "you're doing it wrong". Normally you want to create a class derived from a QObject and move that class to a new thread object instead of deriving your class from a Qthread
Now to get onto the specifics of your question, you're not able to directly modify the ui elements of your main GUI thread from a separate thread. You have to connect a signal from your 2nd thread to a slot in your main thread. You can pass any data that you need through this signal/slot connection but you're unable to directly modify the ui element (which in all honestly you probably do not want to if you intend to keep the frontend of your app separate from the backend). Checkout Qt's signal and slot documentation for a whole lot more information
how can i do that?
You've already got the answers to what you should be doing, but not a why, so I'm going to add a why.
The reason you don't modify GUI elements from another thread is because GUI elements are usually not thread-safe. This means that if both your main GUI thread and your worker thread update the UI, you cannot be certain of the order of what happened when.
For reading data generally this can sometimes be fine (e.g. checking a condition) but generally you do not want this to be case. For writing data, this is almost always the source of very, very stressful bugs which occur "at random".
Another answer has remarked on good design principles - not only does constraining your GUI logic to one thread and firing signals to talk to it get rid of your race condition issues, but it also forces you to compartmentalize your code nicely. Presentation logic (the display bit) and data processing logic can then be cleanly separated out, which makes maintaining the two much easier.
At this stage you might think: heck, this threads business is farrrrrr too much work! I'll just avoid that. To see why this is a bad idea, implement a file copy program in a single thread with a simple progress bar telling you how far along the copy is. Run it on a large file. On Windows, after a while, the application will "go white" (or on XP I think it goes gray) and will be "not responding". This is very literally what is happening.
GUI applications internally mostly work on the variation of "one big loop" processing and dispatching messages. Windows, for example, measures response time to those messages. If a message takes too long to get a response, Windows then decides it is dead, and takes over. This is documented in GetMessage().
So whilst it may seem like quite a bit of work, Signals/Slots (an event-driven model) is basically the way to go - another way to think of this is that it is totally acceptable for your threads to generate "events" for the UI too - such as progress updates and the like.
I have a multithreaded program that downloads and gets information from a website, then takes the info, creates a object that i then add to my GridLayout with a image.
I have thousands of objects that i need created and then added to my gridlayout, using 8 threads to get the information and those same 8 threads then create the objects and add them to 8 different grids.
After the program runs for about 20 seconds, i get a
QThread::start: Failed to create thread (The access code is invalid.) errors.
After another few more seconds i get some Runtime C++ Errors that hang and don't display a message.
What does this imply?
What confuses me, is that at this point, my threads are already created and im not actually creating more threads.. inside those threads though they are each creating Widget Objects and then im sending those to the Main Gridlayout..
I checked the amount of threads i have running (QThread:idealThreadCount) which returns only 8.
I'm multithreading to download the information from the sites, which then each records returns values back which i created into a Object to put into my GridLayout.
Im thinking because at that point, these objects all belong to the single thread, and not ever emit(finished()) until all urls are done, so its hogging something and causing this error?
Should i try MoveToThread and send all these objects back to the Main thread after the information is downladed and the new object is added to my gridlayout?
Does any of this make sense of what is happening, and if do you want me to post the code?
Thanks.
Edit:
Just tried moving the threads after creation, but learnt
QObject::moveToThread: Widgets cannot be moved to a new thread
Here is my creation of threads
void checkNewArrivals::createWorkers(QString url, QString category, QString subCategory){
QThread* thread = new QThread;
checkNewArrivalWorker* worker = new checkNewArrivalWorker(url, category, subCategory);
worker->moveToThread(thread);
connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
connect(thread, SIGNAL(started()), worker, SLOT(process()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(result(QString,QString,QString,QString,QString,int, int)), this, SLOT(addItem(QString,QString,QString,QString,QString,int, int)));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
Here is my worker class function that emits the results
void checkNewArrivalWorker::getHtml(QString url){
html = QString::fromStdString(Curl->getWebsiteHtml(url.toStdString()));
html = html.mid(html.indexOf("Goods List"));
html = html.mid(0, html.indexOf("footer"));
for (int i = 0; i < html.count("GoodsBox"); i++){
//blah blah blah blah
emit result(idLink, picLink, price, category, subCategory, row, col);
col++;
if (col == 5){
col = 0;
row++;
}
html = html.replace(itemRow, "");
}
}
Then i add the results to create a item and add it to a gridlayout
void checkNewArrivals::addToGrid(QGridLayout *layout, QString id, QString picUrl, QString usPrice, int row, int col){
checkNewArrivalItem* item = new checkNewArrivalItem;
if (item->setupItem(id, picUrl, usPrice) == true){
layout->addWidget(item, row, col);
};
}
These items work fine, until a overload of items i think.. not too sure why im getting this error.
Qt GUI objects cannot exist in any thread other than the main thread. The GUI objects need to be created and added to the layout in the main thread. You can still gather data from background threads, but you'll need to pass it back to the main thread for display. For that, I recommend using signals, as signal/slot connections are thread-safe by default.
Edit: Your new threading looks like it should work. However, I think you'd find it preferable to keep a specific number of threads running and delegate the work to them. For one, creating, starting, stopping and destroying threads is an expensive process. But more importantly, perhaps, is that you might just be creating too many threads.
Just to be really clear, QThread::idealThreadCount() is not the number of threads you have running, but is the number of threads your CPU can most efficiently handle. I'm guessing you have a quad-core CPU with hyperthreading, making it 8.
I think it's most likely that you're creating too many threads. This sounds a lot like the time when I mistakenly did that.
class genericTaskList : public QListWidget
{
Q_OBJECT
public:
QListWidgetItem *defaultText;
genericTaskList (QWidget *parentWidget)
{
setParent (parentWidget);
setFixedSize (445, 445);
defaultText = new QListWidgetItem ("Double click here to compose the task");
defaultText->setFlags (defaultText->flags () | Qt :: ItemIsEditable);
insertItem (0, defaultText);
QObject :: connect (this, SIGNAL (currentRowChanged (int)), this, SLOT (addDefaultText (int)));
}
public slots:
void addDefaultText (int rr)
{
std::cout << "\ndsklfjsdklfhsdklhfkjsdf\n";
insertItem (++rr, defaultText);
}
};
This code is supposed to issue a signal each time the row gets edited.
After I call "insertItem" in the constructor, the signal is issued.
But, that's it. It never gets issued after that - no matter how many times I edit the row.
What am I missing?
At first it seems like QListWidget::itemChanged is the way to go, but soon you run into a problem: the signal is sent for everything - inserts, removes, changing colors, checking boxes, etc! So then you end up trying to put in flags and filter everywhere by intercepting various signals to find out if editing was the actual event. It gets very messy.
There is also QAbstractItemModel::dataChanged , which would seem like a good solution. It even has a parameter "const QVector& lstRoles" so you could scan for Qt::EditRole and see if it was really edited. Alas, there's a catch - it gets called for everything just like QListWidget::itemChanged and unfortunately, for QListWidget anyway, the roles parameter is always empty when it's called (I tried it). So much for that idea...
Fortunately, there's still hope... This solution does the trick! :
http://falsinsoft.blogspot.com/2013/11/qlistwidget-and-item-edit-event.html
He uses QAbstractItemDelegate::closeEditor, but I prefer using QAbstractItemDelegate::commitData.
So make a connect like so...
connect(ui.pLstItems->itemDelegate(), &QAbstractItemDelegate::commitData, this, &MyWidget::OnLstItemsCommitData);
Then implement the slot like this...
void MyWidget::OnLstItemsCommitData(QWidget* pLineEdit)
{
QString strNewText = reinterpret_cast<QLineEdit*>(pLineEdit)->text();
int nRow = ui.pLstItems->currentRow();
// do whatever you need here....
}
Now you have a slot that gets called only when the list item's text has been edited!
currentRowChanged indicates the row selection has changed, not the content of the row. Perhaps you want to use currentTextChanged or itemChanged instead.
The reuse of the word current and changed in the QT docs is quite confusing.
Warning: A QListWidgetItem can only be added to a QListWidget once. Adding the same QListWidgetItem multiple times to a QListWidget will result in undefined behavior.
So even if it will emit the signal I think you should better to add newly created Item.
And when do you want the new row to be inserted ? -
as soon as item is double clicked or finishing edit - they differ.