Use signals stored in a python dict - python

I would like to dynamically create then manipulate lots of widgets. My idea is to store widgets in a dict (mywidgets) and to trigger them with signals stored in another dict (mysignals). Both dict shared same keys defined in a list (names), dicts are initialized with for loops.
When I connect signals to slots, I'm currently facing an AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'.
I have tried to disable signal/slot connections: the GUI looks good, QLineEdit are well stored in mywidgets. Types of mysignals items are correct: class 'PyQt5.QtCore.pyqtSignal'.
Can you, please, explain me where the issue come from ?
Thanks.
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QPushButton, QVBoxLayout
from PyQt5.QtCore import pyqtSlot, pyqtSignal
class App(QWidget):
names = ["foo","bar"]
mysignals = {} # Store several signals in a dict
for name in names:
mysignals[name] = pyqtSignal(str)
def __init__(self):
super().__init__()
# Create Widgets
self.btn_go = QPushButton("Go") #Simple push button
self.mywidgets = {} #Store several QLineEdit in a dict
for name in self.names:
self.mywidgets[name] = QLineEdit()
# Connect signals
self.btn_go.clicked.connect(self.on_click) #Connect push button
for name in self.names:
print(type(self.mysignals[name]))
self.mysignals[name].connect(self.mywidgets[name].setText) #Connect several signals
# Configure layout
layout = QVBoxLayout()
layout.addWidget(self.btn_go)
for name in self.names:
layout.addWidget(self.mywidgets[name])
self.setLayout(layout)
# Show widget
self.show()
#pyqtSlot()
def on_click(self):
data = {"foo":"Python3","bar":"PyQt5"}
for key,value in data.items():
self.mysignals[key].emit(value)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
The expected result is to display respectivelly Python3 and PyQt5 in mywidgets["foo"] and mywidgets["bar"] QLineEdit widgets when push button is clicked.

As the docs point out:
A signal (specifically an unbound signal) is a class attribute. When a
signal is referenced as an attribute of an instance of the class then
PyQt5 automatically binds the instance to the signal in order to
create a bound signal. This is the same mechanism that Python itself
uses to create bound methods from class functions.
A signal is declared as an attribute of the class but when referenced through self a bind is made with the object, that is, the declared signal is different from the instantiated signal:
from PyQt5 import QtCore
class Foo(QtCore.QObject):
fooSignal = QtCore.pyqtSignal()
print("declared:", fooSignal)
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
print("instantiated:", self.fooSignal)
if __name__ == '__main__':
import sys
app = QtCore.QCoreApplication(sys.argv)
obj = Foo()
Output:
declared: <unbound PYQT_SIGNAL )>
instantiated: <bound PYQT_SIGNAL fooSignal of Foo object at 0x7f4beb998288>
That is the reason for the error you get, so if you want to use the signals you must obtain it using the object so we can inspect the attributes and get the signals:
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
foo = QtCore.pyqtSignal(str)
bar = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.fill_signals()
self.names = ["foo", "bar"]
self.btn_go = QtWidgets.QPushButton("Go")
self.mywidgets = {}
for name in self.names:
self.mywidgets[name] = QtWidgets.QLineEdit()
signal = self.mysignals.get(name)
if signal is not None:
signal.connect(self.mywidgets[name].setText)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.btn_go)
for name in self.names:
layout.addWidget(self.mywidgets[name])
self.btn_go.clicked.connect(self.testing)
def fill_signals(self):
self.mysignals = dict()
for p in dir(self):
attr = getattr(self, p)
if isinstance(attr, QtCore.pyqtBoundSignal):
self.mysignals[p] = attr
def testing(self):
self.foo.emit("foo")
self.bar.emit("bar")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

Sorry, I think you have complicated the algorithm to obtain the expected result. Try it:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QPushButton, QVBoxLayout
from PyQt5.QtCore import pyqtSlot, pyqtSignal
class App(QWidget):
def __init__(self, names):
super().__init__()
self.names = names
layout = QVBoxLayout()
# Create Widgets
self.btn_go = QPushButton("Go") # Simple push button
self.btn_go.clicked.connect(self.on_click) # Connect push button
layout.addWidget(self.btn_go)
self.mywidgets = {} # Store several QLineEdit in a dict
for name in self.names:
self.mywidgets[name] = QLineEdit()
layout.addWidget(self.mywidgets[name])
self.setLayout(layout)
#pyqtSlot()
def on_click(self):
data = {"foo":"Python3", "bar":"PyQt5"}
for key, value in data.items():
self.mywidgets[key].setText(value)
if __name__ == '__main__':
app = QApplication(sys.argv)
names = ["foo", "bar"]
ex = App(names)
ex.show()
sys.exit(app.exec_())

