Passing parameters from programatically-built buttons - python

I'm trying to build a little PyQt5 application that will do some work on a directory of files for me. I need to build a QGridLayout to hold the buttons. Naturally, the contents are subject to change, so I'm building the buttons by looping through a list of simple objects (path, filename, etc.).
My question is this: how do I pass an identifying parameter from the button's "on_click" action when they're built from a loop? I don't care too much what I return from the click - I can find the matching file object one way or another.
Here's my code:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QGroupBox, QDialog, QVBoxLayout, \
QGridLayout
from PyQt5.QtCore import pyqtSlot
from DirListing import Listing
class App(QDialog):
def __init__(self):
super().__init__()
self.title = 'Grid Test'
self.left = 10
self.top = 10
self.width = 320
self.height = 500
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.createGridLayout()
windowLayout = QVBoxLayout()
windowLayout.addWidget(self.horizontalGroupBox)
self.setLayout(windowLayout)
self.show()
def createGridLayout(self):
self.horizontalGroupBox = QGroupBox("Grid")
layout = QGridLayout()
counter = 0
for obj in Listing:
button = QPushButton(obj.filename)
button.clicked.connect(self.on_click)
layout.addWidget(button, counter, 0)
counter = counter + 1
self.horizontalGroupBox.setLayout(layout)
#pyqtSlot()
def on_click(self):
print("Halp!")
def lower(self):
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
The fun part is just the little loop that begins with "for obj in Listing" - that's my loop of file objects. I use a simple counter to go down one row in the GridLayout for each button. But I can't figure out how to pass a unique parameter out of the click function.

There are several ways to perform this task, I will list some:
Use sender() which is a function that returns the object that emits the signal and some attribute of the widget, in the case of QPushButton you can use the text that is shown
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.setLayout(QVBoxLayout())
for i in range(10):
button = QPushButton("{}".format(i), self)
self.layout().addWidget(button)
button.clicked.connect(self.on_click)
#pyqtSlot()
def on_click(self):
print("button-{}".format(self.sender().text()))
Using objectName() where each widget is given a particular name
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.setLayout(QVBoxLayout())
for i in range(10):
button = QPushButton("{}".format(i), self)
self.layout().addWidget(button)
button.setObjectName("button-{}".format(i))
button.clicked.connect(self.on_click)
#pyqtSlot()
def on_click(self):
print(self.sender().objectName())
Using lambda functions
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.setLayout(QVBoxLayout())
for i in range(10):
button = QPushButton("{}".format(i), self)
self.layout().addWidget(button)
button.clicked.connect(lambda checked, i=i: self.on_click(i))
def on_click(self, i):
print("button-{}".format(i))
For buttons you can use QButtonGroup
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.setLayout(QVBoxLayout())
group = QButtonGroup(self)
for i in range(10):
button = QPushButton("{}".format(i), self)
self.layout().addWidget(button)
group.addButton(button, i)
group.buttonClicked[int].connect(self.on_click)
group.buttonClicked.connect(self.on_click2)
#pyqtSlot(int)
def on_click(self, i):
print("button-{}".format(i))
def on_click2(self, btn):
print("2button-{}".format(btn.text()))
Using functools.partial
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.setLayout(QVBoxLayout())
for i in range(10):
button = QPushButton("{}".format(i), self)
self.layout().addWidget(button)
import functools
button.clicked.connect(functools.partial(self.on_click, i))
#pyqtSlot()
def on_click(self, i):
print("button-{}".format(i))

Related

PyQt5 Automatic drawing from input value

