PyQt5: QPushButton double click? - python

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.

Related

Python QT5 can't show message box from another class

When I try to show a message box from another class, it doesn't react anything.
On the other hand, I could succeed to show the box with the internal function.
I can print a message in console through another method of the class.
It seems just not working without any error.. I'm reading some articles but doesn't help.. Could you let me know which part I missed?
[Main.py]
class WinViewer(QWidget):
def __init__(self):
super().__init__()
def initUI(self):
self.setWindowTitle('Autolin')
self.move(300, 300)
self.resize(400, 200)
crw = Crawler.Crawler()
runBtn = QPushButton('Run', self)
runBtn.move(150, 50)
runBtn.clicked.connect(crw.putWebData)
tstBtn = QPushButton('Tst', self)
tstBtn.move(150, 100)
tstBtn.clicked.connect(self.tstmsg)
crw.testFunc()
self.show()
def tstmsg(self):
QMessageBox.about(self, "Tst", "Tst works.")
if __name__ == '__main__':
q_app = QApplication(sys.argv)
gui = WinViewer()
gui.initUI()
sys.exit(q_app.exec_())
[Sub.py]
class Crawler():
def putWebData(self):
QMessageBox.about(self, "Run", "Run works.")
def testFunc(self):
print('Hello')
try using this
Main.py
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
from Sub import Crawler
class WinViewer(QWidget):
def __init__(self, *args):
super(WinViewer, self).__init__(*args)
def initUI(self):
self.setWindowTitle('Autolin')
self.move(300, 300)
self.resize(400, 200)
crw = Crawler(self)
runBtn = QPushButton('Run', self)
runBtn.move(150, 50)
runBtn.clicked.connect(crw.putWebData)
tstBtn = QPushButton('Tst', self)
tstBtn.move(150, 100)
tstBtn.clicked.connect(self.tstmsg)
crw.testFunc()
self.show()
def tstmsg(self):
QMessageBox.about(self, "Tst", "Tst works.")
if __name__ == '__main__':
q_app = QApplication(sys.argv)
gui = WinViewer()
gui.initUI()
sys.exit(q_app.exec_())
Sub.py
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Crawler(QMessageBox):
def __init__(self, *args):
super(Crawler, self).__init__(*args)
def putWebData(self):
self.about(self, "Run", "Run works.")
def testFunc(self):
print('Hello')
[EDIT]
I went to do something which gave me no time to add the explanation.
What you missed is the super().__init__() is a must when you are inheriting a class from Qt Classes and it works like this super({CLASS_NAME}, self) self represents the inherited class and after it .__init__(*args, **kwargs) it should have both args and kwargs so you can get the full functionality of the inhereted class. *args, **kwargs should be assigned as attributes inside def __init__(self, *args, **kwargs)

PyQt5 passing argument between two classes: lambda vs partial