Related

How to pass a pandas dataframe from main class to another class?

There are lots of widgets in the original code and that is why I need to open the file in the main window. Therefore, I need to pass a dataframe (data_df) that comes from a csv file open in the main menu (main class) to 'MyApp' class. I will use the dataframe (input_df) to perform calculations down the road.
How to pass the data from main class to MyApp class?
# Import dependencies
from PyQt5.QtWidgets import (QWidget, QApplication, QTableWidget, QTableWidgetItem, QHBoxLayout, QVBoxLayout, QHeaderView, QPushButton, QCheckBox,
QLabel, QFileDialog, QMainWindow, QAction, QLineEdit, QMessageBox, QComboBox, QSizePolicy)
from PyQt5.Qt import Qt, QPen, QFont
from PyQt5.QtGui import *
from PyQt5.QtChart import QChart, QChartView, QLineSeries, QCategoryAxis
import sys
import pandas as pd
import math
import csv
# Creates a QApplication instance
class MyApp(QWidget):
def __init__(self):
super().__init__()
# Creates layout object
self.layout = QHBoxLayout()
# Create push buttons
self.buttonCalc = QPushButton('Calculate')
self.layout.addWidget(self.buttonCalc)
# Connect button to function
self.buttonCalc.clicked.connect(self.calculate)
def displayInfo(self):
self.show()
# Create a Model to handle the calculator's operation
def calculate(self):
# get dataframe
input_df = df
# Create a subclass of QMainWindow to setup the main GUI
class MainWindow(QMainWindow):
def __init__(self, w):
super().__init__()
self.setWindowTitle('My code')
# for icon, uncomment line below
#self.setWindowIcon(QIcon(r'c:\image.png'))
self.resize(1200, 1200)
self.myApp = MyApp()
self.menuBar = self.menuBar()
self.fileMenu = self.menuBar.addMenu('File')
# import data
importAction = QAction('Open csv File', self)
importAction.setShortcut('Ctrl+O')
importAction.triggered.connect(self.openSeries)
# exit action
exitAction = QAction('Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(lambda: app.quit())
self.fileMenu.addAction(importAction)
self.fileMenu.addAction(exitAction)
self.setCentralWidget(w)
def openSeries(self):
self.filePath = QFileDialog.getOpenFileName(self, 'Open data series csv file', 'C:\', 'CSV(*.csv)')
if self.filePath != ('', ''):
file_data = self.filePath[0]
data_df = pd.read_csv(file_data, encoding='ISO-8859-1')
# I need to pass this dataframe to MyApp class
return data_df
def passInformation(self):
self.myApp.input_df
if __name__ =='__main__':
app = QApplication(sys.argv)
w = MyApp()
window = MainWindow(w)
window.show()
try:
sys.exit(app.exec())
except SystemExit:
print('Closing window...')
You can pass the data from one another through the __init__ method using something like this on your main window class:
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.init_ui()
def goToOtherWindow(self, variable):
self.window = OtherWindow(variable)
self.window.show()
self.close()
On OtherWindow class:
class OtherWindow(QtWidgets.QWidget):
def __init__(self, variable, parent=None):
super(OtherWindow, self).__init__(parent)
self.variable = variable
self.init_ui()
Of course, you have to adapt this function to your specific case.

Include MenuBar from seperated file

I am stack trying to include MenuBar from separate file and trying to connect with function
I include some code that I have the same problem
foo.py
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUiType
import os
import sys
from foomenu import menu
FROM_MAIN, _ = loadUiType(os.path.join(os.path.dirname(__file__), "SalesGui.ui"))
class Main(QMainWindow, FROM_MAIN):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
self.MyMenu = menu(self)
self.MyMenu.NewProduct.triggered.connect(self.NewProduct())
def NewProduct(self):
print("foo")
def main():
app = QApplication(sys.argv)
window = Main()
window.show()
app.exec_()
if __name__ == '__main__':
try:
main()
except Exception as why:
print(why)
and foomenu.py
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QMenu
def menu(self):
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu('FooMenu')
NewProduct = QAction(QIcon('icons/exit.png'), 'Foo', self)
NewProduct.setShortcut('Ctrl+Q')
NewProduct.setStatusTip('FooAction')
fileMenu.addAction(NewProduct)
When Trying to connect the "NewProduct" button with "New Product" function I get the following error
'NoneType' object has no attribute 'NewProduct'
Try it:
import os
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QMenu
#from PyQt5.uic import loadUiType
#from foomenu import menu
#FROM_MAIN, _ = loadUiType(os.path.join(os.path.dirname(__file__), "SalesGui.ui"))
def menu(self):
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu('FooMenu')
self.NewProduct = QAction(QIcon('exit.png'), 'Foo', self) # 'icons/exit.png' # + self
self.NewProduct.setShortcut('Ctrl+Q')
self.NewProduct.setStatusTip('FooAction')
fileMenu.addAction(self.NewProduct)
return self.NewProduct # +++
class Main(QMainWindow):#, FROM_MAIN):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
# self.setupUi(self)
self.MyMenu = menu(self)
# self.MyMenu.NewProduct.triggered.connect(self.funcNewProduct) # - ()
self.MyMenu.triggered.connect(self.funcNewProduct) # +
def funcNewProduct(self):
print("foo")
qApp.quit()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Main()
window.show()
app.exec_()
It is much more elegant to subclass and setup the menubar in this another python file then import and instance this class in your file that holds the Main class.
In the MyMenu class you can binding the signal a socket locally that can provide from here the sender object for the parent class.
BTW you can iterate over the action object of the menubar/menus but it is much more harder to maintain and control then the explicit describe where to connect the potential different actions.
Menu.py:
class MyMenu(QMenuBar):
new_product_clicked = Signal(object)
def __init__(self, parent=None):
super(MyMenu, self).__init__(parent)
file_menu = QMenu("File menu", self)
new_product_action = QAction('Foo', self)
new_product_action.setShortcut('Ctrl+Q')
new_product_action.setStatusTip('FooAction')
new_product_action.triggered.connect(self.new_product_clicked)
file_menu.addAction(new_product_action)
self.addMenu(file_menu)
def new_product_clicked(self):
""" Call a method from the parent class. """
self.new_product_clicked.emit(self.sender())
Main.py:
from Menu import MyMenu
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
my_menu = MyMenu(self)
self.setMenuBar(my_menu)
self.my_menu.new_product_clicked.connect(self.product_clicked)
def product_clicked(self, action):
""" Socket for the clicked action """
clicked_action = action
print clicked_action.text()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Main()
window.show()
app.exec_()

How to populate several QComboBox from a QFileSystemModel?

How does one use a QFileSystemModel to populate several QComboBox with subdirectories?
I have built a project management tool that allows me to create and manage my projects. I am currently using a combination of os.listdir and json to populate and validate my QComboboxes. But I am trying to learn a more modelview approach with QFileSystemModel.
So this is what I have:
class FileSystemModel(QW.QFileSystemModel):
def __init__(self, root, parent=None):
QW.QFileSystemModel.__init__(self, parent)
self.root = root
self.rootIndex = self.setRootPath(root)
class Window(QW.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__()
self.init()
def init(self):
layout = QW.QVBoxLayout()
self.cbox = QW.QComboBox()
self.cbox2 = QW.QComboBox()
self.model = FileSystemModel("C:\\projects\\")
self.cbox.setModel(self.model)
self.cbox2.setModel(self.model)
self.cbox.setRootModelIndex(self.model.rootIndex)
self.cbox.currentIndexChanged.connect(self._indexChanged)
layout.addWidget(self.cbox)
layout.addWidget(self.cbox2)
self.setLayout(layout)
def _indexChanged(self):
row = self.sender().currentIndex()
index = self.sender().rootModelIndex().child(row, 0)
self.cbox2.setRootModelIndex(index)
def main():
app = QW.QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
I was attempting to repopulate the cbox2 using the index from cbox, but with my code it doesn't seem to work - it just stays empty.
Okay here is modified version of what you had:
from sys import exit as sysExit
from PyQt5.QtCore import QDir, pyqtSlot
from PyQt5.QtWidgets import QApplication, QWidget, QFileSystemModel, QHBoxLayout, QComboBox
class SysDirModel(QFileSystemModel):
def __init__(self, DirPath):
QFileSystemModel.__init__(self)
self.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs)
self.setReadOnly(True)
# Property
self.setRootPath(DirPath)
# Property
self.RootIndex = self.index(DirPath)
class SysFileModel(QFileSystemModel):
def __init__(self, DirPath):
QFileSystemModel.__init__(self)
self.setFilter(QDir.NoDotAndDotDot | QDir.Files)
self.setReadOnly(True)
# Property
self.setRootPath(DirPath)
# Property
self.RootIndex = self.index(DirPath)
def ResetPath(self, DirPath):
self.setRootPath(DirPath)
self.RootIndex = self.index(DirPath)
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setGeometry(150, 150, 450, 100)
# If you use forward slash this works in Windows as well and it is cleaner
self.SysDirs = SysDirModel('C:/projects/')
self.SysFils = SysFileModel('C:/projects/')
# Setup first ComboBox
self.cbxDirs = QComboBox()
self.cbxDirs.setMinimumWidth(200)
self.cbxDirs.setModel(self.SysDirs)
self.cbxDirs.setRootModelIndex(self.SysDirs.RootIndex)
# This sends a Signal to a predefined Slot
self.cbxDirs.currentIndexChanged.connect(self.IndexChanged)
self.cbxFiles = QComboBox()
self.cbxFiles.setMinimumWidth(200)
self.cbxFiles.setModel(self.SysFils)
self.cbxFiles.setRootModelIndex(self.SysFils.RootIndex)
HBox = QHBoxLayout()
HBox.addWidget(self.cbxDirs)
HBox.addStretch(1)
HBox.addWidget(self.cbxFiles)
self.setLayout(HBox)
# This is the receiver of a Signal (aka Slot) so it ought to be used as such
#pyqtSlot(int)
def IndexChanged(self, RowIdx):
# Get your Current DirPath based on the Selected Value
index = self.cbxDirs.rootModelIndex().child(RowIdx, 0)
DirPath = self.cbxDirs.model().filePath(index)
# Reset what ComboBox 2's Model and what it is looking at
self.cbxFiles.clear()
self.SysFils.ResetPath(DirPath)
self.cbxFiles.setModel(self.SysFils)
if __name__ == '__main__':
MainThred = QApplication([])
MainGui = MainWindow()
MainGui.show()
sysExit(MainThred.exec_())

How to emit custom Events to the Event Loop in PyQt

I am trying to emit custom events in PyQt. One widget would emit and another would listen to events, but the two widgets would not need to be related.
In JavaScript, I would achieve this by doing
// Component 1
document.addEventListener('Hello', () => console.log('Got it'))
// Component 2
document.dispatchEvent(new Event("Hello"))
Edit: I know about signals and slots, but only know how to use them between parent and child. How would I this mechanism (or other mechanism) between arbitrary unrelated widgets?
In PyQt the following instruction:
document.addEventListener('Hello', () => console.log('Got it'))
is equivalent
document.hello_signal.connect(lambda: print('Got it'))
In a similar way:
document.dispatchEvent(new Event("Hello"))
is equivalent
document.hello_signal.emit()
But the big difference is the scope of the "document" object, since the connection is between a global element. But in PyQt that element does not exist.
One way to emulate the behavior that you point out is by creating a global object:
globalobject.py
from PyQt5 import QtCore
import functools
#functools.lru_cache()
class GlobalObject(QtCore.QObject):
def __init__(self):
super().__init__()
self._events = {}
def addEventListener(self, name, func):
if name not in self._events:
self._events[name] = [func]
else:
self._events[name].append(func)
def dispatchEvent(self, name):
functions = self._events.get(name, [])
for func in functions:
QtCore.QTimer.singleShot(0, func)
main.py
from PyQt5 import QtCore, QtWidgets
from globalobject import GlobalObject
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
button = QtWidgets.QPushButton(text="Press me", clicked=self.on_clicked)
self.setCentralWidget(button)
#QtCore.pyqtSlot()
def on_clicked(self):
GlobalObject().dispatchEvent("hello")
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
GlobalObject().addEventListener("hello", self.foo)
self._label = QtWidgets.QLabel()
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self._label)
#QtCore.pyqtSlot()
def foo(self):
self._label.setText("foo")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w1 = MainWindow()
w2 = Widget()
w1.show()
w2.show()
sys.exit(app.exec_())

How to return variables from PyQt5 UI to Main function - Python

I have design a customized formlayout ui using pyqt5 and want to import variables back to the main function for further execution of the main function.
I have tried many ways to get the return values from the main function when the "OK" button has clicked but unable to get the variables from the main function.
Can you please guide me, how can i get the variables from the pyqt5 formlayout ui to main function -
Here is the Code of PyQt5 FormLayout UI function -
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit,
QVBoxLayout,QCheckBox)
import sys
app = QApplication([])
class Dialog(QDialog):
def __init__(self,dinput):
super(Dialog, self).__init__()
self.createFormGroupBox(dinput)
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(buttonBox)
self.setLayout(mainLayout)
self.setWindowTitle("Form Layout")
def accept(self):
print(self.linedit1.text())
print(self.combox1.currentText())
print(self.spinbox1.value())
self.closeEvent()
def reject(self):
print('Cancelled')
self.closeEvent()
def getoutput(self):
return self.linedit1.text()
def createFormGroupBox(self,dinput):
self.formGroupBox = QGroupBox("Form layout")
layout = QFormLayout()
self.linedit1 = QLineEdit()
self.linedit1.setText('TestName')
layout.addRow(QLabel(dinput[0]), self.linedit1)
self.combox1 = QComboBox()
self.combox1.setToolTip('Hello')
self.combox1.addItems(['India','France','UK','USA','Germany'])
layout.addRow(QLabel(dinput[1]), self.combox1)
self.spinbox1 = QSpinBox()
layout.addRow(QLabel(dinput[2]), self.spinbox1)
self.formGroupBox.setLayout(layout)
Main Function is -
import os
import sys
import pyformlayout as pyfl
# Staring Functions for Execution
dinput = ['LastName','Country','Age']
# Call the UI and get the inputs
dialog = pyfl.Dialog(dinput)
if(dialog.exec_()):
TName = dialog.getoutput
print('------------------')
print(TName)
# Main Function Continous by getting the inputs
# from UI
I am unable to get the desired values to the output function. Even i have used the getoutput function to return the values and get the output to "TName". But i am not able to get the value into the TName variable and nothing is displaying.
The Result i am getting is - (which is basically printing the accept button function but not the TName variable which is returned to Main function.
TestName
India
25
How can i get the return values from PyQt5 Formlayout UI function to Main function..?
In the first place, FormLayout is a layout, that is, a class that is responsible for positioning the widgets within a window, so it is irrelevant for these cases. On the other hand, closeEvent() should never be invoked, that is a function that serves to handle the closed window event.
Going to the point the accept method is called when Ok is pressed, so it is the right place to get the values so it must be stored in a variable, and then returned in the get_output() method:
pyformlayout.py
import sys
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
class Dialog(QtWidgets.QDialog):
def __init__(self, dinput):
super(Dialog, self).__init__()
self.createFormGroupBox(dinput)
buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
mainLayout = QtWidgets.QVBoxLayout(self)
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(buttonBox)
self.setWindowTitle("Form Layout")
def createFormGroupBox(self, dinput):
layout = QtWidgets.QFormLayout()
self.linedit1 = QtWidgets.QLineEdit('TestName')
self.combox1 = QtWidgets.QComboBox()
self.combox1.setToolTip('Hello')
self.combox1.addItems(['India','France','UK','USA','Germany'])
self.spinbox1 = QtWidgets.QSpinBox()
for text, w in zip(dinput, (self.linedit1, self.combox1, self.spinbox1)):
layout.addRow(text, w)
self.formGroupBox = QtWidgets.QGroupBox("Form layout")
self.formGroupBox.setLayout(layout)
def accept(self):
self._output = self.linedit1.text(), self.combox1.currentText(), self.spinbox1.value()
super(Dialog, self).accept()
def get_output(self):
return self._output
And in the file main.py I get the value if only the ok button has been pressed:
main.py
import pyformlayout as pyfl
# Staring Functions for Execution
dinput = ['LastName','Country','Age']
# Call the UI and get the inputs
dialog = pyfl.Dialog(dinput)
if dialog.exec_() == pyfl.Dialog.Accepted:
name, item, value = dialog.get_output()
print(name, item, value)

Categories

Resources