Emit a signal when any button is pressed - python

In a pyqt5 app I want to untoggle a button when any other button is clicked.
Is it possible to have a signal emitted when any button inside a layout or a widget is clicked?
I can connect each button click to an untoggle function but that would be hard to maintain if the buttons ever change. Is there a clean way to achieve the same functionality without having to connect the buttons one by one?

How to point out a possible solution is to inherit from the button and implement the indicated logic, but in the case of Qt it can be taken advantage of the fact that the clicked signal is associated with the QEvent.MouseButtonRelease event when a button is pressed. Considering the above, notify can be used to implement the logic:
import sys
from PyQt5.QtCore import pyqtSignal, QCoreApplication, QEvent
from PyQt5.QtWidgets import (
QAbstractButton,
QApplication,
QPushButton,
QVBoxLayout,
QWidget,
)
class Application(QApplication):
buttonClicked = pyqtSignal(QAbstractButton)
def notify(self, receiver, event):
if (
isinstance(receiver, QAbstractButton)
and receiver.isEnabled()
and event.type() == QEvent.MouseButtonRelease
):
self.buttonClicked.emit(receiver)
return super().notify(receiver, event)
def main(argv):
app = Application(argv)
view = QWidget()
lay = QVBoxLayout(view)
special_button = QPushButton("Special")
lay.addWidget(special_button)
for i in range(10):
button = QPushButton(f"button{i}")
lay.addWidget(button)
view.show()
def handle_clicked(button):
if button is not special_button:
print(f"{button} clicked")
QCoreApplication.instance().buttonClicked.connect(handle_clicked)
sys.exit(app.exec_())
if __name__ == "__main__":
main(sys.argv)

What you want is added functionality for an existing type. This suggests that you want to write an extension of the Button class. Write a class that inherits from Button. Write your own function to handle button clicks: this function will call your toggle function and then call the parent's button click handler.

Related

PyQt - QPushButton loop

I'm new to Python and PyQt so sorry if I can't describe my problems clearly. I want to do a cinema seat selector UI and this is the code I have made below:
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import (QWidget, QApplication)
class Ui2(QWidget):
def __init__(self):
super(Ui2, self).__init__()
uic.loadUi('seat.ui', self)
self.A1.setStyleSheet("background-color: red")
self.B1.clicked.connect(self.greenButton)
self.show()
def greenButton(self):
self.B1.setStyleSheet("background-color: green")
self.B1.clicked.connect(self.whiteButton)
def whiteButton(self):
self.B1.setStyleSheet("background-color: white")
self.B1.clicked.connect(self.greenButton)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Ui2()
sys.exit(app.exec_())
The problem is, if I click the button B1 multiple times the program will freeze - I read it somewhere that this is caused by full memory.
Also this is only for the button B1, what should I do if I want to implement the same functions for all buttons?
Thanks a lot!
You shouldn't call self.B1.clicked.connect so many times. Each time you call that function, it registers another event handler. When the button is clicked, all the event handlers that have ever been registered get called. So as you keep clicking, each click causes more and more things to happen. Eventually it fails.
In general you want to try to have one handler for each event. Here is one simple way to do that:
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import (QWidget, QApplication)
class Ui2(QWidget):
def __init__(self):
super(Ui2, self).__init__()
uic.loadUi('seat.ui', self)
self.b1_color = "green"
self.A1.setStyleSheet("background-color: red")
self.B1.clicked.connect(self.onButton)
self.show()
def onButton(self):
if self.b1_color == "green":
self.b1_color = "white"
else:
self.b1_color = "green"
self.B1.setStyleSheet("background-color: " + self.b1_color)
Tested with PySide and Qt4.8, but it should still work in your environment (I hope).

pyqt5 - closing/terminating application

