Using Signals for communicating between classes - python

I want to use signals for communicating between my view and my application controller. I have following approach but since I'm beginner in PyQt I don't know if that is the right one. Can anyone tell me If I am on the right path or are there better solutions?
EDIT: I have changed the example to a fully working example.
import sys
from PyQt4 import QtGui, QtCore
class View(QtGui.QMainWindow):
sigFooChanged = QtCore.pyqtSignal()
sigBarChanged = QtCore.pyqtSignal()
def __init__(self):
QtGui.QMainWindow.__init__(self)
central_widget = QtGui.QWidget()
central_layout = QtGui.QHBoxLayout()
self.__cbFoo = QtGui.QComboBox()
self.__cbBar = QtGui.QComboBox()
self.__cbFoo.currentIndexChanged[str].connect(lambda x: self.sigFooChanged.emit())
self.__cbBar.currentIndexChanged[str].connect(lambda x: self.sigBarChanged.emit())
central_layout.addWidget(QtGui.QLabel("Foo:"))
central_layout.addWidget(self.__cbFoo)
central_layout.addWidget(QtGui.QLabel("Bar:"))
central_layout.addWidget(self.__cbBar)
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
def setFooModel(self, model):
self.__cbFoo.setModel(model)
def setBarModel(self, model):
self.__cbBar.setModel(model)
class Controller:
def __init__(self, view):
self.__view = view
# Connect all signals from view with according handlers
self.__view.sigFooChanged.connect(self.handleFooChanged)
self.__view.sigBarChanged.connect(self.handleBarChanged)
self.__fooModel = QtGui.QStringListModel(["Foo1", "Foo2", "Foo3"])
self.__barModel = QtGui.QStringListModel(["Bar1", "Bar2", "Bar3"])
self.__view.setFooModel(self.__fooModel)
self.__view.setBarModel(self.__barModel)
def handleFooChanged(self):
print("Foo Changed")
def handleBarChanged(self):
print("Bar Changed")
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
view = View()
controller = Controller(view)
view.show()
sys.exit(app.exec_())

Personally, I don't create a separate generic controller class like that. It could be my own preference, but I tend to consider the actual QWidget class my controller, and the view is usually the GUI-only definitions that I generate from QtDesigner (Ui_Dialog for example), or create manually. And I make all my connections in the relevant QWidget.
Now on to your code, I don't know if you are just considering this snippet a general pseudocode example of the direction you are taking...but it has errors... I would normally suggest posting working code so people don't get confused as to whether you are having errors because of it, or just asking if its generally a correct direction to laying out code.
You are forgetting to call __init__() on the QMainWindow superclass.
I'm not sure what controller.show() would do (fail as of right now) because I don't see an example of how you intend to forward that show() command to your main window object? Again I don't really see why its even necessary to have that separate class.
Here is how I would see a more realistic example, again considering the QWidget classes themselves to be the controllers:
View
## mainUI.py ##
from PyQt4 import QtCore, QtGui
class Ui_MyWidget(object):
def setupUi(self, obj):
obj.layout = QtGui.QVBoxLayout(obj)
obj.cbFoo = QtGui.QComboBox()
obj.cbBar = QtGui.QComboBox()
obj.layout.addWidget(obj.cbFoo)
obj.layout.addWidget(obj.cbBar)
Non-Gui Library Module (Controller)
## nonGuiModule.py ##
class LibModule(object):
def handleBarChanged(self, *args):
print("Bar Changed: %s" % args)
Controller (any entry point)
## main.py ##
import sys
from PyQt4 import QtCore, QtGui
from mainUI import Ui_MyWidget
from nonGuiModule import LibModule
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.resize(640,480)
self._lib = LibModule()
self.myWidget = MyWidget(self)
self.setCentralWidget(self.myWidget)
self.myWidget.sigFooChanged.connect(self.handleFooChanged)
self.myWidget.sigBarChanged.connect(self._lib.handleBarChanged)
def handleFooChanged(self, *args):
print("Foo Changed: %s" % args)
class MyWidget(QtGui.QFrame, Ui_MyWidget):
sigFooChanged = QtCore.pyqtSignal(str)
sigBarChanged = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
# this is where you set up from the view
self.setupUi(self)
self.cbFoo.addItems(['Foo1', 'Foo2'])
self.cbBar.addItems(['Bar1', 'Bar2'])
self.layout.addWidget(self.cbFoo)
self.layout.addWidget(self.cbBar)
# going to forward private signals to public signals
self.cbFoo.currentIndexChanged[str].connect(self.sigFooChanged)
self.cbBar.currentIndexChanged[str].connect(self.sigBarChanged)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv[1:])
view = Main()
view.show()
sys.exit(app.exec_())

Related

Passing methods from secondary GUI to main GUI in python

