How to detect any mouse click on PySide Gui? - python

I am trying implement a feature such that when a mouse is clicked on the gui, a function is triggered
Below is my mouse click detection, it doesn't work when I click on any part of the gui
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QHBoxLayout(self)
layout.addWidget(QLabel("this is the main frame"))
layout.gui_clicked.connect(self.anotherSlot)
def anotherSlot(self, passed):
print passed
print "now I'm in Main.anotherSlot"
class MyLayout(QHBoxLayout):
gui_clicked = Signal(str)
def __init__(self, parent=None):
super(MyLayout, self).__init__(parent)
def mousePressEvent(self, event):
print "Mouse Clicked"
self.gui_clicked.emit("emit the signal")
a = QApplication([])
m = Main()
m.show()
sys.exit(a.exec_())
This is my goal
Mouseclick.gui_clicked.connect(do_something)
Any advice would be appreciated

Define mousePressEvent inside Main:
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QHBoxLayout(self)
layout.addWidget(QLabel("this is the main frame"))
def mousePressEvent(self, QMouseEvent):
#print mouse position
print QMouseEvent.pos()
a = QApplication([])
m = Main()
m.show()
sys.exit(a.exec_())

This can get complicated depending on your needs. In short, the solution is an eventFilter installed on the application. This will listen the whole application for an event. The problem is "event propagation". If a widget doesn't handle an event, it'll be passed to the parent (and so on). You'll see those events multiple times. In your case, for example QLabel doesn't do anything with a mouse press event, therefore the parent (your main window) gets it.
If you actually filter the event (i.e. you don't want the original widget to respond to the event), you won't get that problem. But, I doubt that this is your intent.
A simple example for just monitoring:
import sys
from PySide import QtGui, QtCore
class MouseDetector(QtCore.QObject):
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
print 'mouse pressed', obj
return super(MouseDetector, self).eventFilter(obj, event)
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
layout = QtGui.QHBoxLayout()
layout.addWidget(QtGui.QLabel('this is a label'))
layout.addWidget(QtGui.QPushButton('Button'))
self.setLayout(layout)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mouseFilter = MouseDetector()
app.installEventFilter(mouseFilter)
main = MainWindow()
main.show()
sys.exit(app.exec_())
You can see that, clicking on the QLabel will give you something like:
mouse pressed <PySide.QtGui.QLabel object at 0x02B92490>
mouse pressed <__main__.MainWindow object at 0x02B92440>
Because, QLabel receives the event and since it doesn't do anything with it, it's ignored and passed to the parent (MainWindow). And it's caught by the filter/monitor again.
Clicking on the QPushButton doesn't have any problem because it uses that event and does not pass to the parent.
PS: Also note that this can cause performance problems since you are inspecting every single event in the application.

Related

How to set an event handler for an item (QTableWidget) being enabled/disabled?

I have a QTableWidget that gets enabled and disabled depending on certain data.
I have a button I want to be enabled when the QTableWidget is enabled, and disabled when the widget is disabled.
Is there any event handler that would let me do this?
Such as (and this does not work):
myTable.changeEvent.connect(lambda: print("test"))
Ideally in the above code, 'test' would be printed every time the table gets enabled or disabled.
The simplest solution is that the moment you deactivate the QTableWidget you also deactivate the button (or use a signal to transmit the information).
Instead, another solution is to use an event filter that allows to emit a signal every time the widget's state changes and then use that information to change the button's state.
import random
import sys
from PyQt5 import QtCore, QtWidgets
class EnableHelper(QtCore.QObject):
enableChanged = QtCore.pyqtSignal(bool)
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, obj, event):
if obj is self.widget and event.type() == QtCore.QEvent.EnabledChange:
self.enableChanged.emit(self.widget.isEnabled())
return super().eventFilter(obj, event)
class Widget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableWidget(5, 4)
self.button = QtWidgets.QPushButton("Hello world")
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.table)
lay.addWidget(self.button)
helper = EnableHelper(self.table)
helper.enableChanged.connect(self.button.setEnabled)
self.test()
def test(self):
self.table.setEnabled(random.choice([True, False]))
QtCore.QTimer.singleShot(1000, self.test)
def main():
app = QtWidgets.QApplication(sys.argv)
win = Widget()
win.show()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
Note: changeEvent is not a signal so you should not use connect as this is a class method. Also it is not good to use it if you only want to detect if the widget changes state from enabled to disabled, or vice versa, since this is also called in other cases.

How to speedup the delay of the pressed signal of QPushButton compared to KeyPress event in Qt/ PyQt?

