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

Hans Jörg Maurer hjm at pmeonline.net
Wed Mar 13 05:38:38 GMT 2019


@ Maurizio

Thank you so much for clearing the nodal in my brain. Beautiful music
you make, btw. 

Regards Hans









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 [1]

















Links:
------
[1] http://www.jidesk.net/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20190313/33929df9/attachment.html>


More information about the PyQt mailing list