Sign up for the KDAB Newsletter
Stay on top of the latest news, publications, events and more.
Go to Sign-up


Find what you need - explore our website and developer resources
19 March 2026
A while back I was writing a program that could instantiate an arbitrary number of windows containing controls synchronized across them. How a control would be synchronized would depend on a condition that determined which window instances would be linked to other instances. There are a few ways this could be implemented. In this entry I'll share my approach, in which I used a singleton C++ class serving as a message broker to bind properties across window instances.
Properties bound across multiple instances of a window
The software I wrote this for is a small app that allows you to have many windows open, all synchronized to display a single color on a per-monitor basis. The idea behind this is for users to adjust the light that comes off their monitors and use it to illuminate their faces when recording a video or taking pictures. By having individual windows be synchronized, users can continue to interact with the computer through their displays (rather inconveniently), while simultaneously using them to illuminate themselves.
This is, by no means, a replacement for a proper recording setup. Some of you will know that a light source placed in front of the subject can serve as either a nice "fill light" or a "scary spotlight", depending on the height and size of the source. Therefore, this should be complemented with other sources of light; ideally ambient light and a top light, to achieve a nice look. If the app seems useful to you, there's a link to it at the end of this article. That's enough gaffer speak for today. Let's talk about the code.
When writing code, one of my main concerns is always long-term maintainability. For that reason, I prefer to connect different parts of code in explicit, and easy to follow ways. One of such ways is passing values through a hierarchy of components; that is generally easy to track and produces well performing code. However, that approach can become unviable when connecting dynamically instantiated items to other dynamically instantiated items. A better solution in this instance is to use signals and slots to interconnect the items via a message broker class, done in C++. Each item would have a model or backend class in C++ and those classes would have the message broker in common. Lastly, the properties would be exposed to QML through Q_PROPERTY and updated via your control's signal handlers.
The message broker needs to be a singleton. That way there's only one instance of the broker in memory and all instantiated objects interface with the same broker. Our message broker only needs to provide the signals that will be used for routing properties. The actual connections that the routing involves are to be done from the outside. As such, a broker class would look like this:
// internalmessagebroker.hpp
// Singleton broker class contains signals that serve as pipes
// for different parts of a program to communicate with each other.
#pragma once
#include <QObject>
class InternalMessageBroker : public QObject
{
Q_OBJECT
// Hide regular constructor
private:
InternalMessageBroker() = default;
public:
// Disable copy constructor
InternalMessageBroker(const InternalMessageBroker& obj) = delete;
InternalMessageBroker& operator=(InternalMessageBroker const&) = delete;
static std::shared_ptr<InternalMessageBroker> instance()
{
static std::shared_ptr<InternalMessageBroker> sharedPtr{new InternalMessageBroker};
return sharedPtr;
}
// This is where all the signals would go
signals:
void broadcastAPropertyChange(int value, bool broadcast);
}; Then we have the class or classes that would connect the properties together. On my Display Panels app, all controls and visual features come from QML, meaning I only have to concern myself with interconnecting the properties. To that end, I've created a class based on QObject and instantiated it within the delegate of a QML Instantiator. This class is only for managing property data, so I refer to it as a model class, called PropertiesModel:
// Main.qml
// Here are 3 windows, each with a PropertiesModel,
// that allows them all to share a binding to aProperty
// across all window instantiatons.
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls
import com.kdab.example
Item {
Instantiator {
model: 3
delegate: Window {
PropertiesModel {
id: propertiesModel
aProperty: 1
}
ColumnLayout {
anchors.fill: parent
Text {
text: propertiesModel.aProperty
}
Slider {
id: hueSlider
value: propertiesModel.aProperty
Layout.fillWidth: true
onMoved: {
propertiesModel.aProperty = value;
}
}
}
}
}
} Each property of our class is to be declared using a Q_PROPERTY macro with a getter, a setter, and a notifier. The getter and the notifier have nothing out of the ordinary...
// An ordinary getter
int PropertiesModel::aProperty()
{
return m_aProperty;
} // An ordinary notifier, form propertiesmodel.h
signals:
void aPropertyChanged(); However, the setter must be able to distinguish between when its call is the product of a user interaction and when it comes from the message broker. We accomplish this by having the setter accept a boolean argument (broadcast) that will be used to determine whether the value being set should be sent through the message broker or notified back to QML for the UI to be updated. When a setter is first called, the value being set should be broadcasted and only on its way back should the UI be updated.
There are a few ways we could make sure that the value is always broadcasted first. We could set broadcast to true by default, or have two setter functions: a private one taking in both the value and broadcast arguments and a public one that only takes-in the value and calls the private function with broadcast set to true. Another option is to have broadcast be an enum with only two possible values. That would produce more readable code, however, I didn't worry about that in my code because broadcast is only to be used on calls to the private setter.
Upon the private setter being called by the public setter, it will emit the broker's broadcast signal and that signal will in turn call the calling private setter for a second time (as well as the private setters of all other instances of PropertiesModel; those being called for the first time). When the private setter calls the broker that calls back to the private setter, it also passes broadcast set to false. All other instances will then evaluate the value of broadcast to be false determining that the UIs should be updated and preventing an infinite loop.
// Private setter implementation for a property that's being broadcasted
void PropertiesModel::setAProperty(const int value, const bool broadcast)
{
if (broadcast)
emit m_broker.get()->broadcastAPropertyChange(value, false);
else if (m_currentScreen == screenName) {
m_aProperty = value;
emit screenSaturationChanged();
}
}
// Publicly exposed setter prevents the broadcast argument from being
// specified by other callers
void PropertiesModel::setAProperty(const int value)
{
// Always broadcast properties not being set by the message broker
setAProperty(value, true);
} To complete this loop as described, we need to connect the signal from the broker to the private setter of the PropertiesModel class whenever a new copy is instantiated. The best place to accomplish that is from the class' constructor, like so:
// The constructor is used to connect signals from the broker to setter properties
PropertiesModel::PropertiesModel(QObject* parent)
: QObject { parent }
{
m_mb = InternalMessageBroker::instance();
// Connections take place after the class has been instantiated
// and its QML properties parsed.
QTimer::singleShot(0, this, [this] () {
connect(m_mb.get(), &InternalMessageBroker::aPropertyChange,
this, &ScreenModel::setAProperty);
});
} If you've been reading the blocks of code that accompany this article, you may be wondering "Why is there a singleShot QTimer there?" When the PropertiesModel class is instantiated via QML, any connected properties that have values assigned from QML code will trigger the Q_PROPERTY's setter function. This will happen once per instantiation. If the broker were connected, it would broadcast the value set to all currently instantiated instances every single time that a new instance is added. To prevent that, we must not make the connection to the broker until after a class has been fully initialized. A single shot timer can be used to accomplish that; setting its delay to 0 will ensure that it is run immediately after all properties have been evaluated.
In the end, this is what the PropertyModel header would look like:
// propertiesmodel.h
#pragma once
#include "internalmessagebroker.hpp"
#include <QQmlEngine>
class PropertiesModel : public QObject {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int aProperty READ aProperty WRITE setAProperty NOTIFY aPropertyChanged FINAL)
public:
explicit PropertiesModel(QObject* parent = nullptr);
int aProperty();
void setAProperty(const int value);
signals:
void aPropertyChanged();
private:
void setAProperty(const int value, const bool broadcast);
std::shared_ptr<InternalMessageBroker> m_mb;
int m_aProperty;
}; If a condition must be met for the propagated value to result in an update, then, in addition to value and broadcast, you should also broadcast any other values that are required to for such condition to be met. Pass those as arguments to the setter's and the broker's signal. Condition validation would then take place within the base case of the private setter. Here's a commented snippet of what that looks like on the Display to Light Panels app that this article was based on:
// In the original code what here I named broadcast used to be named spread.
void ScreenModel::setScreenHue(const int hue, const bool spread=true, const QString &screenName="s")
{
if (spread)
emit m_mb.get()->spreadHueChange(hue, false, m_currentScreen);
// The incoming value is only accepted if screenName matches the screen
// that the window is at and discarded otherwise.
else if (m_currentScreen == screenName) {
m_screens[m_currentScreen].hue = hue;
emit screenHueChanged();
}
} Properties bound across instances of a window
For a real world application, you can read the code for Display to Light Panels, the app that inspired this article, at: https://github.com/Cuperino/Display-to-Light-Panels
If you need help solving architecture problems, such as this one, reach out to us and we will gladly find ways in which we can help.
About KDAB
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Upgrade your applications from Qt 5 to Qt 6 with KDAB’s migration services. Get a free migration assessment and join a hands-on workshop to prepare your team for a successful transition!
Learn more
Learn Modern C++
Our hands-on Modern C++ training courses are designed to quickly familiarize newcomers with the language. They also update professional C++ developers on the latest changes in the language and standard library introduced in recent C++ editions.
Learn more