1

Following a few tutorials, I've managed to pull together this small Python program that successfully pulls in an .ui file and shows it on screen:

from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QDialog, QMessageBox, QVBoxLayout
from PySide2.QtWidgets import QPushButton, QLineEdit
from PySide2.QtCore import QFile, Slot
from PySide2.QtUiTools import QUiLoader

class Dialog(QDialog):
    def __init__(self, parent = None):
        super(Dialog, self).__init__(parent)
        print('hey')

    def alert(self):
        print('Alert')

if __name__ == '__main__':
    app = QApplication([])

    loader = QUiLoader()
    loader.registerCustomWidget(Dialog)

    ui_file = QFile('alert-quit.ui')
    ui_file.open(QFile.ReadOnly)
    dialog = loader.load(ui_file)
    ui_file.close()

    dialog.show()

The dialog shows correctly, but I get the error QObject::connect: No such slot QDialog::alert() and the button does nothing. (The hey text is not shown either.)

The .ui file contains the definition of a QDialog with a signal from the "Alert" button:

enter image description here enter image description here

I'm not sure about what the registerCustomWidget(), from another reply it seemed to be the thing to do. Sadly, the official documentation fails what how circle.ui contains.

And the official documentation on loading .ui files does not show how to interact with the items defined in the .ui file itself.

How can I load a full QDialogfrom an .ui file and get the the buttons in it to trigger actions in my code?

P.S.: Since I cannot attach an .ui file, here is its XML:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>344</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLineEdit" name="lineEdit"/>
   </item>
   <item>
    <widget class="QPushButton" name="alert_button">
     <property name="maximumSize">
      <size>
       <width>100</width>
       <height>16777215</height>
      </size>
     </property>
     <property name="text">
      <string>Alert</string>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QPushButton" name="quit_button">
     <property name="maximumSize">
      <size>
       <width>100</width>
       <height>16777215</height>
      </size>
     </property>
     <property name="text">
      <string>Quit</string>
     </property>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>alert_button</sender>
   <signal>clicked()</signal>
   <receiver>Dialog</receiver>
   <slot>alert()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>91</x>
     <y>30</y>
    </hint>
    <hint type="destinationlabel">
     <x>133</x>
     <y>51</y>
    </hint>
   </hints>
  </connection>
 </connections>
 <slots>
  <slot>alert()</slot>
  <slot>quit()</slot>
 </slots>
</ui>
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
a.l.e
  • 792
  • 8
  • 16
  • ok, i did not stop experimenting and [this post](https://stackoverflow.com/questions/52128188/using-a-custom-pyside2-widget-in-qt-designer), with some memories of posts i've been reading yesterday, helped me getting in the good track. very soon i will be able to answer my own question : - ) – a.l.e Oct 09 '18 at 07:23
  • no, still not here. now i can get the signal to be linked to the function in _my_ python class, but i still cannot reference the text field in the dialog from the _slot_ function... any idea? – a.l.e Oct 09 '18 at 07:29
  • The question that you point out as a reference in your comment goes in another direction. On the other hand in PySide2 there is no loadUI method that performs this type of tasks, the idea is to implement this method which involves parsing the .ui and get all the elements and make the connections – eyllanesc Oct 09 '18 at 07:58
  • thanks @eyllanesc i got it to work by slightly manually tweaking the `.ui`. i'm writing right now an answer to my own question and i will post it very soon. up to the tweaking of the `.ui`, it looks like a clean anser... but i welcome further comments and improvements (most of all if i can avoid the hack in the `.ui` file!) – a.l.e Oct 09 '18 at 08:01
  • you will have to parse the file, that is what PyQt5 does, I recommend you to review it so that you understand the logic. – eyllanesc Oct 09 '18 at 08:03
  • @eyllanesc, as you see in my answer below, Qt for Python seems to be able to parse it by itself... or i have made a big mistake that only works because i am very very lucky : - ) – a.l.e Oct 09 '18 at 08:16

2 Answers2

2

As I wrote in the comments to my question, I've found a working solution. I'm not 100% sure, it's the correct one (at least for the part relating to the manual editing the .ui file) but I get the dialog to show up and behave the way I was expecting.

First I had to modify the XML in the .ui file and use two different class names in the .py and .ui files. I've modified the one in the .ui to be named AlertDialog and to be of type Dialog instead of QDialog. I could not find a way to modiffy the type in Qt Designer.

3,4c3,4
<  <class>Dialog</class>
<  <widget class="QDialog" name="Dialog">
---
>  <class>AlertDialog</class>
>  <widget class="Dialog" name="AlertDialog">
18c18
<     <widget class="QLineEdit" name="lineEdit"/>
---
>     <widget class="QLineEdit" name="text_field"/>
66c66
<    <receiver>Dialog</receiver>
---
>    <receiver>AlertDialog</receiver>

As you see above, there was also a small typo in the name of the input box: that was the reason why, I got the .ui file pasted in the question!).

