Using Qt Designer

The Qt Designer application provides a graphical interface to generate a templated layout of Qt widgets that can be used in an application. Two slightly different approaches have been successful at WMKO for deploying the generated .ui XML templates. A sample .ui file is available, refer to the examples for information on how to interface the template with Python code.

Preprocessed with pyuic

The pyuic application will translate a .ui XML file into a Python class. Note that it may be installed with a version-specific suffix, like pyuic4 or pyuic5, as many package management systems are prepared to have multiple versions of Qt installed. The members of the class will be appropriate PyQt widgets, with relative names in the class matching the original naming of the widgets in Qt Designer. These Python files can be generated via a kroot Makefile, and installed as a Python library that can be imported for use by the GUI application. Here is an example section from a Makefile, working with two design templates, Main and Rotator:

DESIGNER = Main.ui Rotator.ui
SANITIZED = $(DESIGNER:%.ui=%_sanitized.ui)
SINITIZED = $(SANITIZED:%=%.sin)
TRANSLATED = $(DESIGNER:%.ui=%.py)

RELLIB = __init__.py $(TRANSLATED)
LIBSUB = python/nirspecQt

RELDAT = $(wildcard *.png)
DATSUB = nirspec/images

FILES = $(SANITIZED) $(SINITIZED) $(TRANSLATED)


$(TRANSLATED): %.py: %_sanitized.ui
    pyuic $^ --output $@

# prefix *.png with @RELDIR@/data/@DATSUB/

$(SINITIZED): %_sanitized.ui.sin: %.ui
    $(SED) 's/<pixmap>\(.*\).png<\/pixmap>/<pixmap>@RELDIR@\/data\/@DATSUB@\/\1.png<\/pixmap>/;s/<normaloff>\(.*\).png<\/normaloff>/<normaloff>@RELDIR@\/data\/@DATSUB@\/\1.png<\/normaloff>/' < $^ > $@ || $(RM) $@

Some of these macro definitions are kroot-specific and have implications for which files will be installed where. In particular, note the definition of DATSUB here, which effectively defines a Python module name; the kpython interpreter establishes environment variables that automatically search $RELDIR/lib/python when importing modules in Python code. For this example, the GUI application would import nirspecQt to gain access to the templated designs.

The last target rule is doing something relatively exotic. In this rule the relative path names for unique .png files included in the design are remapped to their actual install location in $RELDIR/data. So, the generation of the .py file has dependencies:

.ui -> _sinitized.ui.sin            # remapped .png paths
_sinitized.ui.sin -> _sanitized.ui  # substitued for @MACRO@ values
_sanitized.ui -> .py                # final generation of pyuic .py files

The nice part about getting all this done in advance is that you don’t have to worry about any of these pathing or potential translation issues at runtime, you can assume you already took care of it at build/install time.

Run-time translation

The PyQt5.uic.loadui() method will effectively do the same thing as pyuic, but instead of the template design being an external module that you bring in, PyQt5.uic.loadui() will monkey-patch an existing QWidget with the contents of the template.

One of the benefits of using this approach is that it’s more amenable to IDE-based development, in that you can avoid the extra make install to invoke pyuic on the command line. The trade-off is that your code has to work a little harder to manipulate the contents of the template on the fly.

Here is an example sequence for the just-in-time ingestion of the design template:

def setupUi(self):
    reldir = os.environ['RELDIR']
    filename = os.path.join(reldir, 'data', 'nirspec', 'Main.ui')
    PyQt5.uic.loadUi(filename, self)