I couldn't understand the connectSlotsByName() method which is predominently used by pyuic4.. As far the class is single in a PyQt file it's ok since we can use self which will be associated with a single object throughout.. But when we try to use various classes from different files the problem and the need to use connectSlotsByName() arises.. Here's what i encountered which is weird..
I created a stacked widget..
I placed my first widget on it.. It
has a button called "Next >".
On clicking next it hides the current
widget and adds another widget which has the "click me" button..
The problem here is the click event for "click me" button in second is not captured.. It's a minimal example that i can give for my original problem.. Please help me..
This is file No.1..(which has the parent stacked widget and it's first page). On clicking next it adds the second page which has "clickme" button in file2..
from PyQt4 import QtCore, QtGui
import file2
class Ui_StackedWidget(QtGui.QStackedWidget):
def __init__(self,parent=None):
QtGui.QStackedWidget.__init__(self,parent)
self.setObjectName("self")
self.resize(484, 370)
self.setWindowTitle(QtGui.QApplication.translate("self", "stacked widget", None, QtGui.QApplication.UnicodeUTF8))
self.createWidget1()
def createWidget1(self):
self.page=QtGui.QWidget()
self.page.setObjectName("widget1")
self.pushButton=QtGui.QPushButton(self.page)
self.pushButton.setGeometry(QtCore.QRect(150, 230, 91, 31))
self.pushButton.setText(QtGui.QApplication.translate("self", "Next >", None, QtGui.QApplication.UnicodeUTF8))
self.addWidget(self.page)
QtCore.QMetaObject.connectSlotsByName(self.page)
QtCore.QObject.connect(self.pushButton,QtCore.SIGNAL('clicked()'),self.showWidget2)
def showWidget2(self):
self.page.hide()
obj=file2.widget2()
obj.createWidget2(self)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
ui = Ui_StackedWidget()
ui.show()
sys.exit(app.exec_())
Here's file2
from PyQt4 import QtGui,QtCore
class widget2():
def createWidget2(self,parent):
self.page = QtGui.QWidget()
self.page.setObjectName("page")
self.parent=parent
self.groupBox = QtGui.QGroupBox(self.page)
self.groupBox.setGeometry(QtCore.QRect(30, 20, 421, 311))
self.groupBox.setObjectName("groupBox")
self.groupBox.setTitle(QtGui.QApplication.translate("self", "TestGroupBox", None, QtGui.QApplication.UnicodeUTF8))
self.pushButton = QtGui.QPushButton(self.groupBox)
self.pushButton.setGeometry(QtCore.QRect(150, 120, 92, 28))
self.pushButton.setObjectName("pushButton")
self.pushButton.setText(QtGui.QApplication.translate("self", "Click Me", None, QtGui.QApplication.UnicodeUTF8))
self.parent.addWidget(self.page)
self.parent.setCurrentWidget(self.page)
QtCore.QMetaObject.connectSlotsByName(self.page)
QtCore.QObject.connect(self.pushButton,QtCore.SIGNAL('clicked()'),self.printMessage)
def printMessage(self):
print("Hai")
Though in both the widgets(i mean pages)
QtCore.QMetaObject.connectSlotsByName(self.page)
the clicked signal in second dialog isn't getting processed. Thanks in advance.. Might be a beginner question..
A better question is "Why not just use new-style signals and slots?". They're much simpler and don't require any weird naming conventions:
from sys import argv, exit
from PyQt4 import QtCore, QtGui
class MyWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self._layout = QtGui.QVBoxLayout()
self.setLayout(self._layout)
self._button = QtGui.QPushButton()
self._button.setText('Click NOW!')
self._layout.addWidget(self._button)
self._button.clicked.connect(self._printMessage)
#QtCore.pyqtSlot()
def _printMessage(self):
print("Hai")
if __name__ == "__main__":
app = QtGui.QApplication(argv)
main = MyWidget()
main.show()
exit(app.exec_())
At first, here is the minimal working example:
from sys import argv, exit
from PyQt4 import QtCore, QtGui
class widget2(QtGui.QWidget):
def __init__(self, args):
self.app = MainApp(args)
QtGui.QWidget.__init__(self)
self.setObjectName('I')
self._layout = QtGui.QVBoxLayout(self)
self.setLayout(self._layout)
self.pushButtoninWidget2 = QtGui.QPushButton(self)
self.pushButtoninWidget2.setObjectName("pushButtoninWidget2")
self.pushButtoninWidget2.setText('Click NOW!')
self._layout.addWidget(self.pushButtoninWidget2)
QtCore.QMetaObject.connectSlotsByName(self)
#QtCore.pyqtSlot()
def on_pushButtoninWidget2_clicked(self):
print("Hai")
class MainApp(QtGui.QApplication):
def __init__(self, args):
QtGui.QApplication.__init__(self, args)
if __name__ == "__main__":
main = widget2(argv)
main.show()
exit(main.app.exec_())
When you trying to connect slots by name, you must give proper names to the slots and then someone (moc, uic, or you by calling connectSlotsByName) must connect them. Proper name for such a slot is: "on_PyQtObjectName_PyQtSignalName".
Note, that, if I'd omitted #QtCore.pyqtSlot() in the example, slot would be executed once for every appropriate overload (twice in this case).
You DO need to call connectSlotsByNames directly, cause there is no moc, which do it for you when you use QT in C++, and you do not use uic and .ui file. If you want to connect slots implicitly (I'm always doing so, except slots, connected directly in .ui), you'd better use more pytonish syntaxe: button.clicked.connect(self._mySlot).
And take a look at https://riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html#connecting-slots-by-name
You do not need to call connectSlotsByName(), just remove those lines.
In file2, calling QtCore.QMetaObject.connectSlotsByName(self.page) tries to do this:
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL('clicked()'), self.on_pushButton_clicked())
That will not work for you since self.on_pushBotton_clicked() slot is not defined.
I find it is easiest to create your own connections in PyQt... I recommend removing the calls to connectSlotsByName from your both classes... you do not need it.
Also, your wdiget1 class should set the name of it's pushButton (preferably something other then "pushButton" to avoid confusion with the button in widget2).
Thank you so much jcoon for your reply.. But after a very long time banging my head against the wall i found the solution..
The problem was..
self.obj=test_reuse_stacked1.widget2()
self.obj.createWidget2(self)
instead of obj..
Here is #MarkVisser's QT4 code updated to QT5:
from sys import argv, exit
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget
class MyWidget(QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self._layout = QtWidgets.QVBoxLayout()
self.setLayout(self._layout)
self._button = QtWidgets.QPushButton()
self._button.setText('Click NOW!')
self._layout.addWidget(self._button)
self._button.clicked.connect(self._print_message)
#QtCore.pyqtSlot()
def _print_message(self):
print("Hai")
if __name__ == "__main__":
app = QApplication(argv)
main = MyWidget()
main.show()
exit(app.exec_())
Another minimal working example with Qt for Python aka PySide2/6.
Key ingredients:
widget to connect MUST have .setObjectName
function to connect MUST be decorated with #QtCore.Slot()
both objects (function AND widget) MUST be members of passed object (self here)
from PySide2 import QtCore, QtWidgets
# or from PySide6 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self):
super(Widget, self).__init__()
layout = QtWidgets.QVBoxLayout(self)
self.button = QtWidgets.QPushButton(self)
self.button.setObjectName('button')
self.button.setText('Click Me!')
layout.addWidget(self.button)
QtCore.QMetaObject.connectSlotsByName(self)
#QtCore.Slot()
def on_button_clicked(self):
print(f'Hai from {self.sender()}')
if __name__ == '__main__':
app = QtWidgets.QApplication([])
main = Widget()
main.show()
app.exec_()
I couldn't get mit any smaller really 🤔
Related
Normally in Qt applications you would use this at the start and end of your code:
app = QtWidgets.QApplication(sys.argv)
...
app.exec_()
But in Maya, you don't use this because Qt runs on the Maya application it self. I'm sure this works the same for many other applications as well if you don't know what Maya is. That said, my code looks like this:
import sys
from PySide2 import QtWidgets, QtGui, QtCore
class Test():
def __init__(self):
self.open_qt()
def open_qt(self):
# app = QtWidgets.QApplication(sys.argv) # Don't need this in Maya
self.window = QtWidgets.QWidget() # I tried QDialog also
btn = QtWidgets.QPushButton("press me")
btn.clicked.connect(self.login)
lay = QtWidgets.QVBoxLayout()
lay.addWidget(btn)
self.window.setLayout(lay)
self.window.show()
# app.exec_() # Don't need this in Maya
def login(self):
print("logged in!")
print("before")
temp = Test()
print("after")
But running this in Maya I get this result:
before
after
logged in!
But I need it to be:
before
logged in!
after
If you run this code outside of Maya (and you use those two commented out lines) then you get the correct result (block above here).
How can I get the Maya Qt to also wait correctly like it would if you used QtWidgets.QApplication(sys.argv)?
A QDialog might be more well suited for your needs, as it runs its own event loop that won't block the program, while waiting for the dialog to be completed.
The important thing to do is to call of its exec_() and call accept() when needed.
from PySide2 import QtWidgets, QtGui, QtCore
class Test(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
layout = QtWidgets.QVBoxLayout(self)
self.spinBox = QtWidgets.QSpinBox()
layout.addWidget(self.spinBox)
btn = QtWidgets.QPushButton("press me")
layout.addWidget(btn)
btn.clicked.connect(self.login)
def login(self):
print("logged in!")
self.accept()
dialog = Test()
if dialog.exec_():
print(dialog.spinBox.value())
I don't have Maya, but according to this answer you can get its main window using the maya.OpenMayaUI module and shiboken's wrapInstance().
I am trying to translate my small application written in pyside2/pyqt5 to several languages, for example, Chinese. After googling, I managed to change the main window to Chinese after select from the menu -> language -> Chinese. However, the pop up dialog from menu -> option still remains English version. It seems the translation info is not transferred to the dialog. How do I solve this?
Basically, I build two ui files in designer and convert to two python files:One mainui.py and one dialogui.py. I then convert the two python file into one *.ts file using
pylupdate5 -verbose mainui.py dialogui.py -ts zh_CN.ts
after that, in linguist input the translation words. I can see the items in the dialog, which means this information is not missing. Then I release the file as zh_CN.qm file. All this supporting file I attached below using google drive.
Supporting files for the question
The main file is as
import os
import sys
from PySide2 import QtCore, QtGui, QtWidgets
from mainui import Ui_MainWindow
from dialogui import Ui_Dialog
class OptionsDialog(QtWidgets.QDialog,Ui_Dialog):
def __init__(self,parent):
super().__init__(parent)
self.setupUi(self)
self.retranslateUi(self)
class MainWindow(QtWidgets.QMainWindow,Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.actionConfigure.triggered.connect(self.showdialog)
self.actionChinese.triggered.connect(self.change_lang)
def showdialog(self):
dlg = OptionsDialog(self)
dlg.exec_()
def change_lang(self):
trans = QtCore.QTranslator()
trans.load('zh_CN')
QtCore.QCoreApplication.instance().installTranslator(trans)
self.retranslateUi(self)
if __name__=='__main__':
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
ret = app.exec_()
sys.exit(ret)
I think it should be a typical task because almost no application will only have a mainwindow.
You have to overwrite the changeEvent() method and call retranslateUi() when the event is of type QEvent::LanguageChange, on the other hand the QTranslator object must be a member of the class but it will be deleted and it will not exist when the changeEvent() method is called.
Finally assuming that the Language menu is used to establish only translations, a possible option is to establish the name of the .qm as data of the QActions and to use the triggered method of the QMenu as I show below:
from PySide2 import QtCore, QtGui, QtWidgets
from mainui import Ui_MainWindow
from dialogui import Ui_Dialog
class OptionsDialog(QtWidgets.QDialog,Ui_Dialog):
def __init__(self,parent):
super().__init__(parent)
self.setupUi(self)
def changeEvent(self, event):
if event.type() == QtCore.QEvent.LanguageChange:
self.retranslateUi(self)
super(OptionsDialog, self).changeEvent(event)
class MainWindow(QtWidgets.QMainWindow,Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.m_translator = QtCore.QTranslator(self)
self.actionConfigure.triggered.connect(self.showdialog)
self.menuLanguage.triggered.connect(self.change_lang)
# set translation for each submenu
self.actionChinese.setData('zh_CN')
#QtCore.Slot()
def showdialog(self):
dlg = OptionsDialog(self)
dlg.exec_()
#QtCore.Slot(QtWidgets.QAction)
def change_lang(self, action):
QtCore.QCoreApplication.instance().removeTranslator(self.m_translator)
if self.m_translator.load(action.data()):
QtCore.QCoreApplication.instance().installTranslator(self.m_translator)
def changeEvent(self, event):
if event.type() == QtCore.QEvent.LanguageChange:
self.retranslateUi(self)
super(MainWindow, self).changeEvent(event)
if __name__=='__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
ret = app.exec_()
sys.exit(ret)
I understand (more or less) content of the page of official documentation except the the final example, which is essencially as follows (I do not care the button):
from ui_imagedialog import ImageDialog
class MyImageDialog(ImageDialog):
def __init__(self):
super(MyImageDialog, self).__init__()
# Connect up the buttons.
self.okButton.clicked.connect(self.accept)
Problem: I'm stuck a bit trying to understand how to make this snippet work. It rises an error: 'cannon import name ImageDialog'.
What should I add from the first example of the aforementioned documentation page to make this code to show up a Dialog window?
What I've tried:
I've made the file with generated Python code with the name ui_imagedialog.py as requiered. It has the the following content, which obviously works on its own:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ImageDialog(object):
def setupUi(self, ImageDialog):
ImageDialog.setObjectName("ImageDialog")
ImageDialog.resize(303, 204)
self.pushButton = QtWidgets.QPushButton(ImageDialog)
self.pushButton.setGeometry(QtCore.QRect(200, 160, 75, 23))
self.pushButton.setObjectName("pushButton")
self.retranslateUi(ImageDialog)
QtCore.QMetaObject.connectSlotsByName(ImageDialog)
def retranslateUi(self, ImageDialog):
_translate = QtCore.QCoreApplication.translate
ImageDialog.setWindowTitle(_translate("ImageDialog", "Dialog"))
self.pushButton.setText(_translate("ImageDialog", "PushButton"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ImageDialog = QtWidgets.QDialog()
ui = Ui_ImageDialog()
ui.setupUi(ImageDialog)
ImageDialog.show()
sys.exit(app.exec_())
Any constructive help is appreciated.
Qt Designer is used to create the graphic part, but not for the logic, you must create the logical part depending on the widget you used in the design. In your case by the name I think it is QDialog.
from ui_imagedialog import Ui_ImageDialog
from PyQt5 import QtCore, QtGui, QtWidgets
class ImageDialog(QtWidgets.QDialog, Ui_ImageDialog):
def __init__(self, parent=None):
super(ImageDialog, self).__init__(parent=parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.accept)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = ImageDialog()
w.show()
sys.exit(app.exec_())
Observations: In the ui_imagedialog.py file there is no ImageDialog class, only the Ui_ImageDialog class, so I generated the error. Also in the design the button is called self.pushButton, so you can not call it self.okButton.
I am trying to write a PyQt5 application that does the following:
Creates and opens a Main Window. The MainWindow has a function that opens a QFileDialog window.
Function to open QFileDialog can be triggered in two ways (1) from a file menu option called 'Open' (2) automatically, after the Main window is shown.
My problem is that I haven't found a way to get the QfileDialog to automatically open (2) that doesn't cause the application to hang when the main window is closed. Basic example of code can be found below:
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QWidget,
QHBoxLayout, QCalendarWidget, QScrollArea, QFileDialog, QAction, QFrame)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.openAction = QAction(QIcon('/usr/share/icons/breeze/places/64/folder-open.svg'), 'Open', self)
self.openAction.triggered.connect(self.openDialog)
self.menubar = QMenuBar(self)
fileMenu = self.menubar.addMenu('&File')
fileMenu.addAction(self.openAction)
self.event_widgets = EventWidgets(self)
self.setMenuBar(self.menubar)
self.setCentralWidget(self.event_widgets)
def openDialog(self):
ics_path = QFileDialog.getOpenFileName(self, 'Open file', '/home/michael/')
class EventWidgets(QWidget):
def __init__(self, parent):
super(EventWidgets, self).__init__(parent)
self.initUI()
def initUI(self):
self.calendar = QCalendarWidget(self)
self.frame = QFrame()
self.scrollArea = QScrollArea()
self.scrollArea.setWidget(self.frame)
horizontal_box = QHBoxLayout()
horizontal_box.addWidget(self.calendar)
horizontal_box.addWidget(self.scrollArea)
self.setLayout(horizontal_box)
if __name__ == '__main__':
app = QApplication(sys.argv)
app_window = MainWindow()
app_window.showMaximized()
app_window.openDialog()
sys.exit(app.exec_())
Code has been tested on KDE Neon and Arch Linux, both have same issue.
I can get round this issue by handling the close event of the Main Window manually - i.e. adding this function to MainWindow:
def closeEvent(self, event):
sys.exit()
But I am not sure a) why this is necessary b) if it is best practice.
I tried your code on macOS Sierra and it works as it's supposed to. However I would propose a different approach to solve your problem.
What you could do is to implement the showEvent() function in your MainWindow class, which is executed whenever a widget is displayed (either using .show() or .showMaximized()) and trigger your custom slot to open the QFileDialog. When doing this you could make use of a single shot timer to trigger the slot with some minimal delay: the reason behind this is that if you simply open the dialog from within the showEvent(), you will see the dialog window but not the MainWindow below it (because the QFileDialog is blocking the UI until the user perform some action). The single shot timer adds some delay to the opening of the QFileDialog, and allows the MainWindow to be rendered behind the modal dialog. Here is a possible solution (not that I made some minor changes to your code, which you should be able to easily pick up):
import sys
from PyQt5 import QtCore
from PyQt5 import QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.openAction = QtWidgets.QAction('Open', self)
self.openAction.triggered.connect(self.openDialog)
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
fileMenu.addAction(self.openAction)
self.event_widgets = EventWidgets(self)
self.setCentralWidget(self.event_widgets)
def showEvent(self, showEvent):
QtCore.QTimer.singleShot(50, self.openDialog)
#QtCore.pyqtSlot()
def openDialog(self):
ics_path = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', '/Users/daniele/')
class EventWidgets(QtWidgets.QWidget):
def __init__(self, parent):
super(EventWidgets, self).__init__(parent)
self.calendar = QtWidgets.QCalendarWidget(self)
self.frame = QtWidgets.QFrame()
self.scrollArea = QtWidgets.QScrollArea()
self.scrollArea.setWidget(self.frame)
horizontal_box = QtWidgets.QHBoxLayout()
horizontal_box.addWidget(self.calendar)
horizontal_box.addWidget(self.scrollArea)
self.setLayout(horizontal_box)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app_window = MainWindow()
app_window.showMaximized()
sys.exit(app.exec_())
Here is the error I am getting, which I am really confused about. My UI file which I am loading has this button name and it matches. But for some reason it doesn't seem to recognize and load it. I just tried converting this code over to PySide (it was originally PyQt). Am I doing something wrong in translating it?
Error: AttributeError: file line 25: 'swapRefGUI' object has no attribute 'swapRefBtn' #
from PySide import QtCore, QtGui, QtUiTools
import maya.cmds as cmds
class swapRefGUI(QDialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent)
loader = QtUiTools.QUiLoader()
uifile = QtCore.QFile('C:\Scripts\swapRef.ui')
uifile.open(QtCore.QFile.ReadOnly)
ui = loader.load(uifile, parent)
uifile.close()
self.setFixedSize(400, 300)
self.swapRefBtn.clicked.connect(self.swapRefBtn_clicked)
self.closeBtn.clicked.connect(self.close)
def swapRefBtn_clicked(self):
pass
if __name__ == "__main__":
#app = QApplication(sys.argv)
app = QApplication.instance()
if app is None:
app = QApplication(sys.argv)
myGUI = swapRefGUI(None)
myGUI.show()
sys.exit(app.exec_())
Right now you are trying to access swapRefBtn through the class instance swapRefGUI, but you actually need to access it through the ui variable where you load. The 2nd argument of loader.load should also be self to display the qt gui in your window. There's also a few instances where you are trying to access objects from PySide like QDialog, when it should be QtGui.QDialog (because of the way you imported PySide module).
Here's some code that worked with a ui file.
from PySide import QtCore, QtGui, QtUiTools
import maya.cmds as cmds
class swapRefGUI(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
loader = QtUiTools.QUiLoader()
uifile = QtCore.QFile('C:\Scripts\swapRef.ui')
uifile.open(QtCore.QFile.ReadOnly)
self.ui = loader.load(uifile, self) # Qt objects are inside ui, so good idea to save the variable to the class, 2nd arg should refer to self
uifile.close()
self.setFixedSize(400, 300)
self.ui.swapRefBtn.clicked.connect(self.swapRefBtn_clicked) # Need to access button through self.ui
#self.ui.closeBtn.clicked.connect(self.close) # This needs to have an existing function in the class or it will crash when executing
def swapRefBtn_clicked(self):
print 'WORKS'
myGUI = swapRefGUI()
myGUI.show()