I have recently created a widget with Qpaint, which I want to pass value to it, at the same time force the Qpaint Widget to draw from input values. The idea is to define a data value from a Qdialog and pass it to main widget, and pass the value to Qpaint Widget class. I would like to have, when user clicks on the button 'Getting values' a dialog widget would appear and insert some int values, then pass it to main Widget. from there pass value to correct class Paint. Which would draw and display the result. I tried with Qlabel, to assign value first to Qlabel or QlineEdit,
class Button(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Button, self).__init__(parent)
---------
self.value = QtWidgets.QLabel()
--------
Then inside the paint class call the value or text of those. then assign it to Qpaint event. But seems does not work.'
class Paint(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Paint, self).__init__(parent)
self.button = Button()
self.Value = self.button.value
---------
painter.drawRect(100,100,250,250) <----- instead of value 250 having self.Value
The code Main.py
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from datainput import *
class Foo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 800, 800))
self.button = Button()
self.paint = Paint()
self.lay = QtWidgets.QVBoxLayout()
self.lay.addWidget(self.paint)
self.lay.addWidget(self.button)
self.setLayout(self.lay)
class Paint(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Paint, self).__init__(parent)
self.button = Button()
self.Value = self.button.value
self.setBackgroundRole(QtGui.QPalette.Base)
self.setAutoFillBackground(True)
def paintEvent(self, event):
self.pen = QtGui.QPen()
self.brush = QtGui.QBrush( QtCore.Qt.gray, QtCore.Qt.Dense7Pattern)
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(self.pen)
painter.setBrush(self.brush)
painter.drawRect(100,100,250,250)
painter.setBrush(QtGui.QBrush())
class Button(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Button, self).__init__(parent)
getbutton = QtWidgets.QPushButton('Getting values')
Alay = QtWidgets.QVBoxLayout(self)
Alay.addWidget(getbutton)
self.value = QtWidgets.QLabel()
getbutton.clicked.connect(self.getbuttonfunc)
def getbuttonfunc(self):
subwindow=Dinput()
subwindow.setWindowModality(QtCore.Qt.ApplicationModal)
if subwindow.exec_() == QtWidgets.QDialog.Accepted:
self._output = subwindow.valueEdit.text()
return self.value.setText(self._output)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())
Input Qdialog code, datainput.py
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Dinput(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Dinput, self).__init__(parent)
valuelabel = QtWidgets.QLabel('Input: ')
self.valueEdit = QtWidgets.QLineEdit()
buttonBox = QtWidgets.QDialogButtonBox()
buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.close)
self.Alay = QtWidgets.QHBoxLayout()
self.Alay.addWidget(valuelabel)
self.Alay.addWidget(self.valueEdit)
self.Blay = QtWidgets.QVBoxLayout()
self.Blay.addLayout(self.Alay)
self.Blay.addWidget(buttonBox)
self.setLayout(self.Blay)
def closeEvent(self, event):
super(Dinput, self).closeEvent(event)
def accept(self):
super(Dinput, self).accept()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Dinput()
w.show()
sys.exit(app.exec_())
Visualization
I appreciate any help. Thankssss
datainput is irrelevant, your task is only to obtain a number so for space question I will not use it and instead I will use QInputDialog::getInt(). Going to the problem, the strategy in these cases where the value can be obtained at any time is to notify the change to the other view through a signal, in the slot that receives the value is to update a variable that stores the value and call update so that it calls when necessary to paintEvent, and in the paintEvent use the variable that stores the value.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Foo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 800, 800))
self.button = Button()
self.paint = Paint()
self.button.valueChanged.connect(self.paint.set_size_square)
self.lay = QtWidgets.QVBoxLayout(self)
self.lay.addWidget(self.paint)
self.lay.addWidget(self.button)
class Paint(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Paint, self).__init__(parent)
self.setBackgroundRole(QtGui.QPalette.Base)
self.setAutoFillBackground(True)
self._size_square = 250
#QtCore.pyqtSlot(int)
def set_size_square(self, v):
self._size_square = v
self.update()
def paintEvent(self, event):
pen = QtGui.QPen()
brush = QtGui.QBrush( QtCore.Qt.gray, QtCore.Qt.Dense7Pattern)
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(pen)
painter.setBrush(brush)
r = QtCore.QRect(QtCore.QPoint(100, 100), self._size_square*QtCore.QSize(1, 1))
painter.drawRect(r)
class Button(QtWidgets.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(Button, self).__init__(parent)
getbutton = QtWidgets.QPushButton('Getting values')
Alay = QtWidgets.QVBoxLayout(self)
Alay.addWidget(getbutton)
self.value = QtWidgets.QLabel()
getbutton.clicked.connect(self.getbuttonfunc)
#QtCore.pyqtSlot()
def getbuttonfunc(self):
number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
self.tr("Input:"), 1, 1)
if ok:
self.valueChanged.emit(number)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())