So I have 2 GUIs. One is the main gui which has one push button to activate the second gui. The second gui is a simple calculator which sums two numbers when I push the button with external function.The second gui (the calculator) runs fine standalone However when I try to activate the second gui from the main one the program crashes so I probably doing something wrong.
Also if I change the code in main to this:
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.SumCalcBtn.clicked.connect(self.OpenSecondWindow)
def OpenSecondWindow(self):
self.ex = SumCalculator(self)
self.ex.show()
It runs but doesn't do anything in second gui when I push the button to sum the numbers.(it seems the methods didn't pass to the instance)
I attach the code for better understanding:
Main.py
import sys
from calculators import summary
from PyQt5 import QtCore, QtGui, QtWidgets
from SummaryUI import Ui_SummaryUI
from SummaryMain import SumCalc
from MainWindow import Ui_MainWindow
class SumCalculator(SumCalc):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.setupUi(self)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.SumCalcBtn.clicked.connect(self.OpenSecondWindow)
def OpenSecondWindow(self):
self.ex = SumCalc(self)
self.ex.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
SummaryMain.py
import sys
from calculators import summary
from PyQt5 import QtCore, QtGui, QtWidgets
from SummaryUI import Ui_SummaryUI
class SumCalc(QtWidgets.QMainWindow, Ui_SummaryUI):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
Ui_SummaryUI.__init__(self)
self.setupUi(self)
self.CalculateSumBtn.clicked.connect(self.sum_function)
def sum_function(self):
number_a = int(self.FirstNumberInput.text())
number_b = int(self.SecondNumberInput.text())
sum = summary(number_a, number_b)
self.SumResultsValue.setText(str(sum))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = SumCalc()
window.show()
sys.exit(app.exec_())
replace self.ex = SumCalc(self) with self.ex = SumCalc() because the constructor(__init__) function of SumCalc does not take any argument (def __init__(self))
or juts add parameter parent to SumCalc's constructor so it becomes def __init__(self, parent=none)

Sending Data from Child to Parent Window in PyQt5

What I can't do
I'm not able to send data back from a child to a parent window.
What I have
I've got a complex GUI with several windows sendíng data to child windows. Each window represents a unique Python-script in the same directory. There was no need to explicitely specify parents and childs, as the communication was always unidirectional (parent to child). However, now I need to send back data from childs to parents and can't figure out how to do this as each window (i.e. each class) has its own file.
Example
Here's a minimal example showing the base of what I want to accomplish.
What it does: win01 opens win02 and win02 triggers func in win01.
# testfile01.py
import sys
from PyQt5.QtWidgets import *
import testfile02 as t02
class win01(QWidget):
def __init__(self, parent=None):
super(win01, self).__init__(parent)
self.win02 = t02.win02()
self.button = QPushButton("open win02", self)
self.button.move(100, 100)
self.button.clicked.connect(self.show_t02)
def initUI(self):
self.center
def show_t02(self):
self.win02.show()
def func(self):
print("yes!")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = win01()
ex.show()
sys.exit(app.exec_())
##########################################################################
# testfile02.py
from PyQt5.QtWidgets import *
import testfile01 as t01
class win02(QWidget):
def __init__(self, parent=None):
super(win02, self).__init__(parent)
self.win01 = t01.win01()
self.button = QPushButton()
self.button.clicked.connect(self.win01.func)
def initUI(self):
self.center()
What I tried
Importing testfile01 in the second window always leads to the error:
RecursionError: maximum recursion depth exceeded.
Then, I tried the following approaches, but they didn't work either:
Not importing testfile01 in win02 and adjusting parent=None to different other objects
Importing testfile01 within the __init__ call of win02
Creating a signal in win02 to trigger func in win01
The Question
Is there a solution how to properly trigger func in win01 from win02?
Why are you getting RecursionError: maximum recursion depth exceeded?
You are getting it because you have a circular import that generates an infinite loop, in testfile01 you are importing the file testfile02, and in testfile02 you are importing testfile01, .... So that is a shows of a bad design.
Qt offers the mechanism of signals for objects to communicate information to other objects, and this has the advantage of non-dependence between classes that is a great long-term benefit (as for example that avoids circular import), so for that reason I think it is the most appropriate.
For this I will create the clicked signal in the class win02 that will be triggered by the clicked signal of the button, and make that clicked signal call the func:
testfile01.py
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication
import testfile02 as t02
class win01(QWidget):
def __init__(self, parent=None):
super(win01, self).__init__(parent)
self.win02 = t02.win02()
self.win02.clicked.connect(self.func)
self.button = QPushButton("open win02", self)
self.button.move(100, 100)
self.button.clicked.connect(self.show_t02)
def show_t02(self):
self.win02.show()
def func(self):
print("yes!")
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = win01()
ex.show()
sys.exit(app.exec_())
testfile02.py
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout
class win02(QWidget):
clicked = pyqtSignal()
def __init__(self, parent=None):
super(win02, self).__init__(parent)
self.button = QPushButton("call to func")
self.button.clicked.connect(self.clicked)
lay = QVBoxLayout(self)
lay.addWidget(self.button)
I recommend you read:
https://doc.qt.io/qt-5/signalsandslots.html
Both the Widgets are independent and have no link in between.
Set win01 parent of win02.
In class win01
Replace :
self.win01 = t02.win02()
#and
self.win02.show()
with:
self.win01 = t02.win02(self)
#and
self.win01.show()
and in class win02
Replace:
self.win02 = t01.win01()
#and
self.button.clicked.connect(self.win01.func)
with:
self.win02 = self.parent()
#and
self.button.clicked.connect(self.win02.func)