I have a PyQt-GUI with a function to move an object that is connected both via the pressed-signal to a QPushButton and via a QEvent.KeyPress-event to a key from the keyboard. However, I notice a serious lag in the object movement after clicking the PushButton, which is not the case if I press the specified key on the keyboard.
Can you explain why the execution of the same function is slower if triggered by the PushButton compared to the key-event?
Is there a way to make the execution faster/ the lag slower for the QPushButton?
Here is an abstract of the relevant code:
class GUI(QtWidgets.QMainWindow):
def __init__(self, control: 'Controler'):
super(GUI, self).__init__()
uic.loadUi("ressources/interface.ui", self)
# Defining the QPushButton and connecting the signal with the function
self.button_left = self.findChild(QtWidgets.QPushButton, "button_bbox_left")
self.button_left.pressed.connect(lambda: self.translate_along_x(left=True))
# Handling key_press_event with eventFilter
def eventFilter(self, event_object, event):
if (event.type() == QEvent.KeyPress) and (event.key() == QtCore.Qt.Key_A):
self.translate_along_x(left=True)
# The function that moves the object
def translate_along_x(self, distance=0.05, left=False):
if left:
distance *= -1
move_object(distance)
One difference between the two function calls is that for the QPushButton I have to use a lambda-function due to the parameter. But the lag remains if I set it without the lambda-function.
Update
As #eyllanesc suggested I tried to create a minimal reproducible example. You find it below. There I could not measure a significant difference. So (although it is much simpler) it might be more like PyQt is not executing all clicks if I click the button repeatedly a lot of times.
So is there an option to make the button handle many fast clicks better?
MRE:
import sys, time
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QMainWindow
app = QApplication(sys.argv)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
button = QPushButton("Click me")
self.setCentralWidget(button)
self.start = None
button.pressed.connect(self.button_clicked)
def button_clicked(self):
self.start = time.time()
self.do_sth()
def keyPressEvent(self, event):
self.start = time.time()
self.do_sth()
def do_sth(self):
print("Reacted to signal/ event!")
print("Took %s seconds!" % str(time.time()-self.start))
window = MainWindow()
window.show()
sys.exit(app.exec_())

Extending a Widget Loaded from a UI File