Mutual resizing of widgets

I'm implementing an application, where a side widget can expand/shrink, so an another widget must shrink/expand. (Or the side widget can be shown over that widget, it doesn't matter, I accept both implementations). It can looks like that:
Here is a part of my code:
class AppView:
def __init__(self):
self._mainWindow = QDialog(None)
self._schedule = ScheduleView(self._mainWindow)
self._schedule.setMinimumWidth(25)
self._schedule.setMaximumWidth(250)
self._tutorial = TutorialView(self._mainWindow)
self._schedule.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
self._tutorial.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
layout = QHBoxLayout()
layout.addWidget(self._schedule)
layout.addWidget(self._tutorial)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 1)
self._mainWindow.setLayout(layout)
class TutorialView(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent=parent)
self._presenter = TutorialPresenter(self)
self.reqReprSections.connect(self.setModel)
self.reqReprTopics.connect(self.setModel)
self._widget = QQuickWidget(self)
self._widget.rootContext().setContextProperty('tutorialView', self)
self._widget.setSource(QUrl('modules/manual/manualForm/TutorialForm.qml'))
class ScheduleView(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent=parent)
self._presenter = SchedulePresenter(self)
self._widget = QQuickWidget(self)
self._widget.setResizeMode(QQuickWidget.SizeViewToRootObject)
self._widget.rootContext().setContextProperty('scheduleView', self)
self._widget.rootContext().setContextProperty('groupsModel', self)
self._widget.setSource(QUrl('modules/schedule/scheduleForm/ScheduleForm.qml'))
How can I do such resizes in code?
To get that behavior you can use a QHBoxLayout by embedding a rotated button in the middle of the side widgets. You must change the left widget's size policy so that it does not expand.
To implement the rotated button you must override the paintEvent method, in addition to modifying the size policy so that it expands vertically and not horizontally.
class ShrinkExpandButton(QPushButton):
def __init__(self, *args, **kwargs):
QPushButton.__init__(self, *args, **kwargs)
self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding)
self.setFixedWidth(2*self.fontMetrics().height())
def paintEvent(self, event):
painter = QStylePainter(self)
painter.rotate(-90)
painter.translate(-self.height(), 0)
option = QStyleOptionButton()
self.initStyleOption(option)
size = option.rect.size()
size.transpose()
option.rect.setSize(size)
painter.drawControl(QStyle.CE_PushButton, option)
class ShrinkExpandWidget(QWidget):
def __init__(self, leftWidget, rightWiget, text, parent=None):
QWidget.__init__(self, parent)
button = ShrinkExpandButton(text, self)
self.setLayout(QHBoxLayout())
self.layout().setSpacing(0)
self.layout().addWidget(leftWidget)
self.layout().addWidget(button)
self.layout().addWidget(rightWiget)
leftWidget.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding)
button.clicked.connect(lambda: leftWidget.setVisible(not leftWidget.isVisible()))
Example:
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
listWidget = QListWidget()
for i in range(20):
listWidget.addItem("{}".format(i))
tableWidget = QTableWidget()
tableWidget.setColumnCount(10)
tableWidget.setRowCount(20)
for i in range(tableWidget.rowCount()):
for j in range(tableWidget.columnCount()):
tableWidget.setItem(i, j, QTableWidgetItem("({}, {})".format(i, j)))
listWidget.setFixedWidth(240)
w = ShrinkExpandWidget(listWidget, tableWidget, "Shrink - Expand")
w.resize(720, 480)
w.show()
sys.exit(app.exec_())
Output:

