[PyQt] Confusion about load.uic() and import regarding pyinstaller

Maurizio Berti maurizio.berti at gmail.com
Wed Mar 13 03:39:14 GMT 2019


Il giorno mar 12 mar 2019 alle ore 20:28 Hans Jörg Maurer <hjm at pmeonline.net>
ha scritto:

> Here the link to the SO Question mentioned in the mail below:
>
> https://stackoverflow.com/questions/46515179/subclassing-of-widgets-which-are-loaded-by-uic-loadui
>

After many years of playing around with PyQt I've found "my" optimal setup
for a good environment allowing simpler coding and integration with .ui
files created within Designer. I'd also like to add that the documentation
about this is not always clear, and it sometimes "makes sense" only
whenever you've completely understood how Qt, PyQt and the whole GUI
environment works.

Let me share how I usually work.

- All main windows (QMainWindow and QDialog, but QWidget too) are only
saved to .ui files, and I load their ui through loadUi(). While "building"
with pyuic might be better for various reasons (paths, mainly), working
with plain raw ui files avoids confusion: sometimes (a lot of times, in my
case, it can be simple fatigue or distraction after hours of coding) you
edit a widget in Designer and then you forget that you've to recreate the
python ui file, then after minutes of trying to understand why something is
not working as it should, you remember... This is annoying, and makes you
lose a *lot* of time.
- Due to the previous point, all windows that use an .ui file only have to
inherit from their base Qt class; the only drawback is that you've to be
careful to never use object names that might conflict with python naming
standards and QWidget/QObject property/function names: for example,
"style", "cursor" or "layout". The huge advantage is that you don't need to
refer to the "self.ui", and everything is transparently available as a
property/method of the main widget class.
- All custom widgets of a GUI loaded from an .ui file that require some
sort of customization that is not a public function or cannot be easily
"fixed" through an eventFilter are "promoted widgets".

The last point is probably the one that creates more confusion above
everything else. But, while it might seem an annoying thing to do, once
you've got the hand of it, it's fairly easy to implement a widget just by
subclassing only what you need.

Let's assume you've a QMainWindow with a QListView for which you only need
to print out drop events, and let's assume you'll use "myprogram.py" as the
file for your python program.

- Create a QMainWindow in Designer with a list view, and ensure that the
dragDropMode is set to DropOnly or DragDrop.
- Right click on the view and select "Promote to..."
- Type the class name, let's say "MyListView", and in the "Header file"
field type "myprogram" (the name you'd use if you were to import the main
python code file)
- Click "Add", then "Promote"
- save the ui file in the same directory the myprogram.py is
- And that's the only code you'll need, avoiding any eventFilter or
whatsoever:

class MyListView(QtWidgets.QListView):
    def dragEnterEvent(self, event):
        # to get a dropEvent, both the dragEnterEvent and
        # dragMoveEvent (which is fired right after) have to
        # be accepted!
        event.accept()
    def dragMoveEvent(self, event):
        event.accept()
    def dropEvent(self, event):
        print('Something has been dropped in here!')

class Window(QtWidgets.QMainWindow):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        loadUi('mywindow.ui', self)
        # the list view (and its properties/methods) is available as
        # a direct attribute of the main class

  self.listView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)


Still considering the small naming "drawbacks" and path issues listed
before, this allows you to have some clean code, get simple and logical
references to all widgets and still implement custom widgets in an easy way.
For example, you can add a QPushButton and implement its paintEvent only,
or a QLabel and allow it to react to mousePressEvent by sending a custom
signal.

A couple of considerations:
- Whenever you'll use a promoted widget, PyQt will "silently import" the
file set as the "header"; if you've some processing in those files, they
will be executed everytime an ui (and its promoted widget) is loaded.
- Paths: loadUi() doesn't behave according to the path of the running
program, but from where it's loaded. I'm using cxFreeze to build Windows
and MacOS binaries for some projects and I had to create my own loadUi
function that builds and returns the ui object (I don't need the actual ui
object, I could've ignored that, but that's not the point):

def loadUi(uiPath, widget):
    current = path.dirname(path.abspath(__file__))
    #fix for cx_freeze
    if current.endswith('\\library.zip\\myproject'):
        current = current.replace('\\library.zip', '')
    elif current.endswith('/library.zip/myproject'):
        current = current.replace('/library.zip', '')
    return uic.loadUi(path.join(current, uiPath), widget)

While you can ignore the ifs if you're not using similar building systems,
the path relocation is necessary, since uic will try to load the file from
the path the program is ran from, which is not good.

Hope this helps :-)

Maurizio


-- 
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20190313/12b5d758/attachment-0001.html>


More information about the PyQt mailing list