PyQt5 Define the slot in a separate module for QShortcut - python

Main interacts with a GUI defined by a separate module. Two alternative approaches are used in Main to define the slot for a QShortcut. The lambda method works (but seems cumbersome). The direct method works, only if the lambda method is executed first. If the line with the lambda method is commented out, then the direct method fails (with no error message). Why does the direct method alone fail?
'''
# p.py
import sys
from PyQt5 import QtWidgets as qtw
from Q import MainWindow
class MyActions():
def __init__(self, my_window):
self.my_window = my_window
self.my_window.run.activated.connect(lambda: self.rmssg1()) # had to use this as a work-around
self.my_window.run.activated.connect(self.rmssg2) # expected this to work
def rmssg1(self):
self.my_window.my_label1.setText('Ctrl+R pressed -> mssg 1')
def rmssg2(self):
self.my_window.my_label2.setText('Ctrl+R pressed -> mssg 2')
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
mw = MainWindow()
MyActions(mw)
mw.show()
sys.exit(app.exec())
'''
Here's the GUI in a separate module
'''
#q.py
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5.QtGui import QKeySequence
class MainWindow(qtw.QMainWindow):
def __init__(self, parent=None):
super().__init__()
self.my_label1 = qtw.QLabel("Enter Ctrl+R", self)
self.my_label1.setGeometry(20,20,200,30)
self.my_label2 = qtw.QLabel("Enter Ctrl+R", self)
self.my_label2.setGeometry(20, 50, 200, 30)
#define shortcut
self.run = qtw.QShortcut(QKeySequence('Ctrl+R'), self)
'''

The problem is simple: When you do MyActions(mw) not assign a variable to the object so memory can be lost, the solution is:
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
mw = MainWindow()
foo = MyActions(mw)
mw.show()
sys.exit(app.exec())

Related

Can't connect PyQt signal to function in custom object class [duplicate]

I have this Python 3.5.1 program with PyQt5 and a GUI created from a QtCreator ui file where the pyqtSlot decorator causes "TypeError: connect() failed between textChanged(QString) and edited()".
In the sample code to reproduce the problem I have 2 custom classes: MainApp and LineEditHandler. MainApp instantiates the main GUI (from the file "mainwindow.ui") and LineEditHandler handles a QLineEdit object. LineEditHandler reason to exist is to concentrate the custom methods that relate mostly to the QLineEdit object in a class. Its constructor needs the QLineEdit object and the MainApp instance (to access other objects/properties when needed).
In MainApp I connect the textChanged signal of the QLineEdit to LineEditHandler.edited(). If I don't decorate LineEditHandler.edited() with pyqtSlot() everything works fine. If I do use #pyqtSlot() for the method, the code run will fail with "TypeError: connect() failed between textChanged(QString) and edited()". What am I doing something wrong here?
You can get the mainwindow.ui file at: https://drive.google.com/file/d/0B70NMOBg3HZtUktqYVduVEJBN2M/view
And this is the sample code to generate the problem:
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSlot
Ui_MainWindow, QtBaseClass = uic.loadUiType("mainwindow.ui")
class MainApp(QMainWindow, Ui_MainWindow):
def __init__(self):
# noinspection PyArgumentList
QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
# Instantiate the QLineEdit handler.
self._line_edit_handler = LineEditHandler(self, self.lineEdit)
# Let the QLineEdit handler deal with the QLineEdit textChanged signal.
self.lineEdit.textChanged.connect(self._line_edit_handler.edited)
class LineEditHandler:
def __init__(self, main_window, line_edit_obj):
self._line_edit = line_edit_obj
self._main_window = main_window
# FIXME The pyqtSlot decorator causes "TypeError: connect() failed between
# FIXME textChanged(QString) and edited()"
#pyqtSlot(name="edited")
def edited(self):
# Copy the entry box text to the label box below.
self._main_window.label.setText(self._line_edit.text())
def main():
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Why do you want to use #pyqtSlot?
The reason it fails is that LineEditHandler is not a QObject. What #pyqtSlot does is basically creating a real Qt slot instead of internally using a proxy object (which is the default behavior without #pyqtSlot).
I don't know what was wrong, but I found a workaround: Connect the textChanged signal to a pyqtSlot decorated MainApp method that calls LineEditHandler.edited():
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSlot
Ui_MainWindow, QtBaseClass = uic.loadUiType("mainwindow.ui")
class MainApp(QMainWindow, Ui_MainWindow):
def __init__(self):
# noinspection PyArgumentList
QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
# Instantiate the QLineEdit handler.
self._line_edit_handler = LineEditHandler(self, self.lineEdit)
self.lineEdit.textChanged.connect(self._line_edited)
#pyqtSlot(name="_line_edited")
def _line_edited(self):
self._line_edit_handler.edited()
class LineEditHandler:
def __init__(self, main_window, line_edit_obj):
self._line_edit = line_edit_obj
self._main_window = main_window
def edited(self):
# Copy the entry box text to the label box below.
self._main_window.label.setText(self._line_edit.text())
def main():
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