When one checkbox is checked, by pressing a button to print some texts from a LineEdit if another checkbox is also checked

import sys, os
import PyQt4
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Tab1Widget1(QWidget):
def __init__(self, parent=None):
super().__init__()
self.Tab1Widget1initUI()
self.bridge = Tab1Widget2()
def Tab1Widget1initUI(self):
self.setLayout(QGridLayout())
self.T1W1_checkbox = QCheckBox('checkbox1', self)
self.layout().addWidget(self.T1W1_checkbox, 1, 0)
def test(self):
print ('123')
def run(self):
if self.T1W1_checkbox.isChecked() == True:
self.test()
if self.bridge.T1W2_checkbox.isChecked() == True:
print (self.bridge.T1W2_le.text())
class Tab1Widget2(QWidget):
def __init__(self, parent=None):
super().__init__()
self.setLayout(QGridLayout())
self.T1W2_checkbox = QCheckBox('checkbox2', self)
self.layout().addWidget(self.T1W2_checkbox, 0, 0)
self.T1W2_le = QLineEdit()
self.layout().addWidget(self.T1W2_le, 0, 1)
class Tab1Layout(QWidget):
def __init__(self, parent=None):
super().__init__()
self.setLayout(QGridLayout())
self.group1 = Tab1Widget1(self)
scroll = QScrollArea(self)
scroll.setWidget(self.group1)
scroll.setWidgetResizable(True)
self.layout().addWidget(scroll, 0, 0)
self.group2 = Tab1Widget2(self)
self.layout().addWidget(self.group2, 1, 0)
self.btnRun = QPushButton('Run', self)
self.layout().addWidget(self.btnRun, 3, 0)
self.btnRun.clicked.connect(self.group1.run)
class Page1(QTabWidget):
def __init__(self, parent=None):
super().__init__()
self.tab1 = Tab1Layout()
self.addTab(self.tab1, "Tab1")
self.tab2 = QWidget()
self.tab3 = QWidget()
self.addTab(self.tab2, "Tab2")
self.addTab(self.tab3, "Tab3")
self.tab2_initUI()
self.tab3_initUI()
def tab2_initUI(self):
grid = QGridLayout()
self.tab2.setLayout(grid)
def tab3_initUI(self):
grid = QGridLayout()
self.tab3.setLayout(grid)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__()
self.setGeometry(450, 250, 800, 550)
self.startPage1()
def startPage1(self):
x = Page1(self)
self.setWindowTitle("Auto Benchmark")
self.setCentralWidget(x)
self.show()
def main():
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If checkbox1 is checked and I press the run button, it will print 123. However, by pressing run button, I want checkbox2 to also print some texts entered in lineedit if the checkbox1 are also checked (i.e. it should print 123 first and then print 456).
I've looked up some similar types of questions, but none of that provides a proper answer. If anyone knows how to solve it, pls let me know thanks!!
The problem is that you are creating several Tab1Widget2, the first one you created in Tab1Layout, and that is the one you see, then you have created another one in Tab1Widget1, but not the time because you have not passed a parent, if you pass it to ** self ** as parent you will observe the following:
self.bridge = Tab1Widget2(self)
which is not what you want, so instead of creating a new you must pass the one that already exists, an option is to pass it through the constructor:
class Tab1Widget1(QWidget):
def __init__(self, bridge, parent=None): # Modify here
super().__init__(parent)
self.Tab1Widget1initUI()
self.bridge = bridge #Modify here
# ...
def test(self): print ('123')
def run(self):
if self.T1W1_checkbox.isChecked():
self.test()
if self.bridge.T1W2_checkbox.isChecked():
print (self.bridge.T1W2_le.text())
class Tab1Widget2(QWidget):
# ...
class Tab1Layout(QWidget):
def __init__(self, parent=None):
super().__init__()
self.setLayout(QGridLayout())
self.group2 = Tab1Widget2(self) # Modify here
self.group1 = Tab1Widget1(self.group2, self) # Modify here
scroll = QScrollArea(self)
scroll.setWidget(self.group1)
scroll.setWidgetResizable(True)
self.layout().addWidget(scroll, 0, 0)
# self.group2 = Tab1Widget2(self) # Modify here
self.layout().addWidget(self.group2, 1, 0)
# ...

