A Qt user interface for your realtime application

February 14, 2024

It's often useful to create a user interface for an application developed with Model RealTime. Even if the final application will not use that UI, it can be very useful during the development of the application. The UI can show the state of key capsules in your application, and can provide a more tailored way of interacting with the application. It can be used as a complement to, or even replacement for, the generic UI that is provided by the Model Debugger.

In principle there are two ways how to integrate a user interface with a realtime application generated by Model RealTime:

  1. Develop the UI as a separate application that communicates with the realtime application. For example, the UI can consist of a web page where the web server communicates with the realtime application. The traffic-light-web sample on GitHub is an example of an application that uses this approach.
  2. Let the UI run within the realtime application itself, in the same process.

In this newsletter we will look at the second option. We'll use Qt, which is a popular C++ framework for developing UIs (and more), but the same principle works for any UI framework. A sample Qt application, qt-traffic-light is available on GitHub and is discussed below.

Threads

Most UI frameworks require that all code that accesses the UI runs in the same thread (a "GUI thread"), and this is often the main thread. This thread runs an event loop where UI events are dispatched to and from the different UI components. While it would be technically possible to let this UI thread also do other things, such as running the top capsule of your realtime application, it's usually better to separate the realtime part of the application into its own thread. Code generated by Model RealTime can run in any thread, and even if the top capsule by default is run by the main thread of an executable, this is not a requirement.

In the sample Qt application we let the UI thread create a QThread (MyThread) which is reponsible for running the realtime part of the application. When this thread starts (in MyThread::run()) it calls RTMain::entryPoint() which is the entry point for running the top capsule TLSystem. The top capsule can in turn spawn new threads for running other capsules in the realtime application as required, but in the Qt sample application we don't need to do this. So in the general case we have one UI thread and one or many realtime threads. The next step is to look at how to realize the communication between these threads.

Thread Communication

In most cases there is a need for communication in both directions:

1) From the UI thread to a realtime thread

Example: A button is pushed in the UI, and this should lead to the sending of an event on a capsule port.

2) From a realtime thread to the UI thread

Example: An event sent out from a capsule port should cause an image to be changed in the UI.

For communication from the UI thread to a realtime thread (1) we can use external ports. An external port on a capsule let's code that runs in a different thread raise an event on the port in a thread-safe way. It's also possible to pass data from the other thread to the capsule. Read more about external ports in the documentation (chapter "External Port Service").

In the Qt application we use an external port on the PushButton capsule. It has a public function that is called from MainWindow::onButtonClicked():

void PushButton_Actor::onClicked() {
    external.raise();
}

Note that the PushButton capsule must first enable the external port by calling enable() on it. An external port that has been enabled by the capsule can receive exactly one raised event. It then has to be enabled again before another raised event can be received. This ensures that the capsule is in full control over when it's ready to receive events on the external port.

Also note that it's necessary to provide the UI code with a reference to the PushButton capsule instance so that it can call onClicked() on it. This happens in the initial transition of the PushButton capsule where it calls MyThread::registerPushButton().

For communication from the realtime thread to the UI thread (2) we need to post an event to the UI event loop. When that event gets processed by the UI thread the UI can be updated safely. Different UI frameworks has different mechanisms for doing this, and in Qt we can use signals and slots. When a signal is emitted and there is a slot connected to that signal an event is posted in the receiving thread's event loop which tells that the slot should be invoked.

In the Qt sample application signals are defined on MyThread and slots on MainWindow. Signals are bound to slots in the constructor of MainWindow. This means that when a signal is emitted from the realtime thread, the slots that are bound to it will execute in the UI thread.

Build the Sample Qt Application

The sample Qt application is designed to be built from the Qt Creator IDE which means that Model RealTime only generates the C++ code without building it. However, with few changes you could also choose to build the realtime code as a library from Model RealTime and then use that library from the Qt executable.

It should be mentioned that the Qt sample application works also with code generated from Code RealTime. If you want to use Code RealTime, the workspace folder is located here.

Follow the steps to build the sample with with Model RealTime or Code RealTime.