In the Python code I simply had to correctly decorate my function as a Slot. Here is the full code:

from PySide2.QtWidgets import QApplication
from PySide2.QtWidgets import QDialog, QMessageBox
from PySide2.QtCore import QFile, Slot
from PySide2.QtUiTools import QUiLoader

class Dialog(QDialog):
    def __init__(self, parent = None):
        super(Dialog, self).__init__(parent)

    @Slot()
    def alert(self):
        alert = QMessageBox()
        alert.setText(self.text_field.text())
        alert.exec_()


if __name__ == '__main__':
    app = QApplication([])

    loader = QUiLoader()
    loader.registerCustomWidget(Dialog)

    ui_file = QFile('alert-quit.ui')
    ui_file.open(QFile.ReadOnly)
    dialog = loader.load(ui_file)
    ui_file.close()

    dialog.show()
    app.exec_()

Neat, isn't it? If anybody (my future self included...) knows how to get rid of the .ui tweaking or has otherwise improvements to this solutions, your comments are very welcome!

a.l.e
  • 792
  • 8
  • 16
  • I continue my monologue. While the code in this solution is really clean and compact, creating an executable (as an example with PyInstaller) gets trickier. For now, if you want to distribute your code, you should probably stick to the good old conversion of the `.ui` code in Python code with `pyside-uic`. – a.l.e Oct 11 '18 at 13:49
0

since I struggled a lot with Pyside2 as well, I share here how I managed this, I'm not sure it's the best way to proceed, but it works for me.

First you have to install pyqt5 with :

pip3 install pyqt5

And assuming that your file is the following :

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>344</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QLineEdit" name="lineEdit"/>
   </item>
   <item>
    <widget class="QPushButton" name="alert_button">
     <property name="maximumSize">
      <size>
       <width>100</width>
       <height>16777215</height>
      </size>
     </property>
     <property name="text">
      <string>Alert</string>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QPushButton" name="quit_button">
     <property name="maximumSize">
      <size>
       <width>100</width>
       <height>16777215</height>
      </size>
     </property>
     <property name="text">
      <string>Quit</string>
     </property>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>alert_button</sender>
   <signal>clicked()</signal>
   <receiver>Dialog</receiver>
   <slot>alert()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>91</x>
     <y>30</y>
    </hint>
    <hint type="destinationlabel">
     <x>133</x>
     <y>51</y>
    </hint>
   </hints>
  </connection>
 </connections>
 <slots>
  <slot>alert()</slot>
  <slot>quit()</slot>
 </slots>
</ui>

You just type the command :

pyuic5 path_to_your_ui_file -o path_to_the_output_python_file # don't forget the extension .py for the output

There is normally the output :

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'test.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(344, 300)
        self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
        self.verticalLayout.setObjectName("verticalLayout")
        self.lineEdit = QtWidgets.QLineEdit(Dialog)
        self.lineEdit.setObjectName("lineEdit")
        self.verticalLayout.addWidget(self.lineEdit)
        self.alert_button = QtWidgets.QPushButton(Dialog)
        self.alert_button.setMaximumSize(QtCore.QSize(100, 16777215))
        self.alert_button.setObjectName("alert_button")
        self.verticalLayout.addWidget(self.alert_button)
        self.quit_button = QtWidgets.QPushButton(Dialog)
        self.quit_button.setMaximumSize(QtCore.QSize(100, 16777215))
        self.quit_button.setObjectName("quit_button")
        self.verticalLayout.addWidget(self.quit_button)
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout.addItem(spacerItem)

        self.retranslateUi(Dialog)
        self.alert_button.clicked.connect(Dialog.alert)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
        self.alert_button.setText(_translate("Dialog", "Alert"))
        self.quit_button.setText(_translate("Dialog", "Quit"))

All you have to do is to correct the imports and load the generated file :

from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import QFile, QObject 
import mainwindow  # if your generated file name is mainWindow.py

class MainDialog(QDialog, mainwindow.Ui_Dialog):

    def __init__(self, parent=None):
        super(MainDialog, self).__init__(parent)
        self.setupUi(self)

app = QApplication(sys.argv)
form = MainDialog()
form.show()
app.exec_()
RMPR
  • 3,368
  • 4
  • 19
  • 31
  • 1
    well, here you are using pyqt5 and not pyside2... and you're using the `uic` tool to convert the `.ui` file in Python code. this works also with pyisde2. as i discovered later, this also works with pyside2. but my goal was _not_ to convert first the file.... : - ) – a.l.e Oct 25 '18 at 17:01
  • " get rid of the .ui tweaking" this part of your answer confused me, but I got your point, thanks I didn't knew the existence of uic tool for pyside2 – RMPR Oct 26 '18 at 12:46
  • well, the idea is to get rid of the .ui tweaking **and** keep the _dynamic_ loading of the .ui file : - ) – a.l.e Oct 26 '18 at 14:13