PyQt5: QPushButton double click?

I can't find a good answer for this: is there a way for double click to execute a certain function, and single click one other function?? For example:
def func1(self):
print('First function')
def func2(self):
print('Second function')
self.ui.button.clicked.connect(self.func1)
self.ui.button.doubleClicked.connect(self.func2)
I've seen double clicking is possible for the QTreeview but not a QPushButton. Thanks!
You can add the functionality easily yourself by extending QPushButton class:
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class QDoublePushButton(QPushButton):
doubleClicked = pyqtSignal()
clicked = pyqtSignal()
def __init__(self, *args, **kwargs):
QPushButton.__init__(self, *args, **kwargs)
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.clicked.emit)
super().clicked.connect(self.checkDoubleClick)
#pyqtSlot()
def checkDoubleClick(self):
if self.timer.isActive():
self.doubleClicked.emit()
self.timer.stop()
else:
self.timer.start(250)
class Window(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.button = QDoublePushButton("Test", self)
self.button.clicked.connect(self.on_click)
self.button.doubleClicked.connect(self.on_doubleclick)
self.layout = QHBoxLayout()
self.layout.addWidget(self.button)
self.setLayout(self.layout)
self.resize(120, 50)
self.show()
#pyqtSlot()
def on_click(self):
print("Click")
#pyqtSlot()
def on_doubleclick(self):
print("Doubleclick")
app = QApplication(sys.argv)
win = Window()
sys.exit(app.exec_())
However, I would not recommend it. Users do not expect to double-click buttons. You can refer to Command Buttons Microsoft guidelines.

Qt and python - how to refer to another class

I have main window which contains scene and button in widget from where I need to call scene:
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class Widget(QWidget):
def __init__(self, scene):
super(Widget, self).__init__()
self.refreshButton = QPushButton("Refresh", self)
self.refreshButton.clicked.connect(self.Refresh)
# THIS ACTION SHOULD PROCEED ARGUMENTS
# TO FUNCION "Refresh"
layout = QHBoxLayout()
layout.addWidget(self.refreshButton)
self.setLayout(layout)
self.show()
def Refresh(self, scene):
mainWinScene = scene
print "On Refresh! - ", mainWinScene.items()
class MainScene(QGraphicsScene):
def __init__(self):
super(MainScene, self).__init__()
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.scene = MainScene()
self.scene.setSceneRect(0,0,200,100)
self.scene.addLine(20,10,150,80)
self.view = QGraphicsView()
self.view.setScene(self.scene)
drawRectAct = QAction('&Add Rectangle', self)
drawRectAct.triggered.connect(self.drawRect)
shapeInspectorAct = QAction('&Show Inspector', self)
shapeInspectorAct.triggered.connect(self.showInspector)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&Shapes')
fileMenu.addAction(drawRectAct)
fileMenu.addAction(shapeInspectorAct)
self.setCentralWidget(self.view)
def drawRect(self):
self.scene.addRect(50,50,20,30)
def showInspector(self):
self.I = Widget(self.scene)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
How to proceed "scene" argument with action - to "Refresh" function?
You can pass a scene in Widget's constructor:
class Widget(QWidget):
def __init__(self, scene):
...
self.scene = scene
...
def Refresh(self):
print "On Refresh! - ", self.scene.items()
class MainWindow(QMainWindow):
...
def showInspector(self):
self.I = Widget(self.scene)
...

Categories