how to block signals reaching to custom slots

blocking signals to reach QObjects is fairly simple with using the QSignalBlocker Class
like
# functionality
self.clickbuton.clicked.connect(self.printsomething)
self.clickbuton.clicked.connect(self.blockprint)
def printsomething(self):
print("dude")
def blockprint(self):
self.clickbuton.blockSignals(True)
what about custom slots like def printsomething(self): ?
trying the same operation but with blocking def printsomething(self): from printing
def blockprint(self):
self.printsomething.blockSignals(True)
will give a AttributeError: 'function' object has no attribute 'blockSignals'
it looks like this method works only for QObjects
how can I block def printsomething(self): from printing without using disconnect while its connected to the clicked signal?
code example
"""
Testing Template for throw away experiment
"""
import sys
import os
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# widget
self.clickbuton = qtw.QPushButton("click me")
# set the layout
layout = qtw.QVBoxLayout()
layout.addWidget(self.clickbuton)
self.setLayout(layout)
# functionality
self.clickbuton.clicked.connect(self.printsomething)
self.clickbuton.clicked.connect(self.blockprint)
def printsomething(self):
print("dude")
def blockprint(self):
self.printsomething.blockSignals(True)
# self.m_blocker = qtc.QSignalBlocker(self.clickbuton)
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
What you're trying to do is to block a slot which is impossible.
You can only block signals of objects inherited from QObject.
As a solution you can instead of
def blockprint(self):
self.printsomething.blockSignals(True)
do this
def blockprint(self):
self.blockSignals(True)
that would block mainwindow signals until you turn blockSignals flag to false again.

Hide current QMainWindow when next QMainWindow is called

