proper design for first project



  • Hi all -

    This will seem trivial to most of you, but I'm having trouble getting started. I want to build a simulator (details probably not important) using Qt for the UI. Despite having completed a bunch of tutorials, I'm still not sure how to design this.

    I'd be thrilled if I could have a small program that displayed a value (Thinking of QLCDnumber) in the UI. A loop in the main execution would: increment a counter, display the new value in the UI, then delay for a second. Once the value got to, say, 10, it would exit.

    Once I got this working, I'd like to add a start/pause button to the UI. I think I can figure most of this out, but...where does the loop go?

    I guess my question really is this: where does your program logic go when building with Qt? I see the various objects, and understand the signal/slot mechanism, but I don't see where the "ordinary" programming goes.

    Thanks for any guidance. I'm not looking for someone to do the work for me, but to help me understand the preferred design here.


  • Lifetime Qt Champion

    Hi,

    In the case you describe, there's no need for a loop. You can use a QTimer and connect it to a slot that will do the incrementation and check, and that will stop the timer when reaching the max value you decided for.


  • Moderators

    What you're describing is sorta a "console" way of doing things i.e. thinking about an app in a linear fashion: do something, sleep, do something else, sleep, do one more thing, exit.

    That's not a good approach when building a ui app. Qt ui applications are event driven, not linear i.e. there's an event loop going in circles "listening" to events happening and reacting to them by invoking handlers. The events can be generated by the ui, the OS or of your own creation. The "ordinary" programming, as you called it, goes into the event handlers. Sleeping at all is generally a bad thing and should be avoided. When there's nothing to do just end a handler and let the control flow go back to the event loop.
    If you want to do something at fixed interval you would use a timer and respond to a timer event, not explicitly sleep.

    So you start with the building blocks i.e. the event loop:

    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        return a.exec();
    }
    

    That's the simplest working ui app with an event loop.
    Now add some ui. To keep the main function clean put the ui code in some class:

    class MyUi : public QWidget
    {
    public:
        MyUi(QWidget* parent = nullptr) : QWidget(parent)
        {
            setLayout(new QVBoxLayout());
    
            QLCDNumber* lcd = new QLCDNumber();
            layout()->addWidget(lcd);
    
            QPushButton* button = new QPushButton("Start/Stop");
            layout()->addWidget(button);
        }
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        MyUi ui;
        ui.show();
    
        return a.exec();
    }
    

    From here on we don't need to touch main(). All it does is shows the ui and starts the event loop.
    Now add a timer and connect its start/stop to the button:

    class MyUi : public QWidget
    {
        QTimer timer;
    public:
        MyUi(QWidget* parent = nullptr) : QWidget(parent)
        {
            setLayout(new QVBoxLayout());
    
            QLCDNumber* lcd = new QLCDNumber();
            layout()->addWidget(lcd);
    
            QPushButton* button = new QPushButton("Start/Stop");
            layout()->addWidget(button);
    
            connect(button, &QPushButton::clicked, this, &MyUi::startStopTimer);
        }
    
        void startStopTimer()
        {
            if(timer.isActive())
                timer.stop();
            else
                timer.start(1000);
        }
    };
    

    Last thing left to do is to actually do something when the timer ticks.
    Add a counter member and connect to timer's timeout() signal to "do calculations" and update ui. Since I need that lcd pointer I'll make it a member too:

    class MyUi : public QWidget
    {
        QTimer timer;
        QLCDNumber* lcd = nullptr;
        int counter = 0;
    public:
        MyUi(QWidget* parent = nullptr) : 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, &MyUi::startStopTimer);
            connect(&timer, &QTimer::timeout, this, &MyUi::doStuff);
        }
    
        void startStopTimer()
        {
            if(timer.isActive())
                timer.stop();
            else
                timer.start(1000);
        }
    
        void doStuff()
        {
            lcd->display(++counter);
        }
    };
    

    And that's it.
    From here you can keep adding more ui and functionality in the same way - add a widget and then handle its events/signals.
    That's the event driven approach, and by events I don't mean just QEvent. It can be a signal, an interrupt, a virtual QEvent handler invocation etc. You then handle these events, either by connecting something to a signal or reimplementing a QEvent handler in QWidget subclass. ++counter represents the "ordinary programming". It could be a function call, database query etc.

    Just remember that these "handlers" should be as small and fast as possible, because they block the event loop. So get in there, do the job and get out fast. If you need to do something longer start a thread, do it there and don't block the ui thread.



  • Hi Chris -

    That's an excellent explanation, and makes a lot of sense. Before I dive into this, I do have a question: I see that you're adding your objects via C++ coding, vs using the forms editor. Any particular reason for that? I ask because most of the tutorials I did (from VoidRealms) used the forms editor.

    And on that subject...how do the two (forms editor and C++ code) connect with each other? When I create something in the forms editor (like adding a QLCDnumber object) I see no evidence of that anywhere in my code.

    Thanks again for a very helpful reply.


  • Moderators

    @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 named ui_<whatever>.h). That generated header contains a definition of a class placed in a Ui:: namespace that has a setupUi method. This setupUi 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 includes ui_mainwindow.h generated from designer file and has a Ui::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.


  • Moderators

    @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 that timeout 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 a Q_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:

    1. 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.

    2. 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.


  • Qt Champions 2016

    @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 run setupUi).



  • @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.


  • Qt Champions 2016

    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.
      1. 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.
      2. 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 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.
      1. 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)).
      2. 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.



  • 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:
    1. the state of the start/stop button (modified by UI, used to "gate" worker)

    2. 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?


  • Qt Champions 2016

    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:

    1. main refers to "app" in a connect() statement; where does this come from? brain dead
    2. the line:
        Worker *workerObject = new Worker(); //< No parent, otherwise you can't move it to the thread
    

    Won't compile without a parent argument.


  • Qt Champions 2016

    @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.


  • Lifetime Qt Champion

    Remove the qint32 from the connect statement. Using that version you only pass the address of the function.


  • Qt Champions 2016

    @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 the Q_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.


  • Lifetime Qt Champion

    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.


  • Lifetime Qt Champion

    I'd check that counterTimer.start(1000) is really called.



  • Yeah, that was basically it. When I stitched together the examples from above, I neglected to notice that my worker object had two timers declared, and I wasn't consistent on which I used.

    Works now. I've tested multiple starts and stops, and verified that the "finished" signal works, too. I do believe I have a functional example. I also modified the program so the worker passes the value to the widget for display.

    So, to summarize what I've learned here:

    1. run at least two threads: one for the UI and one to do work. This prevents compute-intensive tasks from blocking the UI updates.
    2. create a class for the worker, and instantiate an object in main().
    3. use moveToThread to move the worker object to another thread.
    4. make your worker interrupt-driven (work depends mostly on signals from UI).
    5. use the arguments in the signal/slot routines to pass data.

    I think I have a basis for reasonable design now. Thank you to everyone who participated in this.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.