I'm working though the pyqt5 tutorial found here Zetcode, PyQt5
As an exercise for myself I'm trying to expand on an example so that I am presented with the same dialog message box regardless of method used to close the app:
clicking the 'X' button in the title bar (works as intended)
clicking the 'Close' button (produces attribute error)
pressing the 'escape' key (works but not sure how/why)
The dialog message box is implemented in the closeEvent method, full script provided at the end.
I'm having two issues:
1. When clicking 'Close' button, instead of just quitting, I want to call closeEvent method including message box dialog.
I have replaced a line of the example code for the 'Close' push button:
btn.clicked.connect(QCoreApplication.instance().quit)
And instead am trying to call the closeEvent method which already implements the dialog I want:
btn.clicked.connect(self.closeEvent)
However when i run the script and click the 'Close' button and select the resulting 'Close' option in the dialog i get the following:
Traceback (most recent call last):
File "5-terminator.py", line 41, in closeEvent
event.accept()
AttributeError: 'bool' object has no attribute 'accept'
Aborted
Can anyone advise what I'm doing wrong and what needs to be done here?
2. When hitting the escape key somehow the message box dialog is presented and works just fine.
Ok, it's great that it works, but I'd like to know how and why the message box functionality defined in CloseEvent method is called within the keyPressEvent method.
Full script follows:
import sys
from PyQt5.QtWidgets import (
QApplication, QWidget, QToolTip, QPushButton, QMessageBox)
from PyQt5.QtCore import QCoreApplication, Qt
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
btn = QPushButton("Close", self)
btn.setToolTip("Close Application")
# btn.clicked.connect(QCoreApplication.instance().quit)
# instead of above button signal, try to call closeEvent method below
btn.clicked.connect(self.closeEvent)
btn.resize(btn.sizeHint())
btn.move(410, 118)
self.setGeometry(30, 450, 500, 150)
self.setWindowTitle("Terminator")
self.show()
def closeEvent(self, event):
"""Generate 'question' dialog on clicking 'X' button in title bar.
Reimplement the closeEvent() event handler to include a 'Question'
dialog with options on how to proceed - Save, Close, Cancel buttons
"""
reply = QMessageBox.question(
self, "Message",
"Are you sure you want to quit? Any unsaved work will be lost.",
QMessageBox.Save | QMessageBox.Close | QMessageBox.Cancel,
QMessageBox.Save)
if reply == QMessageBox.Close:
event.accept()
else:
event.ignore()
def keyPressEvent(self, event):
"""Close application from escape key.
results in QMessageBox dialog from closeEvent, good but how/why?
"""
if event.key() == Qt.Key_Escape:
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
sys.exit(app.exec_())
Hope someone can take the time to enlighten me.
Your second question answers the first question.
The reimplemented keyPressEvent method calls close(), which sends a QCloseEvent to the widget. Subsequently, the widget's closeEvent will be called with that event as its argument.
So you just need to connect the button to the widget's close() slot, and everything will work as expected:
btn.clicked.connect(self.close)
Unlike the X button your custom button does not seem to pass an close event just a bool. That's why this exercise should work for the X button but not a normal button. In any case, for your first question you might use destroy() and pass instead (of accept and ignore) just like this:
import sys
from PyQt5.QtWidgets import (
QApplication, QWidget, QToolTip, QPushButton, QMessageBox)
from PyQt5.QtCore import QCoreApplication, Qt
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
btn = QPushButton("Close", self)
btn.setToolTip("Close Application")
# btn.clicked.connect(QCoreApplication.instance().quit)
# instead of above button signal, try to call closeEvent method below
btn.clicked.connect(self.closeEvent)
btn.resize(btn.sizeHint())
btn.move(410, 118)
self.setGeometry(30, 450, 500, 150)
self.setWindowTitle("Terminator")
self.show()
def closeEvent(self, event):
"""Generate 'question' dialog on clicking 'X' button in title bar.
Reimplement the closeEvent() event handler to include a 'Question'
dialog with options on how to proceed - Save, Close, Cancel buttons
"""
reply = QMessageBox.question(
self, "Message",
"Are you sure you want to quit? Any unsaved work will be lost.",
QMessageBox.Save | QMessageBox.Close | QMessageBox.Cancel,
QMessageBox.Save)
if reply == QMessageBox.Close:
app.quit()
else:
pass
def keyPressEvent(self, event):
"""Close application from escape key.
results in QMessageBox dialog from closeEvent, good but how/why?
"""
if event.key() == Qt.Key_Escape:
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
sys.exit(app.exec_())
For your second question Qt has default behaviors depending on the Widget (Dialogs might have another, try pressing the Esc key when your Message Dialog is open just to see). When you do need to override the Esc behavior you might try this:
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
print("esc")
As you'll eventually see in ZetCode.
closeEvent()
In above right tick mark code please check the closeEvent it is same other wise it's waste of time to research.

EventFilter for Drop-Events within QTableView