I am trying to pass an argument between two PyQt5 classes. I used three methods:
Using lambda functions.
Wrapper function (similar to lambda function).
partial from functools module.
In the example below, I have two windows:
MainWindow has QLineEdit mw_line_edit and a QPushButton mw_open_new_dialog_button.
Dialog: has a QLineEdit line_edit and aQPushButton push_button.
When I click the button push_button, I want it to insert the content of line_edit to mw_line_edit.
Here is a minimal example:
import sys
from functools import partial
from PyQt5 import QtWidgets, QtGui, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.central_widget = QtWidgets.QWidget(self)
self.setCentralWidget(self.central_widget)
self.mw_open_new_dialog_button = QtWidgets.QPushButton('Open New dialog', self)
self.mw_line_edit = QtWidgets.QLineEdit(self)
self.hlayout = QtWidgets.QHBoxLayout(self)
self.hlayout.addWidget(self.mw_open_new_dialog_button)
self.hlayout.addWidget(self.mw_line_edit)
self.central_widget.setLayout(self.hlayout)
self.mw_open_new_dialog_button.clicked.connect(self.open_new_dialog)
def open_new_dialog(self):
self.dlg = Dialog()
#self.dlg.clicked.connect(partial(self.write_something, self.dlg.line_edit.text())) # <<<<<<< This does not work
self.dlg.clicked.connect(lambda: self.write_something(self.dlg.line_edit.text())) # this works
#self.dlg.clicked.connect(self.wrapper(self.dlg.line_edit.text()))# <<<<<<<<<<This does not work
self.dlg.exec()
#QtCore.pyqtSlot()
def write_something(self, text):
self.mw_line_edit.setText(text)
def wrapper(self, text):
return lambda: self.write_something(text)
class Dialog(QtWidgets.QDialog):
clicked = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(QtWidgets.QDialog, self).__init__(parent)
self.hlayout = QtWidgets.QHBoxLayout(self)
self.line_edit = QtWidgets.QLineEdit(self)
self.push_button = QtWidgets.QPushButton('Click me', self)
self.hlayout.addWidget(self.line_edit)
self.hlayout.addWidget(self.push_button)
self.label = QtWidgets.QLabel('I am a Qlabel', self)
self.hlayout.addWidget(self.label)
self.setLayout(self.hlayout)
self.push_button.clicked.connect(self.clicked)
def write_something(self, text):
print(text)
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())
As you can see in the commented lines, only the following method works:
self.dlg.clicked.connect(lambda: self.write_something(self.dlg.line_edit.text()))
Why the other two do not work, i.e:
self.dlg.clicked.connect(partial(self.write_something, self.dlg.line_edit.text())) # <<<<<<< This does not work
self.dlg.clicked.connect(self.wrapper(self.dlg.line_edit.text()))# <<<<<<<<<<This does not work
Thanks
1) functools.partial()
What arguments are you passing to partial? You are passing the method write_something and the text of self.dlg.line_edit at the time the connection is made.
And what is the value of that text? it is an empty string, this explains the failure.
Is there any solution for this case? Yes, instead of passing the text, pass the QLineEdit, and in the method write_something get the text and set it in the other QLineEdit:
def open_new_dialog(self):
self.dlg = Dialog()
self.dlg.clicked.connect(partial(self.write_something, self.dlg.line_edit))
self.dlg.exec()
def write_something(self, le):
self.mw_line_edit.setText(le.text())
2) wrapper
It is the same problem, you are passing the empty text that you have at the moment of the connection
Is there any solution? Yes, the same solution as the previous one.
def open_new_dialog(self):
self.dlg = Dialog()
self.dlg.clicked.connect(self.wrapper(self.dlg.line_edit))
self.dlg.exec()
def write_something(self, text):
self.mw_line_edit.setText(text)
def wrapper(self, line):
return lambda: self.write_something(line.text())
Will there be a clean solution? Yes, create a signal that transports the text when you click.
from PyQt5 import QtWidgets, QtGui, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
self.mw_open_new_dialog_button = QtWidgets.QPushButton('Open New dialog')
self.mw_line_edit = QtWidgets.QLineEdit()
hlayout = QtWidgets.QHBoxLayout(central_widget)
hlayout.addWidget(self.mw_open_new_dialog_button)
hlayout.addWidget(self.mw_line_edit)
self.mw_open_new_dialog_button.clicked.connect(self.open_new_dialog)
#QtCore.pyqtSlot()
def open_new_dialog(self):
self.dlg = Dialog()
self.dlg.textSignal.connect(self.mw_line_edit.setText)
self.dlg.exec()
class Dialog(QtWidgets.QDialog):
textSignal = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(QtWidgets.QDialog, self).__init__(parent)
hlayout = QtWidgets.QHBoxLayout(self)
self.line_edit = QtWidgets.QLineEdit()
self.push_button = QtWidgets.QPushButton('Click me')
hlayout.addWidget(self.line_edit)
hlayout.addWidget(self.push_button)
self.label = QtWidgets.QLabel('I am a Qlabel')
hlayout.addWidget(self.label)
self.push_button.clicked.connect(self.sendText)
#QtCore.pyqtSlot()
def sendText(self):
self.textSignal.emit(self.line_edit.text())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())

Passing parameters from programatically-built buttons

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))

Make a modal QDialog minimize when QMainWindow minimized (using PyQt 5)

