Solved proper design for first project
-
@mzimmers said in proper design for first project:
I see that you're adding your objects via C++ coding, vs using the forms editor. Any particular reason for that?
It's easier to post on the forum :) There's nothing wrong with either way. I tend to create small parts of ui (couple of widgets like here) from code and switch to the designer for bigger pieces, but there are many opinions - some tend to create as much as possible, including actions and connections, in the designer while others code everything. It's a matter of preference and how much of what you need can actually be done in the designer (it has restrictions compared to coding by hand).
Designer doesn't do anything special. Its code is stored in
<whatever>.ui
files. During compilation these files are transformed by a uic tool into c++ headers (by default namedui_<whatever>.h
). That generated header contains a definition of a class placed in aUi::
namespace that has asetupUi
method. ThissetupUi
method is then usually called at the top of your own widget subclass. It creates the widgets you created in the designer and sets their properties using the same methods you'd use if you did it by hand from code.You can see all that in a basic widgets application generated by Qt Creator's wizard. There's a
MainWindow
class that includesui_mainwindow.h
generated from designer file and has aUi::Mainwindow* ui
member.Ui::MainWindow
class comes from that generated header.Inspecting generated
setupUi
method in the header is actually a pretty good way to learn how to do various things from code. Create a ui in the designar and you can see how it's made in code. -
Interesting...so as an experiment, after I got your example working, I went into the (empty) form that Creator made for me when I created the project, and added a text label. This text box shows up in my .ui file (as you said it would), and also displays when I run the program. No other code file was generated though. Where are these ui_<whatever>.h files stored?
I'm still missing a concept here, too. Let's say that I wanted to eliminate the timer (but preserve the start/stop button). What starts the doStuff() routine (since it won't be a signal). Also, let's assume that I was going to do some compute-intensive work in doStuff(). You pointed out that the slot handlers should be small and fast. This goes back to my original question of where the "ordinary" program logic resides. I'm still not getting this part of it.
BTW: in implementing your example, I had to define startStopTimer() and doStuff() as void slots. This is probably I used both .cpp and .h files. (In case anyone else is following along).
Thanks.
-
@mzimmers said in proper design for first project:
(...) displays when I run the program. No other code file was generated though.
If it showed up at runtime then it was indeed generated. The generator (uic) does not modify your own files in any way and the generated content does not show up in your project tree in Qt Creator. Like all the other generated content (the executable, the .obj and .lib files etc.) it is placed in the build directory. By default it's a directory next to your project directory, one up from where the compiled executable is placed. If in doubt place the cursor on the "ui_...h" include and hit F2 (Follow symbol under cursor). It will take you to the file.
What starts the doStuff() routine (since it won't be a signal).
Whatever you want. You can connect it to some button click, checkbox toggle, any other ui element change or something non ui, like the
finished()
signal of a network access manager. Note thattimeout
I used is a signal of a QTimer object. Most Qt objects have some signals you can connect to. It depends on what you want to do.You pointed out that the slot handlers should be small and fast. This goes back to my original question of where the "ordinary" program logic resides.
If you give me an example of "ordinary code" I could probably answer more specifically.
By small and fast I mean small and fast enough for the user not to notice them i.e. not freeze the ui for long. If a user clicks a button and whatever is connected to that takes, say, 300ms it won't be noticeable, but if it takes 5s the ui will freeze and become unresponsive.
As I mentioned, code that takes more time should be run in separate thread. For example when you click a button a connected slot creates a QThread object, starts some work in it and immediately exits not to block the ui. The thread does its thing and when it's finished it emits a signal (they can be cross-thread). A slot connected to it runs then in the ui thread and does something with the result of the operation e.g. updates some ui element. See the docs of QThread for examples.BTW: in implementing your example, I had to define startStopTimer() and doStuff() as void slots
My example should work without modifications (apart from missing includes for brevity). The functions need to be slots only if you use the old, macro based, connect syntax:
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()))
. In that case you'd also have to add aQ_OBJECT
macro in the header of that class. I strongly advise you not to use the macro based connect in new code. In Qt 5 there's a pointer based version that I used:connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slotOrFunc)
. It's faster, type checked at compile time and can connect to things other than slots: free standing functions, functor objects and lambdas.This is probably I used both .cpp and .h files
The other Qt code generator - moc, is run only on header files by default. This means that if you use macros like Q_OBJECT and slots you do indeed need to put that class declaration in a proper header (you should anyway of course, I didn't for the sake of shorter example). I didn't use any of these so all the code could go in one .cpp file and moc is not used for it at all.
-
Hi, Chris -
That's a LOT of very good information...thanks for taking the time to go into that detail. I think I actually understood everything you wrote, too. I do have two lingering questions:
-
When you place compute intensive tasks into a separate thread, is there any risk of applications behaving differently based on the OS they're using? I just wonder whether thread management varies much between different OSs.
-
Regarding code that is auto-generated by the uic: since this doesn't appear in my source tree, I imagine that modifying it is, shall we say, discouraged. So, how does one programmatically manipulate objects created in the designer?
Thanks again...this is really helpful.
-
-
@mzimmers said in proper design for first project:
When you place compute intensive tasks into a separate thread, is there any risk of applications behaving differently based on the OS they're using? I just wonder whether thread management varies much between different OSs.
Not with Qt, no. If you use the OSs API, then yes, it's different for each platform.
Regarding code that is auto-generated by the uic: since this doesn't appear in my source tree, I imagine that modifying it is, shall we say, discouraged.
Whatever changes you make in the uic/moc generated code will be overwritten the next time you build/run
qmake
, so it's beyond discouraged, it's just hopeless.So, how does one programmatically manipulate objects created in the designer?
How do you mean? All that's generated by the designer is accessible through the generated (
UI
) class, so you can get the objects from there (after you've runsetupUi
). -
@kshegunov said in proper design for first project:
All that's generated by the designer is accessible through the generated (UI) class, so you can get the objects from there (after you've run setupUi).
Oh, excellent...thanks. That helps a lot.
So, on the topic of threads, the tutorials I performed are evidently somewhat out of date. The author shows creating and starting threads in main(). I'm thinking it might make more sense to start the worker thread from the main (UI) thread, when the start button is pressed. Yes/no?
Also, in my simplistic example, the worker thread is going to loop and run or pause based on the state of the button press in the UI thread. Is a mutex as good as any for this inter-thread communication?
Thanks.
-
Where you will start a thread very much depends on the case; parallelism is a "science" in itself so there aren't that many "good solutions". Usually, you'd start from the requirements before actually considering the code and technology to use. That is - what is your thread supposed to do, does your thread need to be event-driven or does it need to be imperative, how long would that thread be active and so on. When you've cleared these question you can choose an appropriate technological solution - low level with event loop, low level without event loop, high-level (e.g.
QtConcurrent
), etc. So after that long-winded and mostly superficial comment: What is your thread supposed to do exactly?Now onto the technical side:
- You use mutexes when you need a MUTual EXclusion primitive, i.e. when only one thread at a given time can be accessing a piece of data. This is the most common when you need to serialize access to a variable that can't be safely accessed concurrently.
- You don't synchronize access to data that's only being read. If you are sure the data will not be written you can
always read it safely from multiple threads without locking. - If you need to provide multiple readers/writers in a more robust manner you can consider
QReadWriteLock
, which has some advantages over the simple mutex.
- You don't synchronize access to data that's only being read. If you are sure the data will not be written you can
- You use semaphores whenever you need to control access to a multitude of resources at once or whenever you need to synchronize the execution of two threads.
- The first case is usually illustrated by considering a circular buffer with some number of elements. It is possible to make it so that the buffer is written and read at the same time by using semaphores to count how much space there's been written and how much space's free to be written (it's a variation of the "producer-consumer problem", which also uses semaphore(s)).
- The second usage for semaphores is due to their abilities to be acquired and released irrespective of the thread. This is in contrast with most mutex implementations, where the mutex can be unlocked only from the same thread that locked it originally. This restriction isn't arbitrary it allows some nifty optimization to make mutexes faster. So if you need to have some thread wait for something to finish/happen it can try to acquire an empty (i.e. 0-value) semaphore an will block. Whenever the other thread(s) need to signal the sleeping one, they'll raise the semaphore's count (i.e. signal the semaphore) and the sleeping thread will wake up and can continue execution.
These two are low-level threading primitives and if you follow the recommended approach for Qt-style threads - the worker object approach - you will rarely need to even think about it.
PS. Don't get me wrong, there's place for mutexes and semaphores (and I use them rather often), in 95% of situations however your requirements will be satisfied by another technique.
- You use mutexes when you need a MUTual EXclusion primitive, i.e. when only one thread at a given time can be accessing a piece of data. This is the most common when you need to serialize access to a variable that can't be safely accessed concurrently.
-
Great post, kshegunov. I particularly appreciate the link to Maya's post; she and you saved me from disappearing down what was looking like a particularly nasty rabbit hole.
Here's how I envision my little program (and I invite critique):
- there are two threads: the main (UI) thread and the worker thread
- there are two elements that need to be shared by the threads:
-
the state of the start/stop button (modified by UI, used to "gate" worker)
-
the value of the counter (modified by worker, displayed by UI)
- To my conventional mind, the way to do this is to pass pointers to both objects to the worker thread upon creation. The worker will also signal the UI whenever the counter is updated.
How does this look to you?
-
Well, the beauty of the worker object is that you don't need to actually "gate" the thread (assuming I've understood properly what you mean). You can start a worker thread with an event loop and it will just sleep and do nothing until you give it something to do - in the case of the worker object that is a slot being executed in response to a signal. Also Qt makes it easy in such a way that signal-slot calls across threads are by default thread-safe, by introducing thread-affinity - basically each
QObject
"belongs" to a thread and its slots are executed in that thread irrespective of where the triggering signal originated. This means, that your start/stop buttons don't need to start the thread but just initiate the counting (or other computation) and at each step you can signal back the GUI thread by raising a signal from your worker object. Consider the following simple example:class Worker : public QObject { Q_OBJECT public: Worker(QObject * parent = nullptr) : QObject(parent), counter(0), counterTimer(this) { // You should set the parent to all "child" objects appropriately, so they will be moved to the worker thread along with the worker object, as done here for counterTimer // On each thick of the timer we call the signalCounterChange slot QObject::connect(&counterTimer, &QTimer::timeout, this, &Worker::signalCounterChange); } public slots: void startCounter() { counter = 0; counterTimer.start(100); // Counter will be increased every 100ms } void stopCounter() { counterTimer.stop(); } signals: void counterChanged(qint32); private slots: void signalCounterChange() { emit counterChanged(counter); counter++; } private: qint32 counter; QTimer counterTimer; };
And this is all, really, for the worker object, you can then use it by connecting the signals and slots as appropriate:
int main(int argc, char ** argv) { QApplication app(argc, argv); // Initialize GUI here or later ... // Start the worker thread QThread workerThread; workerThread.start(); // Create the worker object and move it to the worker thread Worker * workerObject = new Worker(); //< No parent, otherwise you can't move it to the thread worker->moveToThread(&workerThread); // Connect the signals for cleanup QObject::connect(&app, &QApplication::aboutToQuit, &workerThread, &QThread::quit); // When the app is quitting - stop the thread QObject::connect(&workerThread, &QThread::finished, workerObject, &QObject::deleteLater); // Free the worker object on thread exiting // Connect signals/slots from the worker object to your widget or window here ... // Run the main event loop int result = QApplication::exec(); workerThread.wait(); //< Wait for the worker thread to finish before exiting main. return result; }
-
I'm going to try to get your example to work, then remove the timer. Currently I'm experiencing
two issues:main refers to "app" in a connect() statement; where does this come from?brain dead- the line:
Worker *workerObject = new Worker(); //< No parent, otherwise you can't move it to the thread
Won't compile without a parent argument.
-
@mzimmers said in proper design for first project:
Won't compile without a parent argument.
A typo on my part, the constructor should allow for a default
null
argument:Worker(QObject * parent = nullptr) : QObject(parent), counter(0), counterTimer(this) { //...
-
Yeah, I should have figured that out myself.
So, I'm trying to get this working, and I'm hitting two compile errors that I can't figure out. Here's my main()"
int main(int argc, char *argv[]) { QApplication a(argc, argv); QThread workerThread; SimulatorWidget *widget = new SimulatorWidget(); SimulatorWorker *worker = new SimulatorWorker(); //< No parent, otherwise you can't move it to the thread widget->show(); // Start the worker thread and move the worker object to the worker thread workerThread.start(); worker->moveToThread(&workerThread); // Connect the signals for cleanup QObject::connect(&a, &QApplication::aboutToQuit, &workerThread, &QThread::quit); // When the app is quitting - stop the thread QObject::connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); // Free the worker object on thread exiting // Connect signals/slots from the worker object to your widget or window here ... QObject::connect(widget, &SimulatorWidget::buttonPressed, worker, &SimulatorWorker::startStopTimer); QObject::connect(worker, &SimulatorWorker::counterChanged(qint32), widget, &SimulatorWidget::doStuff);
On the last line, I'm getting this error:
/home/mzimmers/hatchco/simulator/main.cpp:26: error: expected primary-expression before ‘)’ token
QObject::connect(worker, &SimulatorWorker::counterChanged(qint32), widget, &SimulatorWidget::doStuff);
^I imagine it's some lame syntax error, but I can't see it.
More troubling is this error:
/opt/Qt/5.9/5.9/gcc_64/include/QtCore/qglobal.h:733: error: static assertion failed: No Q_OBJECT in the class with the signal
#define Q_STATIC_ASSERT_X(Condition, Message) static_assert(bool(Condition), Message)
^
This is generated from the line:QObject::connect(widget, &SimulatorWidget::buttonPressed, worker, &SimulatorWorker::startStopTimer);
Does the problem stem from the fact that SimulatorWidget isn't derived from QObject?
Thanks.
-
Remove the
qint32
from the connect statement. Using that version you only pass the address of the function. -
@mzimmers said in proper design for first project:
Does the problem stem from the fact that SimulatorWidget isn't derived from QObject?
If it's a widget then it's a
QObject
, make sure you have theQ_OBJECT
macro at the top of your class. -
Thank you to SGaist and kshegunov. Both suggestions were exactly right. I did have to re-run qmake after adding the Q_OBJECT macro, though, to get rid of some undefined error references.
Now that it builds, I'll do some debugging and report back. Thanks again...
-
This connect statement isn't doing what I expect:
QObject::connect(widget, &SimulatorWidget::buttonPressed, worker, &SimulatorWorker::startStopTimer);
The signal is firing (as evinced by a breakpoint in the function) but the slot routine isn't being called. Here's the widget code:
SimulatorWidget::SimulatorWidget(QWidget *parent) : QWidget(parent) { setLayout(new QVBoxLayout()); lcd = new QLCDNumber(); layout()->addWidget(lcd); QPushButton* button = new QPushButton("Start/Stop"); layout()->addWidget(button); connect(button, &QPushButton::clicked, this, &SimulatorWidget::buttonPressed); } void SimulatorWidget::buttonPressed() { ; // do nothing }
Any ideas what I'm doing wrong? And I realize this example is somewhat odd, but I couldn't think of a more direct way (to send the button signal to the worker slot).
Thanks.
-
Aren't you connecting two slots there ?
buttonPressed should be a signal.
-
Here's what I was trying to do: get the button clicked signal to fire a slot function within the widget, and that slot would also be a signal to the worker.
From your question, I gather that this isn't right, so how do I connect the button press to the worker?
-
Ohhh...I think I just realized something: the signal isn't a routine defined by me. It just gets created for me (by the MOC?).
So, I made these changes:
void SimulatorWidget::buttonPressed() { emit notifyWorker(); }
and in main:
QObject::connect(widget, &SimulatorWidget::notifyWorker, worker, &SimulatorWorker::startStopTimer);
The worker slot now gets called when the button is pressed.
Now, though, my worker slot for the timer:
QObject::connect(&counterTimer, &QTimer::timeout, this, &SimulatorWorker::signalCounterChange);
isn't working. The slot signalCounterChange() is never called. Any ideas here?
Thanks.
-
I'd check that
counterTimer.start(1000)
is really called.