I try to create a QTableView that can handle drop events. For reasons of application architecture, I want that to be done by an eventFilter of mine (that handles some QAction-triggers for clipboard interaction as well). But the drop-event does not seem to get through to the eventFilter.
I do not care where the data are dropped within the view. That is one of the reasons I want it to be handled by that eventFilter and not by the model. Furthermore, I do not want the model to pop up a dialog ("Are you sure to drop so many elements?"), because user interaction should be done by gui elements.
btw: It did actually work in Qt4/PySide.
I set up an example code snippet to illustrate the problem. The interesting thing about this is that there can appear QDrop Events, but only in the headers of the item view.
#!/usr/bin/env python2.7
# coding: utf-8
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QTableView,
QWidget,
)
from PyQt5.QtCore import (
Qt,
QStringListModel,
QEvent
)
app = QApplication([])
window = QMainWindow()
# Table View with Drop-Options
view = QTableView(window)
view.setDropIndicatorShown(True)
view.setDragEnabled(True)
view.setAcceptDrops(True)
view.setDragDropMode(QTableView.DragDrop)
view.setDefaultDropAction(Qt.LinkAction)
view.setDropIndicatorShown(True)
window.setCentralWidget(view)
# Simple Event Filter for TableView
class Filter(QWidget):
def eventFilter(self, widget, event):
print widget, event, event.type()
if event.type() in (QEvent.DragEnter, QEvent.DragMove, QEvent.Drop):
print "Drag'n'Drop-Event"
if event.type() != QEvent.Drop:
print "\tbut no DropEvent"
event.acceptProposedAction()
else:
print "\tan actual DropEvent"
return True
return False
filter = Filter(window)
view.installEventFilter(filter)
class MyModel(QStringListModel):
# Model with activated DragDrop functionality
# vor view
def supportedDragActions(self):
print "asks supported drop actions"
return Qt.LinkAction | Qt.CopyAction
def canDropMimeData(self, *args):
print "canDropMimeData"
return True
def dropMimeData(self, *args):
print "dropMimeData"
return True
model = MyModel("Entry_A Entry_B Entry_C".split())
view.setModel(model)
window.show()
window.raise_()
app.exec_()
Final question: What widget handles the QDropEvents within the QTableView, or what widget should I install the eventFilter on?
view.viewport() gets all the remaining events. So simply adding
view.viewport().installEventFilter(filter)
will do.

How does PySide/PyQt QMainWindow close a QDockWidget?

I need to know how the QDockWidget is normally closed. I have a serial port/thread attached to a QDockWidget, and I need to make sure the thread and serial port close properly.
class SerialDock(QDockWidget):
...
def close(self):
print("Close")
self.serialport.close()
self.thread.close()
def closeEvent(self, event):
print("closeEvent")
self.serialport.close()
self.thread.close()
The close and closeEvents are not called when I click the QMainWindow X button. Do I have to call the close method from the QMainWindow close? The only way I know to solve this is to use the QApplication.aboutToQuit signal, and I really don't want to have to remember to set that for one specific widget. How does the QDockWidget get destroyed or closed?
You can use the destroyed signal in the QDockWidget:
import PyQt4.QtGui as ui
import PyQt4.QtCore as core
app = ui.QApplication([])
mw = ui.QMainWindow()
mw.setCentralWidget(ui.QTextEdit())
dw = ui.QDockWidget("Test",mw)
dw.setWidget(ui.QLabel("Content"))
mw.addDockWidget(core.Qt.RightDockWidgetArea, dw)
def onDestroy(w):
print("Do stuff here")
print(w)
dw.destroyed.connect(onDestroy)
mw.show()
app.exec_()

change text of lineEdit when a radio button is selected in pyqt

I have two radioButtons in the form made using qt designer, i am now programming in pyqt. i wish to change the text of lineEdit to "radio 1" when radioButton 1 is selected and "radio 2" when the radioButton 2 is selected. how can I achieve this?
Here's a simple example. Each QRadioButton is connected to it's own function. You could connect them both to the same function and manage what happens through that, but I thought best to demonstrate how the signals and slots work.
For more info, take a look at the PyQt4 documentation for new style signals and slots. If connecting multiple signals to the same slot it's sometimes useful to use the .sender() method of a QObject, although in the case of QRadioButton it's probably easier to just check the .isChecked() method of your desired buttons.
import sys
from PyQt4.QtGui import QApplication, QWidget, QVBoxLayout, \
QLineEdit, QRadioButton
class Widget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.widget_layout = QVBoxLayout()
self.radio1 = QRadioButton('Radio 1')
self.radio2 = QRadioButton('Radio 2')
self.line_edit = QLineEdit()
self.radio1.toggled.connect(self.radio1_clicked)
self.radio2.toggled.connect(self.radio2_clicked)
self.widget_layout.addWidget(self.radio1)
self.widget_layout.addWidget(self.radio2)
self.widget_layout.addWidget(self.line_edit)
self.setLayout(self.widget_layout)
def radio1_clicked(self, enabled):
if enabled:
self.line_edit.setText('Radio 1')
def radio2_clicked(self, enabled):
if enabled:
self.line_edit.setText('Radio 2')
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())

Categories