I've let go of the idea of using GUI design tools a long time ago. You quickly want your controls & screens to scale which is easy to do in code but very hard with controls that just sit there at a fixed position.
For what it is worth, I tend to use GtkGrid or QGridLayout for widget layout, much like HTML tables, which scale automatically. I basically never use fixed layouts, as I
need mine to scale according to my varying needs.
I lived through the
"This web site is best viewed in 800x600 resolution" era, and hated every time I encountered that. I didn't do it myself then, and I don't want to do it now. I habitually browse web sites with varying (site-specific!) zoom levels, too. (Right now, for EEVblog forums, on this particular laptop I use 120% with 14pt DejaVu Serif as default font. For other sites, it varies between 90% and 133%. It obviously also varies depending on the display I'm using, and the distance I am at from the display.)
creating a bunch of contols using a for loop is quicker too
True. It really depends on the application, which is my point regarding an example. A synthetic example won't be useful much; we really need a real-world use case for it to matter.
GtkBuilder does support
<template> .ui objects in Gtk+ 3.10 and later, and in both Gtk+ and Qt .ui files you can generate objects (and object hierarchies) and instantiate them multiple times, but I prefer to create Python classes (possibly extending a suitable toolkit widget class) instead. And I have a habit of describing things in dictionaries (in Python; in arrays of structures in C) and instantiating them in a loop.
Whatever makes the code most maintainable, but does not have any significant downsides (like slowdowns or installation quirks), is the best approach in my opinion.
Oddly enough, I had occasion to make just such a tool a few months back. It's a 'data inspector' for signals from an ultrasound system whose purpose it is inconvenient to go into too deeply here. Suffice it to say, the development engineers were having a hard time using the application software, which does some fancy signal processing and presents measurements derived from this processing, to decide if the system was actually working as designed or not.
So I put something together in Python (since rest of the software is in Python) using PyQt5, numpy, scipy & matplotlib. It shows a tabbed interface that lets you look at a signal in the time domain or as a spectrogram, or a whole set of such signals together as an echogram (i.e. like a medical ultrasound image). With features like optional harmonic cursors in the spectrogram, time/frequency/range cursor readout in lots of different modes, and lots of control over the visualisations to bring out different features (or bugs). It's been highly revealing. Also handy to make pretty pictures to show the investors!
Well, visualising signals and filters in time and frequency domains is an important tool in many sub-fields members here would be interested in.
It's also a bit hairy keeping track of the slot/signal connections
When using Qt Designer
.ui files, PyQt5.QtCore.QMetaObject.connectSlotsByName auto-connects
on_widgetName_signalName slots to the named signals. Applying the Principle of Least Surprise, we can do that ourselves with just a couple of lines.
Let's assume we use a custom class, with the widget hierarchy already hanging off the
ui member. (I load this using
self.ui = loadUi("filename.ui"), but it does not matter how you construct it, as long as you use
widget.setObjectName("widgetName") or equivalent to give each signal-generating widget an unique name.)
Connecting all abovenamed functions in the custom class to proper signals requires just a few lines of code, after the
self.ui widget hierarchy has been created (but not shown yet):
for slot in dir(self): if callable(slot) and slot.startswith("on_") and slot.find("_", 3) > 3: widgetName, signalName = slot[3:].split("_", 1) signal = getattr(self.ui.findChild(QtCore.QObject, widgetName), signalName, None) if signal: signal.connect(getattr(self, slot))Alternatively, you can use an array of
("widgetName", "signalName", pythonCallable) tuples, and in a loop over them,
sourceSignal = getattr(self.ui.findChild(QtCore.QObject, widgetName), signalName, None) if sourceSignal is not None: sourceSignal.connect(pythonCallable)To include signal-to-widget-slot tuples of form
("sourceWidgetName", "sourceSignalName", "targetWidgetName", "targetSlotName"), do
sourceSignal = getattr(self.ui.findChild(QtCore.QObject, sourceWidgetName), sourceSignalName, None) targetSlot = getattr(self.ui.findChild(QtCore.QObject, targetWidgetName), targetSlotName, None) if sourceSignal is not None and targetSlot is not None: sourceSignal.connect(targetSlot)instead.
I find these "tricks" make the signal-slot stuff much more maintainable.