I have multiple windows in a Python GUI application using PyQt5.
I need to hide current window when a button is clicked and show the next window.
This works fine from WindowA to WindowB but I get an error while going from WindowB to WindowC.
I know there is some problem in initialization as the initialization code in WindowB is unreachable, but being a beginner with PyQt, i can't figure out the solution.
WindowA code:
from PyQt5 import QtCore, QtGui, QtWidgets
from WindowB import Ui_forWindowB
class Ui_forWindowA(object):
def setupUi(self, WindowA):
# GUI specifications statements here
self.someButton = QtWidgets.QPushButton(self.centralwidget)
self.someButton.clicked.connect(self.OpenWindowB)
# More GUI specifications statements here
def retranslateUi(self, WindowA):
# More statements here
def OpenWindowB(self):
self.window = QtWidgets.QMainWindow()
self.ui = Ui_forWindowB()
self.ui.setupUi(self.window)
WindowA.hide()
self.window.show()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
WindowA = QtWidgets.QMainWindow()
ui = Ui_forWindowA()
ui.setupUi(WindowA)
MainWindow.show()
sys.exit(app.exec_())
WindowB code:
from PyQt5 import QtCore, QtGui, QtWidgets
from WindowB import Ui_forWindowB
class Ui_forWindowB(object):
def setupUi(self, WindowB):
# GUI specifications statements here
self.someButton = QtWidgets.QPushButton(self.centralwidget)
self.someButton.clicked.connect(self.OpenWindowC)
# More GUI specifications statements here
def retranslateUi(self, WindowB):
# More statements here
def OpenWindowB(self):
self.window = QtWidgets.QMainWindow()
self.ui = Ui_forWindowC()
self.ui.setupUi(self.window)
WindowB.hide() # Error here
self.window.show()
# The below code doesn't get executed when Ui_forWindowB is called from A
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
WindowB = QtWidgets.QMainWindow()
ui = Ui_forWindowB()
ui.setupUi(WindowB)
MainWindow.show()
sys.exit(app.exec_())
It works fine from A to B where
WindowA.hide() # Works Properly
While calling WindowC from WindowB
WindowB.hide() # Shows error: name 'WindowB' is not defined
I understand that the initialization isn't done as the "if" statement doesn't get executed.
How to get this working?
I have many more windows to connect in this flow
When you run a Python script, the first file executed will be assigned the name __main__, therefore, if you first execute WindowA the code inside the block if __name__ == "__main__" gets executed and the application is started using WindowA as the main window, similarly if you execute your WindowB script first, the application will be started usingWindowB as the main window.
You cannot start two applications within the same process so you have to choose which one you want to be the main window, all the others will be secondary windows (even if they inherit from QMainWindow).
Nevertheless, you should be able to instantiate new windows from a method in your main window.
As a good practice, you could create a main script to handle the initialization of your application and start an empty main window that will then handle your workflow, also, you may want to wrap your UI classes, specially if they are generated using Qt creator, here is an example:
main.py
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication
from views.main_window import MainWindow
class App(QApplication):
"""Main application wrapper, loads and shows the main window"""
def __init__(self, sys_argv):
super().__init__(sys_argv)
# Show main window
self.main_window = MainWindow()
self.main_window.show()
if __name__ == '__main__':
app = App(sys.argv)
sys.exit(app.exec_())
main_window.py
This is the main window, it doesn't do anything, just control the workflow of the application, i.e. load WindowA, then WindowB etc., notice that I inherit from Ui_MainWindow, by doing so, you can separate the look and feel from the logic and use Qt Creator to generate your UI:
from PyQt5.QtWidgets import QWidget, QMainWindow
from views.window_a import WindowA
from views.window_b import WindowB
from widgets.main_window import Ui_MainWindow
class MainWindow(Ui_MainWindow, QMainWindow):
"""Main application window, handles the workflow of secondary windows"""
def __init__(self):
Ui_MainWindow.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# start hidden
self.hide()
# show window A
self.window_a = WindowA()
self.window_a.actionExit.triggered.connect(self.window_a_closed)
self.window_a.show()
def window_a_closed(self):
# Show window B
self.window_b = WindowB()
self.window_b.actionExit.triggered.connect(self.window_b_closed)
self.window_b.show()
def window_b_closed(self):
#Close the application if window B is closed
self.close()
window_a.py
from PyQt5.QtWidgets import QWidget, QMainWindow
from widgets.main_window import Ui_forWindowA
class WindowA(Ui_forWindowA, QMainWindow):
"""Window A"""
def __init__(self):
Ui_forWindowA.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# Do some stuff
window_b.py
from PyQt5.QtWidgets import QWidget, QMainWindow
from widgets.main_window import Ui_forWindowB
class WindowA(Ui_forWindowB, QMainWindow):
"""Window B"""
def __init__(self):
Ui_forWindowB.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# Do some stuff
Hopefully should give you an idea to get you going.

PyQt5 method not connected to button

This is my code for running PyQt, however the selectFile method is not called by the button. The UI code is converted from QtCreator. I've checked my objectName for the button is browseCSV
import sys
from readCSV import *
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog
import form
from function2 import *
from function4 import *
from Function6 import *
class App(QtWidgets.QMainWindow, form.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self) # This is defined in design.py file automatically
self.browseCSV.clicked.connect(self.selectFile)
def selectFile(self):
print ("Hello")
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = form.Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
You're not actually using your App class. So you need to do this:
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = App()
window.show()
sys.exit(app.exec_()
PS: don't ever use self.__class__ in a super call. In some scenarios, it can cause an infinite regress. If you're using Python 3, you can just use super().__init__() to avoid repeating the class name.

PYQT Adding extra menu items to an existing UI before it opens

Hi I have designed a basic GUI in QT and created a .py file from it.
When the window starts up I want to add another menu item. I have tried a few pieces of code I found on google but nothing seems to work. The code will need to go in the method addAdminMenu()
from PyQt4 import QtGui
import sys
from supplypy.core.windows.main_window import Ui_MainWindow
class SRM(QtGui.QWidget):
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
self.MainWindow = QtGui.QMainWindow()
self.ui = Ui_MainWindow()
self.ui.setupUi(self.MainWindow)
self.MainWindow.show()
sys.exit(self.app.exec_())
def addAdminMenu(self):
pass
#####Add code here to create a Admin menu####
if __name__ == '__main__':
srm = SRM()
It should be as simple as accessing the menuBar() of the QMainWindow and adding an item, for example: (I removed the Ui_MainWindow lines just because I don't know what it's for -- a Windows requirement?)
from PyQt4 import QtGui
import sys
class SRM(QtGui.QWidget):
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
self.MainWindow = QtGui.QMainWindow()
self.menubar = self.MainWindow.menuBar()
self.MainWindow.show()
self.addAdminMenu()
sys.exit(self.app.exec_())
def addAdminMenu(self):
self.menubar.addMenu('&Admin');
if __name__ == '__main__':
srm = SRM()

Categories

Resources