Struggling with basic class structure
-
wrote on 5 Apr 2025, 12:34 last edited by
Hello,
I'm struggling with my project's class structure. Consider the following simplified UML diagram:
The main window has 2 widgets. The SettingsWidget has input fields and sends data to the PlotModel, which then sends data to the PlotWidget (custom widget using qcustomplot) for plotting. What I'm struggling with is
- Which class should own the PlotModel: MainWindow, PlotWidget or a Singleton?
- Who sets the callbacks between the model and the widgets? And in which function are they set? If PlotWidget owns PlotModel, setting the callbacks to SettingsWidget seems difficult
- PlotModel derives from QObject. Is this correct?
- Currently the SettingsWidget's elements are defined in MainWindow.ui and SettingsWidget has input fields as members and populates them with
findChild
. This is bad, right? Should all custom widgets have their own .ui file?
I searched the documentation at https://doc.qt.io/qt-6/ but I'm not really sure where to start. Any advice?
-
From what you're describing you've got 4 classes that should be kept separate and not depend on each other, other than a model interface. Each of them should be able to be created and work in separation i.e. a model doesn't require UI to exist and a UI displays empty space without a model, but is otherwise functional. You can look at Qt's own model view architecture for inspiration - models and views are independent and interchangeable and talk to each other only through an abstract interface. Similarly good UI is comprised of separate widgets that you can put in and take out without modifying other parts (apart from the parent that creates them). Each should be an independent entity.
That being said SettingsWidget should be a separate class (and .ui) from MainWindow. PlotModel should know nothing about any other part of the system, PlotWidget and SettingsWidget would be independent classes that talk to the model via abstract interface (one mostly reading, the other mostly writing). So in pseudo code this should look something like this:
// make separate objects mainWindow = new MainWindow; plotModel = new PlotModel; plotWidget = new PlotWidget; settingsWidget = new SettingsWidget; // connect views to the model plotWidget->setModel(plotModel); settingsWidget->setModel(plotModel); // setup UI //addSubwindow is not a real thing. Could be a central widget or a dock, whatever you need mainWindow->addSubwindow(plotWidget); mainWindow->addSubwindow(settingsWidget);
As to who should own all the objects and where that code exists - in a larger apps there's usually some overarching controlling object. In smaller projects MainWindow class often does the double duty of being both a window and that controlling object. It's not exactly the purest solution from design standpoint, but multiplying entities just for the sake of it is not either.
If you want a separate controlling object you can always make it a stack allocated object inmain()
, like the QApplication instance. Or you could even subclass QApplication and make it be the controlling object. Up to you really. -
wrote on 7 Apr 2025, 16:18 last edited by
Thank you, that was helpful. I read the chapters on Models, Views and Delegates in https://doc.qt.io/qt-6/model-view-programming.html and will redesign my GUI according to these patterns.
-
wrote on 8 Apr 2025, 06:57 last edited by
I mostly agree with @Chris-Kawa. However, instead of calling
setModel()
I would rather suggest that PlotWidget, PlotModel and SettingsWidget are just connected through signals and slots and don't hold pointers to each other (we're talking about Qt here). Most of the time I would just do the connects inside the constructor of MainWindow. If there are too many connects I might refactor the connects into a seperate function (e.g.init()
) called from the MainWindow constructor. If you are going with a controlling object as @Chris-Kawa suggests, this would go in there. However, the Controller in MVC is just connecting signals and slots in Qt. This can be done in a single function and is more procedural than object oriented. I don't really see a point in creating a separate class for this, unless the Controller has more things to do. -
wrote on 8 Apr 2025, 12:51 last edited by
Hi @yannik131,
if there is not much more complexity than your couple classes, you can simple let your main class (i.e. the
QMainWindow
) hold and manage everything else that is going on. Like @SimonSchroeder said above, you don't even need a controller or something.I would pick a hierarchy like this:
- MainWindow
- SettingsWidget (could be
QDialog
) - PlotWidget
- PlotModel (here)
- PlotModel (or here)
- SettingsWidget (could be
QMainWindow
as main entry point to your GUI. From there you can show your dialog to modify your data (PlotModel
) and show/hide the actual plot. This model (and its raw data) can be stored inPlotWidget
itself or also as member in yourMainWindow
. - MainWindow
-
@SimonSchroeder I'd say it heavily depends on a use case. Connections are part of an event driven architecture - something happens and something else reacts to that. They are not really meant to be replacement for simple getters and setters, typical for a data model (think
data()
,setData()
orparent()
members of a model). Replacing a getter with a signal/slot connection is also particularly awkward, because you can only do it by passing a reference, but you don't really know how many slots will be connected to it and set it (if any). It also complicates further with connections across threads.QAbstractItemModel
has around 40 functions as part of its interface. Depending on what a model actually does it might be ok or a terrible idea to replace that with connections. The overhead is maybe not large (especially with direct connections), but can become significant if the model is accessed frequently (think something like a logger and a view that plots some measurement realtime). A typical view in Qt's framework with moderate number of items in a model will make hundreds of calls todata()
to get various aspects for displaying content. I wouldn't say replacing it with connects is a good idea. Also managing all of those connections can be really annoying.To be honest personally I'd much rather a
setModel
that sets a single pointer (you can store it in aQPointer
if you want auto-nulling) than juggling 40 connections. I do recognize that it is a matter of preference though. -
@SimonSchroeder I'd say it heavily depends on a use case. Connections are part of an event driven architecture - something happens and something else reacts to that. They are not really meant to be replacement for simple getters and setters, typical for a data model (think
data()
,setData()
orparent()
members of a model). Replacing a getter with a signal/slot connection is also particularly awkward, because you can only do it by passing a reference, but you don't really know how many slots will be connected to it and set it (if any). It also complicates further with connections across threads.QAbstractItemModel
has around 40 functions as part of its interface. Depending on what a model actually does it might be ok or a terrible idea to replace that with connections. The overhead is maybe not large (especially with direct connections), but can become significant if the model is accessed frequently (think something like a logger and a view that plots some measurement realtime). A typical view in Qt's framework with moderate number of items in a model will make hundreds of calls todata()
to get various aspects for displaying content. I wouldn't say replacing it with connects is a good idea. Also managing all of those connections can be really annoying.To be honest personally I'd much rather a
setModel
that sets a single pointer (you can store it in aQPointer
if you want auto-nulling) than juggling 40 connections. I do recognize that it is a matter of preference though.wrote 30 days ago last edited by@Chris-Kawa said in Struggling with basic class structure:
They are not really meant to be replacement for simple getters and setters
Agreed. You are right about the QAbstractItemModel. I was thinking more along the lines of simple UI elements connecting
valueChanged(...)
signals andsetValue(...)
slots or evenQPushButton::pressed()
. For those I advocate to just connect those signals and slots. E.g.SettingsWidget
might connect the slots of individual UI elements to its own slots (likecountChanged(int)
,nameChanged(QString)
, ...) which the MainWindow can then connect to the PlotModel.
1/7