Create another window of same class in PySide

I am creating a small GUI program using PySide. I am having difficulty creating another object of same class. What exactly I am trying to do is that when clicked on a button on MainWindow it should create another independent window of same class.
import sys
from PySide import QtCore, QtGui
class Sticky(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
self.initUI()
def initUI(self):
....
self.addToolBarElements()
....
self.show()
def addToolBarElements(self):
....
self.newwindow = QtGui.QAction(QtGui.QIcon(os.path.join(os.path.dirname(__file__),'icons/new.png')),"New Note",self)
self.newwindow.setStatusTip("New")
self.newwindow.triggered.connect(newwindow)
self.toolBar.addAction(self.newwindow)
def newwindow(self):
#how to create new object of same class
def run():
app = QtGui.QApplication(sys.argv)
notes = Sticky()
sys.exit(app.exec_())
Here is what I have tried:
I have tried multiprocessing but I didn't understand much. I tried calling run() method again but it gives error.
Do not call with the same name 2 different elements, in your case self.newwindow refers to the QAction as the method of the class, avoid it, that is a type of error easy to commit but difficult to find.
going to the point, you just have to create a new object of the class, but the problem is that the garbage collector will eliminate it, to avoid it there are 2 possible options, the first is to make the new window member of the class, or second store it in a list, that's the one I choose because I think you want to have several windows.
import sys
import os
from PySide import QtCore, QtGui
class Sticky(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
self.others_windows = []
self.initUI()
def initUI(self):
self.addToolBarElements()
self.show()
def addToolBarElements(self):
self.toolBar = self.addToolBar("toolBar")
self.newwindow = QtGui.QAction(QtGui.QIcon(os.path.join(os.path.dirname(__file__),'icons/new.png')), "New Note",self)
self.newwindow.setStatusTip("New")
self.newwindow.triggered.connect(self.on_newwindow)
self.toolBar.addAction(self.newwindow)
def on_newwindow(self):
w = Sticky()
w.show()
self.others_windows.append(w)
def run():
app = QtGui.QApplication(sys.argv)
notes = Sticky()
sys.exit(app.exec_())
run()

How to catch a Signal from QMainWindow class by another thread on PySide?

I have a MainWindow class which have a Gui application running on it and i want that every time i click on a button from my application a signal is emitted and caught by another thread. There is my example code (sorry for not posting my real code but it is real big now):
from PySide.QtGui import *
from PySide.QtCore import *
import sys
import mainGui #Gui file
class MainWindow(QMainWindow, mainGui.Ui_MainWindow):
mySignal = Signal()
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.newThread = workThread()
self.newThread.start()
#myButton is part of Gui application
self.myButton.clicked.connect(self.myfunction)
def myfunction(self):
self.mySignal.emit()
(...) #Other functions and methods
class workThread(QThread):
def __init__(self, parent=None):
super(workThread, self).__init__(parent)
#The problem:
MainWindow.mySignal.connect(self.printMessage)
def run(self):
(...)
def printMessage(self):
print("Signal Recived")
(...)
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
if __name__=="__main__":
main()
... and i get the following error:
MainWindow.mySignal.connect(self.printMessage)
AttributeError: 'PySide.QtCore.Signal' object has no attribute 'connect'
There is any ideia how can i solve this?
Thanks in advance!
Signals are just like methods - they must be bound to instances. They won't work correctly if you try to access them directly via the class.
One way to fix the example is to pass the instance of MainWindow in as the parent of the thread, like so:
self.newThread = workThread(self)
...
parent.mySignal.connect(self.printMessage)

Show dialog window

Can anyone tell me why this code is not working? The Test4 class is my converted simple UI:
import sys
import Test4
from PyQt4 import QtGui, QtCore
class UiViewer(QtGui.QApplication, Test4.Ui_Dialog):
def __init__(self, parent=None):
return super(UiViewer, self).__init__(parent)
self.setupUi(self)
def main(self):
self.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
uiViewer = UiViewer()
uiViewer.main()
app.exec_()
first of all
you need to use
if __name__ == '__main__'
not
if name == 'main':
and also adding the Error Message and describing the behavior of the application when you run it will help trace the problem.
from your question, it can be any number of problems.
Your UiViewer class needs to inherit from the same class as the top-level widget in Qt Designer (presumably QDialog, in your case, but it could also be a QMainWindow or a QWidget):
class UiViewer(QtGui.QDialog, Test4.Ui_Dialog):
def __init__(self, parent=None):
super(UiViewer, self).__init__(parent)
self.setupUi(self)
And note that you must not put return before the super call, otherwise the __init__ function will exit at that point, meaning the rest of its code won't be executed (in particular, setupUi would not be called).

Categories

Resources