I'm writing a tool with GUI, where I inevitably need to use pyqtSlot. I had errors in this tool, related to its usage and decided to try a minimal example. However, I still fail to figure out the problem.
I've read instructions here. My findings for creation of a custom slot for a pushbutton were the following:
In a class, defining my GUI, I need to create a method, decorated as #pyqtSlot();
I need to write something like self.mybtn.connect(self.<method name>) after button creation.
So, I created a UI in QtDesigner, compiled it and came up with the following code:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setObjectName("pushButton")
self.pushButton.clicked.connect(self.react_to_signal)
self.verticalLayout.addWidget(self.pushButton)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
#QtCore.pyqtSlot()
def react_to_signal(self):
print("Button press signal emitted")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
However, when this code is run, it fails for me with the following error:
QObject::connect: Cannot connect QPushButton::clicked(bool) to (nullptr)::react_to_signal()
Traceback (most recent call last):
File "<mypath>/ui_test_slots.py", line 36, in <module>
ui.setupUi(MainWindow)
File "<mypath>/ui_test_slots.py", line 15, in setupUi
self.pushButton.clicked.connect(self.react_to_signal)
TypeError: connect() failed between clicked(bool) and react_to_signal()
Other questions I saw on SO for this problem were caused by usage of the decorated method as a class method, no an instance method. But this doesn't seem to be my case.
What am I missing? What are the real differences between the example I referred to and my code?
Related
This question already has answers here:
QtDesigner changes will be lost after redesign User Interface
(2 answers)
Closed 2 years ago.
I'm new to Python and I've searched for an answer but couldn't find it (or rather couldn't properly implement it).
I've generated a window with a few buttons in QtDesigner's file named "arch.ui", converted to arch.py.
As I'll be updating GUI occasionally, I don't want to create functions in arch.py, so I've created a main.py file for that.
I've a problem with linking button click to a function => I try to link "btnSource" (from arch.py) to function "printMe" (in main.py).
Obviously it doesn't work. Any help welcome.
Here is generated Designer file:
# Form implementation generated from reading ui file 'arch.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(460, 233)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.btnSource = QtWidgets.QPushButton(self.centralwidget)
self.btnSource.setGeometry(QtCore.QRect(80, 60, 75, 23))
self.btnSource.setObjectName("btnSource")
self.lblSource = QtWidgets.QLabel(self.centralwidget)
self.lblSource.setGeometry(QtCore.QRect(180, 60, 511, 21))
self.lblSource.setObjectName("lblSource")
self.lblTarget = QtWidgets.QLabel(self.centralwidget)
self.lblTarget.setGeometry(QtCore.QRect(180, 120, 481, 16))
self.lblTarget.setObjectName("lblTarget")
self.btnTarget = QtWidgets.QPushButton(self.centralwidget)
self.btnTarget.setGeometry(QtCore.QRect(80, 120, 75, 23))
self.btnTarget.setObjectName("btnTarget")
self.btnGo = QtWidgets.QPushButton(self.centralwidget)
self.btnGo.setGeometry(QtCore.QRect(280, 120, 75, 23))
self.btnGo.setObjectName("btnGo")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 460, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.btnSource.setText(_translate("MainWindow", "Source"))
self.lblSource.setText(_translate("MainWindow", "TextLabel"))
self.lblTarget.setText(_translate("MainWindow", "TextLabel"))
self.btnTarget.setText(_translate("MainWindow", "Target"))
self.btnGo.setText(_translate("MainWindow", "Go"))
And here is my main.py file:
from PyQt5 import QtCore, QtGui, QtWidgets
from arch import Ui_MainWindow
import sys
app = QtWidgets.QApplication(sys.argv)
class myWindow(Ui_MainWindow):
def __init__(self):
super(myWindow, self).__init__()
self.btnSource.clicked.connect(self.btnSource.printMe)#
def printMe(self):
print('blah blah blah')
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
tl;dr
Subclass from both QMainWindow and Ui_MainWindow, and call setupUi from there; then create an instance of myWindow:
class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setupUi(self)
self.btnSource.clicked.connect(self.printMe)
def printMe(self):
print('blah blah blah')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
mainWindow = MyWindow()
mainWindow.show()
sys.exit(app.exec_())
Explanation
Your code doesn't work for many reasons; while the main problem might be that you actually never created an instance of myWindow (about that, you should always use capitalized names for classes), making it completely useless, it wouldn't have worked anyway.
That's because you should not subclass from the ui class object, but from the QWidget descendant (QMainWindow, in your case) you're going to use.
The ui_* objects created from pyuic are only intended as a high level (and unmodified) interface to create the UI on top of a QWidget subclass.
Calling setupUi(something) actually creates all child widgets for the widget something, sets the layout and, possibly, automatically connects to slots with a compatible name, but that's all: in fact, if you closely look at the code from the ui file, it actually does nothing besides setupUi and retranslateUi (nor it should!): there's not even an __init__!
If you need to add interaction and create connections from signals to slot/functions, you should use the single/multiple inheritance approaches as explained in the official guide about using Designer with PyQt; the only other possibility is to use loadUi (while still subclassing from the base class) with the source .ui file:
from PyQt5 import QtWidgets, uic
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi('path/to/gui.ui', self)
self.someWidget.someSignal.connect(self.someSlot)
# ...
def someSlot(self, signalArguments, [...]):
# do something...
PS: for various reasons, it's usually better to run a QApplication only if the script is the one that's been run (hence the if __name__ ...), mostly because there should be just only one QApplication instance for every running program; in any case, it shouldn't be created before the class declarations (unless, you really know what you're doing); it's not a big deal in your case, but, as usual, better safe than sorry.
My application, created with Pyinstaller, worked fine until I upgraded from High Sierra to Mojave. In order to demonstrate the issue I create the simple application.
Main window has only one button. When you press the button its text should be changed to "Please wait" for 10 seconds.
When I run this program as .py script, everything works fine, but after creating .app file with Pyinstaller it behaves differently. The text is not updated until you click anywhere outside of the window.
I tried to reinstall Pyinstaller, but the problem still exists.
from PyQt5 import QtCore, QtGui, QtWidgets
import time
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(303, 304)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(50, 80, 300, 43))
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(80, 170, 113, 32))
self.pushButton.setObjectName("pushButton")
self.pushButton.setDefault(True)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.pushButton.clicked.connect(self.click)
self.thread = Thread()
self.thread.finished.connect(lambda: self.pushButton.setEnabled(True))
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "Click me"))
def click(self):
if not self.thread.isRunning():
self.pushButton.setEnabled(False)
self.pushButton.setText("Please wait")
self.label.setText("The button below should display \n 'Please wait' for 10 seconds")
self.thread.start()
class Thread(QtCore.QThread):
def run(self):
time.sleep(10)
ui.pushButton.setEnabled(False)
ui.pushButton.setText("Click me")
ui.label.setText("")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I found an answer to my question. In order to solve this rendering issue you need to add the following line for a ui element, which needs to be updated. In my case it is required only if I need to run this application on macOS Mojave.
<element>.repaint()
For example:
def click(self):
self.pushButton.setEnabled(False)
self.pushButton.setText("Button is clicked...")
self.pushButton.repaint()
I'm looking for a better way to handle python UI "update" from QtDesigner without overwrite button event. The workflow I got now is:
Design UI layout in QtDesigner
convert .ui to .py by pyuic5
adding button event in .py file
excute .py to see window and button action
So if my UI keep changing the design, how do I keep all the button event I add into .py without being overwrite after convert? Thank you.
Answer my own question, what I found is have three python files. Main.py, CallUI.py and MainWindow.py. (Named as you want.)
So you can keep regenerate UI and override MainWindow.py without clear button event you wrote.
1.Main.py is the one to launch everything, name == "main". Call CAllUI.py's setup function.
#Main.py
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
import CallUI
def setUp():
CallUI.setUpWindow()
raw_input()
if __name__ == "__main__":
setUp()
2.CallUI.py is the one to use "QtWidgets.QApplication(sys.argv)" to show UI and add button click functions.
#CallUI.py
import sys
from MainWindow import Ui_Dialog
from PyQt5 import QtCore, QtGui, QtWidgets
import os
class CallUI(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.setUpBtnconnect()
def setUpBtnconnect(self):
self.ui.pushButton.clicked.connect(self.myFunction)
def myFunction(self):
os.system("ipconfig")
raw_input()
def setUpWindow():
app = QtWidgets.QApplication(sys.argv)
nowWindow = CallUI()
nowWindow.show()
sys.exit(app.exec_())
3.MainWindow.py is the one you converted from pyuic5, it's describe all the UI layout.
#MainWindow.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("MainWindow")
Dialog.resize(466, 417)
self.centralwidget = QtWidgets.QWidget(Dialog)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(160, 260, 75, 23))
self.pushButton.setObjectName("pushButton")
self.menubar = QtWidgets.QMenuBar(Dialog)
self.menubar.setGeometry(QtCore.QRect(0, 0, 466, 21))
self.menubar.setObjectName("menubar")
self.statusbar = QtWidgets.QStatusBar(Dialog)
self.statusbar.setObjectName("statusbar")
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
self.pushButton.setText(_translate("MainWindow", "PushButton"))
I am trying to create a path browser in pyqt5 using python3 but I have some doubts. My code is this:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(300, 150, 160, 80))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(self.horizontalLayoutWidget)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.lineEdit = QtWidgets.QLineEdit(self.horizontalLayoutWidget)
self.lineEdit.setObjectName("lineEdit")
self.horizontalLayout.addWidget(self.lineEdit)
self.toolButton = QtWidgets.QToolButton(self.horizontalLayoutWidget)
self.toolButton.setObjectName("toolButton")
self.horizontalLayout.addWidget(self.toolButton)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
# Button action
self.toolButton.pressed.connect(self.selectDirectory(MainWindow, self.lineEdit))
def selectDirectory(self, MainWindow, editText):
editText.setText(str(QtWidgets.QFileDialog.getExistingDirectory(MainWindow, "Select Directory", str(editText.text()))))
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "Path"))
self.toolButton.setText(_translate("MainWindow", "..."))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
The idea was using a edittext to write the path to the folder but as helper I want to introduce a button to open a file browser in order to select the directory. To achive this I am trying to use QFileDialog without success. My problem at this moment is when I run my application the QFileDialog is displayed and when I choose a folder the application crash. The first thing I do not want to display the QFileDialog without press the button and the second thing I do not understand why the application is crashing with this error:
Traceback (most recent call last):
File "test.py", line 48, in <module>
ui.setupUi(MainWindow)
File "test.py", line 31, in setupUi
self.toolButton.pressed.connect(self.selectDirectory(MainWindow, self.lineEdit))
TypeError: argument 1 has unexpected type 'NoneType'
Thanks for your help
The signal pressed hopes to connect to a slot that does not expect arguments, so you can not do the direct connection with the selectDirectory() function, you must make the connection using a lambda function.
You must change
self.toolButton.pressed.connect(self.selectDirectory(MainWindow, self.lineEdit))
to
self.toolButton.pressed.connect(lambda m=MainWindow, l=self.lineEdit: self.selectDirectory(m, l))
I've created a dynamic property in the Designer interface. How do I access this property in my code?
I don't see any properties listed with the name I've provided. I've found a dynamicPropertyNames property that contains a QByteArray object and the name I provided, but I cannot figure out how to access the data I stored (nor do I know if this is even the correct place to be querying).
Thanks!
Just because I had a similar problem and the reason wasn't a wrong object: The property's content can be accessed with toString().
this answer is just for myself, tried to reproduce the question and answer it.
Here the QtDesigner generated code for a Mainwindow with a DynamicPropierty,
main_win.py :
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'main_win.ui'
#
# Created by: PyQt5 UI code generator 5.12.3
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(270, 20, 261, 131))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 29))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
MainWindow.setProperty("Prova-proprieta", _translate("MainWindow", "valore_pp")) ### here the dynamic property created in the Designer interface
self.pushButton.setText(_translate("MainWindow", "PushButton"))
here my code printing the value of the dynamic property,
main.py :
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import QPushButton
from main_win import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setupUi(self)
print('from inside init : ', self.property('Prova-proprieta'))
self.pushButton.clicked.connect(self.pushButton_1_Pressed)
def pushButton_1_Pressed(self):
self.setProperty('Prova-proprieta', 'changed')
print('from PushButton : ', self.property('Prova-proprieta'))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
print('from loop : ',window.property('Prova-proprieta'))
window.show()
sys.exit(app.exec_())
Let me know if it right for You