what's the best practice to write pure c++ widget without ui form?
-
I have used qt for over 1 year, recently I found that it is quite annoying to change ui, the qt designer is so hard to use, especially when the ui file I want to change contains too many widgets, and sometimes I want to put some contents to another ui file, there is no quick short key to help me do that.
Now I want to change to use pure c++ to write ui, for me, this way is easier to refactor my code, and friendly to code review.
Could someone give me any advices on how to do that? -
@aiyolo said in what's the best practice to write pure c++ widget without ui form?:
Suppose that A Widget contains B widget, and B widget contains C widget..., then If I want to query the state of each of subwidget of C widget state in class A, I need to write functions for each query.
Well, Qt makes it easy to just query the data from the widgets directly. This, however, is not necessarily the intended way. There is a pattern called MVC (model/view/controller). Following that pattern your widgets are just the view and you would have separate variables for your model. In Qt this would mean that for each QLineEdit::textChanged, QPushButton::clicked, etc. signal you would have a signal in your classes B and C that forward each change through a connect. Class A can then have slots for these signals connected which will change the variables of the model. You would then only query the values from the model. However, this is a lot of boilerplate code to write.
But why are we writing getters/setters (or signals/slots in this case) in the first place? This is just the cleanest for of object oriented programming. With C++ OOP is not the only tool in your belt. Bjarne Stroustrup (the inventor of C++) advocates to just use public member variables instead of writing getters/setters. The Qt-generated ui-classes (from .ui files) also do this! So, just make your variables public and then you can access e.g. a line edit in class C via
a->b->c->lineEdit
. If you still want to restrict general acces, usefriend
classes instead.findChild()
has also already be mentioned. If you successively add subwidget to a widget they will get reparented when using layouts. Then, you can usefindChild()
on the "most parent" widget, e.g.a->findChild<QLineEdit*>("lineEdit")
for a line edit located in class C. I don't really like this option as a general solution because the compiler cannot help you to check if the line edit by that name exists. -
@aiyolo said in what's the best practice to write pure c++ widget without ui form?:
Could someone give me any advices on how to do that?
You open QtCreator or the IDE of your choice and start to write your code.
I mean there's nothing special about this...
If you want a layout containing aQPushButton
, you do something like(assuming you have a plain, empty widget, without any layout)
QHBoxLayout *hbox = new QHBoxLayout(this); QPushButton *pb = new QPushButton("Click me", this); hbox->addWidget(pb); this->setLayout(hbox);
Other things work the same way.
Note: Objects created manually have no objectname, as the Designer would assign to them.
Since you didn't specified your question further I leave you with that. Explaning every little detail just like that would take too long and it's also written in the documentation how to use the Qt modules its classes.
-
Thanks for you reply!
Should I put the code in a header like the auto-generated ui header or just write them in my customed widget class(this way might make my widgets containng too much code)?
And since the Object created manually have no objectname, can I write some function to help us do that? I hope that there a practice that can organize my code and makes my code seems clean.
-
-
Why do you need an object name for every widget at all?
-
@aiyolo said in what's the best practice to write pure c++ widget without ui form?:
And since the Object created manually have no objectname, can I write some function to help us do that?
Take a look at the code that
uic
generated for any UI file. Everything it does is there, including things like translation support, object names, etc... -
On the subject of creating a GUI application in Creator. I only have Creator 5(?)-ish, whatever came with Qt5 under Ubuntu 22.04, but I would guess it's no different in Creator 13+. I create a large number of tiny, standalone application from scratch (often to answer questions here). For a GUI C++ application the only choice in Creator wizards does allow you to uncheck "create
.ui
file", which is welcome, but insists on creating a class derived fromQWidget
or other similar, which in turn is placed into its own.cpp
&.h
files, in addition tomain.cpp
. I use this as a quick way of getting the project set up, with.pro
file. Then the first thing I have to do (assuming I don't want the extra class files) is remove the#include
frommain.cpp
and delete the class.cpp
&.h
files. I wish there were a choice for a "skeletal"/"minimal" UI project application, which just gave you the necessarymain.cpp
and.pro
(doubtless equivalent for cmake) file, no extra class, in just the same way as the "empty" C++ Qt console application wizard does. -
@Christian-Ehrlicher I just don't want to write to much boilerplate code
-
What "boilerplate code" are you asking @Christian-Ehrlicher about? He is merely saying that although the UI Designer auto-creates a unique object name for each widget, which you would have to do in your own code if you wanted to replicate, there are actually few times when a widget needs an object name so why bother, unless you actively use it which you probably do not?
-
@aiyolo said in what's the best practice to write pure c++ widget without ui form?:
I have used qt for over 1 year, recently I found that it is quite annoying to change ui, the qt designer is so hard to use, especially when the ui file I want to change contains too many widgets
This sounds like a case of decomposing at the wrong level. Create usable components in separate ui files, as you would in C++ classes. Lots of copy any paste usually indicates to me that I need to further decompose.
I agree that designer/creator is a little clunky to use. Reproducing the boilerplate output of uic usually convinces me that it's worth suffering through the gui, or manually editing the XML. -
@aiyolo said in what's the best practice to write pure c++ widget without ui form?:
Should I put the code in a header like the auto-generated ui header or just write them in my customed widget class(this way might make my widgets containng too much code)?
It's totally fine to put your UI code for some widget in the widget.cpp and set everything up in widget's c'tor.
And since the Object created manually have no objectname, can I write some function to help us do that? I hope that there a practice that can organize my code and makes my code seems clean.
I just mentioned the objectname because it's one of the differences people tend to forget about. When you are used to using the UI files, switch to GUI coding and there is that edge case where you need the objectname (nice to have), you will notice that it's empty for manually created
QObject
types.E.g.:
If you want to get that one specfic button from your GUI, you can simply doui->objectnameOfButton
everywhere in your class where your
ui->
pointer is available.
Without the UI file and if you didn't stored your objects as member of your class, you can "find" them by iterating the object tree / widget's childs, like:// returns first single match QPushButton *myButton = this->findChild<QPushButton *>(); // returns a list with all child buttons of "this" widget QList<QPushButton *> allMyButtons = this->findChildren<QPushButton *>();
The first line returns the first matched
QPushButton
"child" of the current class.
Can be the button you actually want to address or the button next to it :)
When there are multiple, you have to identify them somehow.
Easiest way would be by its objectname (that's what this function is also using).
So like this:// assuming you've set an objectname before QPushButton *myButton = this->findChild<QPushButton *>("objectnameOfButton"); // which is equal to ui->objectnameOfButton
You don't need an objectname as being said before, but in some situations it might be helpful to have one, even though there are also workarounds for this.
(the need for an objectname can be an indicator of poor or improvable design) -
@Pl45m4 Thanks for you reply!
It's totally fine to put your UI code for some widget in the widget.cpp and set everything up in widget's c'tor.
The only concern for me is that not only ui code that I should put in widget's c'tor, but also signal and slot function I need to write for each sub widget, thus my customed widget will contain thounsands of lines of code. For example, in one of my previous project, although I writed the ui of the mainwindow using qt designer, but there are still thousands of lines of code in my mainwindow class, most of codes are about the logic of user interaction.
-
@aiyolo said in what's the best practice to write pure c++ widget without ui form?:
can I conclude that using gui is more recommended than directly writing the output of uic?
There is no recommendation for this. It is your preference.
"thus my customed widget will contain thounsands of lines of code" - then split the big class in smaller classes. @jeremy_k already pointed out that you can split your UI stuff in several UI components/widgets and then compose them in your main window.
-
Most of the time I also just put everything into the constructor. If you want to clean it up a little bit, refactor widget creation into a separate function, refactor adding widgets to layouts into a separate function, and refactor connecting slots into a separate function. Sometimes these can even be split up into several functions themselves (or you create several separate classes as suggested). Nothing is preventing you from using more than one .cpp file per class. If you got thousands of lines, split them up into several files, e.g. mywidget_connect.cpp containing the function connecting slots for the class MyWidget.
-
@SimonSchroeder said in what's the best practice to write pure c++ widget without ui form?:
If you got thousands of lines, split them up into several files, e.g. mywidget_connect.cpp containing the function connecting slots for the class MyWidget.
Suppose that A Widget contains B widget, and B widget contains C widget..., then If I want to query the state of each of subwidget of C widget state in class A, I need to write functions for each query.
How can I simplify this procedure?
// pseudocode struct Params{ QString param1; QString param2; }; class A: public QWidget{ Params param; public slots: void on_pushbutton_clicked(){ param.param1 = getLineText(); param.param2 = getButtonText(); } public: QString getLineText(){ // here I want to get the text of QLineEdit in Widget c; // it is quite annoying to write so many functions in A, because we have so many child widget } QString getButtonText(){ // here I want to get the text of QPushbutton in Widget c; } }; A a = new A; B b = new B; C c = new C; a->addwidget(b) b->addwidget(c)
-
@aiyolo said in what's the best practice to write pure c++ widget without ui form?:
Suppose that A Widget contains B widget, and B widget contains C widget..., then If I want to query the state of each of subwidget of C widget state in class A, I need to write functions for each query.
Send a signal from C to A which notifies and transmits some information
-
@aiyolo said in what's the best practice to write pure c++ widget without ui form?:
Suppose that A Widget contains B widget, and B widget contains C widget..., then If I want to query the state of each of subwidget of C widget state in class A, I need to write functions for each query.
Well, Qt makes it easy to just query the data from the widgets directly. This, however, is not necessarily the intended way. There is a pattern called MVC (model/view/controller). Following that pattern your widgets are just the view and you would have separate variables for your model. In Qt this would mean that for each QLineEdit::textChanged, QPushButton::clicked, etc. signal you would have a signal in your classes B and C that forward each change through a connect. Class A can then have slots for these signals connected which will change the variables of the model. You would then only query the values from the model. However, this is a lot of boilerplate code to write.
But why are we writing getters/setters (or signals/slots in this case) in the first place? This is just the cleanest for of object oriented programming. With C++ OOP is not the only tool in your belt. Bjarne Stroustrup (the inventor of C++) advocates to just use public member variables instead of writing getters/setters. The Qt-generated ui-classes (from .ui files) also do this! So, just make your variables public and then you can access e.g. a line edit in class C via
a->b->c->lineEdit
. If you still want to restrict general acces, usefriend
classes instead.findChild()
has also already be mentioned. If you successively add subwidget to a widget they will get reparented when using layouts. Then, you can usefindChild()
on the "most parent" widget, e.g.a->findChild<QLineEdit*>("lineEdit")
for a line edit located in class C. I don't really like this option as a general solution because the compiler cannot help you to check if the line edit by that name exists. -