Example code I am using:
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.open_about = False
self.openAction = QtWidgets.QAction('About', self)
self.openAction.triggered.connect(self.aboutDialog)
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
fileMenu.addAction(self.openAction)
self.calendar = QtWidgets.QCalendarWidget(self)
self.setCentralWidget(self.calendar)
def about_state_upd(self, value):
self.open_about = value
def aboutDialog(self):
self._about = AboutDialog(self)
self._about.exec_()
def hideEvent(self, hideEvent):
if self.open_about == True:
self._about.setVisible(False)
def showEvent(self, showEvent):
if self.open_about == True:
if self._about.isHidden() == True:
self._about.setModal(True)
self._about.setVisible(True)
class AboutDialog(QtWidgets.QDialog):
def __init__(self, parent):
super(AboutDialog, self).__init__(parent)
self.setMinimumSize(400, 350)
self.parent().about_state_upd(True)
def closeEvent(self, closeEvent):
self.parent().about_state_upd(False)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app_window = MainWindow()
app_window.showMaximized()
sys.exit(app.exec_())
This code basically works, but seems very complicated. Is there a simpler / cleaner way to make it so that when the modal QDialog is open, if the QMainWindow is minimized, the QDialog also gets minimized too (and reverse when QMainWindow is restored)?
Code is running on KDE Neon (Kubuntu-based distro).
May be you can use this: http://korbinin.blogspot.fr/search/label/minimize%20button
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MainForm(QDialog):
def __init__(self, fn=None,parent=None):
super(MainForm, self).__init__(parent,\
flags=Qt.WindowMinimizeButtonHint|Qt.WindowMaximizeButtonHint)
Thanks to the people on the PyQt Mailing list, I managed to get a workaround for KDE. Instead of using exec_(), I am just using show() - then I use setDisabled() on QMainWindow to make dialog act in a modal fashion. Here is a (very quick and basic) example for anyone interested:
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('About', self)
self.openAction.triggered.connect(self.aboutDialog)
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
fileMenu.addAction(self.openAction)
self.calendar = QtWidgets.QCalendarWidget(self)
self.setCentralWidget(self.calendar)
def aboutDialog(self):
self._about = AboutDialog(self)
self.setDisabled (True)
self._about.show()
def enableWidgets(self):
self.setDisabled(False)
class AboutDialog(QtWidgets.QDialog):
def __init__(self, parent):
super(AboutDialog, self).__init__(parent)
self.setMinimumSize(400, 350)
def closeEvent(self, parent):
self.parent().enableWidgets()
def changeEvent(self, event):
if event.type() == QtCore.QEvent.WindowStateChange:
if self.windowState() & QtCore.Qt.WindowMinimized:
self.parent().showMinimized()
else:
self.parent().showMaximized()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app_window = MainWindow()
app_window.showMaximized()
sys.exit(app.exec_())
Link to PyQt Mailing List posts.

PyQt signal slot issue with signal outside creating class

I am trying to create a toolbar that can be modified to change actions on the fly.
However signals are not being sent when I add actions from outside the class that creates the toolbar.
In the example below the new action is never triggered. Any idea on how this can be done?
import sys
from PyQt4 import QtGui
from toolbarmodifier import ToolbarModifier
class FluidToolbar(QtGui.QMainWindow):
def __init__(self):
super(FluidToolbar, self).__init__()
self.initUI()
def initUI(self):
createAction = QtGui.QAction( 'create Action', self)
createAction.triggered.connect(self.createActions)
self.toolbar = self.addToolBar('create Action')
self.toolbar.addAction(createAction)
self.setGeometry(300, 300, 300, 200)
self.show()
def createActions(self):
print(">>createActions()")
toolbarModifier = ToolbarModifier()
toolbarModifier.addAction(self)
def main():
app = QtGui.QApplication(sys.argv)
ex = FluidToolbar()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
toolbarmodifier.py
from PyQt4 import QtGui
from PyQt4.QtGui import QWidget
class ToolbarModifier(QWidget):
def __init__(self):
super(ToolbarModifier, self).__init__()
def newActionTriggered(self):
print(">>newActionTriggered()")
def addAction(self, gui):
triggerAction = QtGui.QAction( 'New action', gui)
triggerAction.triggered.connect(self.newActionTriggered)
gui.toolbar.addAction(triggerAction)
print("<<addAction()")
Not having a link back to parent was the issue. In FluidToobar modify code in createActions method to include self in call:
toolbarModifier = ToolbarModifier(self)
In ToolbarModifier change first few lines to:
class ToolbarModifier(QtCore.QObject):
def __init__(self, parent=None):
super(ToolbarModifier, self).__init__(parent)

Categories