I'd like to be able to load a widget (QMainWindow in this case) from a .ui file, but also be able to extend the class so that I can do things like catch keyboard events.
For example, I have the following that doesn't work but shows what I'm trying to accomplish:
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
self.window = QUiLoader().load("mainwindow.ui", self)
self.setCentralWidget(self.window)
self.window.keyPressEvent = self.key_pressed
self.window.setEnabled(True)
def show(self):
self.window.show()
def key_pressed(self, event):
print(event)
Because I cannot extend the object loaded from QUiLoader, I attempted to hijack the keyPressEvent method in that object, and assign it to my own key_pressed method. This doesn't work, but I'm unsure how else to go about capturing keyboard events.
I know I could create MyMainWindow and set it's base class to QMainWindow, and then override the keyPressEvent method, but then I have to do all the layout in code, and I'd much rather leverage the .ui file. How do you go about doing this?
To listen to events from other widgets you must use an eventFilter. Also the initial window is never displayed so it is better to replace it with a QObject.
from PySide2 import QtCore, QtWidgets, QtUiTools
class MyApp(QtCore.QObject):
def __init__(self):
super(MyApp, self).__init__()
self.window = QtUiTools.QUiLoader().load("mainwindow.ui")
self.window.installEventFilter(self)
def show(self):
self.window.show()
def eventFilter(self, obj, event):
if obj is self.window:
if event.type() == QtCore.QEvent.KeyPress:
print(event.key())
return super(MyApp, self).eventFilter(obj, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MyApp()
w.show()
sys.exit(app.exec_())

PyQt4 signals and slots - QToolButton

In PyQt4 I have a main window which when the settings button is clicked opens the settings dialog
from PyQt4 import QtCore, QtGui
import ui_Design, ui_Settings_Design
class MainDialog(QtGui.QMainWindow, ui_Design.Ui_arbCrunchUI):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.settingsBtn.clicked.connect(lambda: self.showSettings())
def showSettings(self):
dialog = QtGui.QDialog()
dialog.ui = SettingsDialog()
dialog.ui.setupUi(dialog)
dialog.exec_()
The above works and my SettingsDialog is displayed but I cant get the setPageIndex to work
class SettingsDialog(QtGui.QDialog, ui_Settings_Design.Ui_SettingsDialog):
def __init__(self, parent=None):
super(SettingsDialog, self).__init__(parent)
self.setupUi(self)
self.bookSettingsBtn.clicked.connect(self.setPageIndex)
#QtCore.pyqtSlot()
def setPageIndex(self):
print 'selected'
self.settingsStackedWidget.setCurrentIndex(0)
The bookSettingsBtn is a QToolButton
self.bookSettingsBtn = QtGui.QToolButton(self.navigationFrame)
And the settingsStackedWidget is a QStackedWidget
self.settingsStackedWidget = QtGui.QStackedWidget(SettingsDialog)
At this point I am pretty confused on signals and slots and nothing I have read clears this up - if anyone can point out what I am doing wrong above and also potentially direct me to a good (beginners) guide / tutorial on signals and slots it would be greatly appreciated
I would also like to know how to make setPageIndex work as follows:
def setPageIndex(self, selection):
self.settingsStackedWidget.setCurrentIndex(selection)
I'm not sure why you're doing the following, but that's the issue:
def showSettings(self):
dialog = QtGui.QDialog()
dialog.ui = SettingsDialog()
dialog.ui.setupUi(dialog)
dialog.exec_()
SettingsDialog itself is a proper QDialog. You don't need to instantiate another QDialog.
Right now, you're creating an empty QDialog and then populate it with the same ui as SettingsDialog (i.e. setupUi(dialog)), then you show this dialog. But... The signal connection is for SettingsDialog, and the dialog you're showing doesn't have that.
Basically, you don't need that extra QDialog at all. The following should be enough:
def showSettings(self):
dialog = SettingsDialog()
dialog.exec_()
Ok. So here is an example how you pass an argument to a slot
from functools import partial
# here you have a button bookSettingsBtn:
self.bookSettingsBtn = QtGui.QPushButton("settings")
self.bookSettingsBtn.clicked.connect(partial(self.setPageIndex, self.bookSettingsBtn.text()))
#pyqtSlot(str) # this means the function expects 1 string parameter (str, str) 2 string parameters etc.
def setPageIndex(self, selection):
print "you pressed button " + selection
In your case the argument would be an int. Of course you have to get the value from somewhere
and then put it in the partial part as the argument (here I just used the text of the button),
but you can use int, bool etc. Just watch the slot signature.
Here is a tutorial that helped me:
http://zetcode.com/gui/pyqt4/
I hope this helps.
Hey here I have a fully running example (just copy paste it in a python file and run it):
Maybe this helps you. It's a small example but here you see how it works.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from functools import partial
class MyForm(QMainWindow):
def __init__(self, parent=None):
super(MyForm, self).__init__(parent)
button1 = QPushButton('Button 1')
button2 = QPushButton('Button 2')
button1.clicked.connect(partial(self.on_button, button1.text()))
button2.clicked.connect(partial(self.on_button, button1.text()))
layout = QHBoxLayout()
layout.addWidget(button1)
layout.addWidget(button2)
main_frame = QWidget()
main_frame.setLayout(layout)
self.setCentralWidget(main_frame)
#pyqtSlot(str)
def on_button(self, n):
print "Text of button is: " + str(n)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
form = MyForm()
form.show()
app.exec_()
So I dont really understand why but changing the way the settingsDialog is called from the MainWindow has fixed my problem. I guess the parent window needed to be specified??:
class MainDialog(QtGui.QMainWindow, ui_Design.Ui_arbCrunchUI):
....
def showSettings(self):
self.settingsDialog = QtGui.QDialog(self)
self.settingsDialog.ui = SettingsDialog(self)
self.settingsDialog.ui.show()
class SettingsDialog(QtGui.QDialog, ui_Settings_Design.Ui_SettingsDialog):
def __init__(self, parent=None):
super(SettingsDialog, self).__init__(parent)
self.setupUi(self)
self.bookSettingsBtn.clicked.connect(partial(self.setPageIndex, 1))
#QtCore.pyqtSlot(int)
def setPageIndex(self, selection):
self.settingsStackedWidget.setCurrentIndex(selection)

pyqt4 mousePressEvent not called (no widget?)

I have just wrote a little example and i can't manage to make it run.
from PyQt4 import QtGui, QtCore
import sys
class Drawer(QtGui.QWidget):
def __init__(self, parent=None):
super(Drawer, self).__init__(parent)
self.setStyleSheet("QWidget { background-color: %s }" % QtGui.QColor(99, 0, 0).name())
def mousePressEvent(self, event):
print 'mouse pressed'
self.update();
class MyApp(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.drawer = Drawer(self)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
myapp = MyApp()
myapp.show()
sys.exit(app.exec_())
widget is not shown (no color, the window is gray) and if i press the mouse no print..
where is my error?
SOLVED: as qiao just point me in a comment, my error is the way to add a widget in the qt4 kind of scenegraph. i thought i had to call the parent in init and that's all. This is not enogh, i have to add a QLayout and add the childs in it (this is quite obvious: the method addWidget is written only in QLayout and not in QWidget and having a scenegraph system without the possibility of adding new child is quite weird)
You have to set the central widget of the main window to be the drawer. Otherwise the drawer will not attached to the main window.
class MyApp(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.drawer = Drawer(self)
self.setCentralWidget(self.drawer)
After the above fix, you will see mouse press event working properly.
As for the color, setting the stylesheet of QMainWindow is fine, so is setting Drawer to be another widget(like QLineEdit). I don't know what's the matter here.

Categories