I am using PyQt and I want to add a right click to a widget, but I can't find any code on this subject online.
How can you do it ?
You just have to override the methods that take care of it.
In this case you will override the mousePressEvent, have a look on this and see if it makes sense and works for what you need.
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QWidget
class MyWidget(QWidget):
def __init__(self):
super(MyWidget, self).__init__()
def mousePressEvent(self, QMouseEvent):
if QMouseEvent.button() == Qt.LeftButton:
print("Left Button Clicked")
elif QMouseEvent.button() == Qt.RightButton:
#do what you want here
print("Right Button Clicked")
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MyWidget()
mw.show()
sys.exit(app.exec_())
Another good way to do that would be installing a event filter in your object and overriding its eventFilter. Inside that method you would make what you want. Remember you can always make use of pyqtSignal for good practices and call another object to make the job, not overloading the method with a lot of logic.
Here is another small example:
import sys
from PyQt5.QtCore import QEvent
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QWidget
class MyWidget(QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.installEventFilter(self)
def eventFilter(self, QObject, event):
if event.type() == QEvent.MouseButtonPress:
if event.button() == Qt.RightButton:
print("Right button clicked")
return False
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MyWidget()
mw.show()
sys.exit(app.exec_())
Note: Remember that this last example will receive ALL KIND OF EVENTS, so you will have to be careful and make sure it's the one you want and not runtime breaking your app calling methods of your event that doesn't exist because it's not of that kind. For example if you call event.button() without making sure before that it is a QEvent.MouseButtonPress your app would break of course.
There are other ways to do that, these are the most known ones.
I have come up with a pretty simple way of doing this and works perfectly. In the ControlMainWindow class add the following to initialise the Context menu policy as CustomeContextMenu where listWidget_extractedmeters will be the name of your QListWidget:
self.listWidget_extractedmeters.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.listWidget_extractedmeters.connect(self.listWidget_extractedmeters,QtCore.SIGNAL("customContextMenuRequested(QPoint)" ), self.listItemRightClicked)
Then in the ControlMainwindow class the following functions allow you to add context menu items and to call a funtion that performs some functionality:
def listItemRightClicked(self, QPos):
self.listMenu= QtGui.QMenu()
menu_item = self.listMenu.addAction("Remove Item")
self.connect(menu_item, QtCore.SIGNAL("triggered()"), self.menuItemClicked)
parentPosition = self.listWidget_extractedmeters.mapToGlobal(QtCore.QPoint(0, 0))
self.listMenu.move(parentPosition + QPos)
self.listMenu.show()
def menuItemClicked(self):
currentItemName=str(self.listWidget_extractedmeters.currentItem().text() )
print(currentItemName)
Related
I have a program programming with Pyqt5 in which I would like to register some keys simoultaneously;for example, up+right to go to the upper diagonal.
The problem is that with the pressEvent only accept the first key.
Also I use QPygletWidget, but I can not register the push_handlers event from pyglet to PyQt5.
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
def keyPressEvent(self, e, autorep=False):
# print(e.key)
# self.widget.key_pressed = e.key()
print(e.key())
# self.widget.key_pressed = None
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_()) ```
Unfortunately QKeySequence doesn't work for sequences that don't contain CTRL.
You can use keyPressEvent and keyReleaseEvent to keep track what keys is currently pressed and not released yet.
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self._keys = {Qt.Key_Up: False, Qt.Key_Right: False}
def keyPressEvent(self, event):
if event.key() in [Qt.Key_Up, Qt.Key_Right]:
self._keys[event.key()] = True
if self._keys[Qt.Key_Up] and self._keys[Qt.Key_Right]:
self.onUpRight()
def keyReleaseEvent(self, event):
if event.key() in [Qt.Key_Up, Qt.Key_Right]:
self._keys[event.key()] = False
def onUpRight(self):
print("onUpRight")
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
Be aware that if hold a key longer than some threshold (a second I guess) it sends sequences of keyPressEvent keyReleaseEvent as if you repeatedly pressed a button, so onUpRight will be called multiple times.
I am building a GUI in python with PyQt5 and I need that when I right-click on an option in my context menu, it displays another context menu as in the image...
(I am aware that I can nest a menu in one of the options, but that is not what I am looking for)
The context menu does not distinguish between right-click and left-click.
There are two problems that I see to achive this...
I do not know how to prevent the right-click from triggering the PyQt5.QtWidgets.QAction.
The second problem is that normally one of the steps involved in creating a context menu is to use the .installEventFilter() method on a widget, but in this case, you should use it on an object of type PyQt5.QtWidgets.QAction
I know that what I am asking is something complex to achieve, but I would greatly appreciate it if you could give me some information to help me achieve it.
Here I leave you the code of a GUI with a widget with a contextual menu installed on it...
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu
import sys
from PyQt5.QtCore import QEvent
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.createWidgets()
def createWidgets(self):
self.my_button = QtWidgets.QPushButton(self)
self.my_button.setText("My Widget")
self.my_button.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QEvent.ContextMenu and source is self.my_button:
menu = QMenu()
action1 = menu.addAction("Option 1")
action2 = menu.addAction("Option 2")
action3 = menu.addAction("Option 3")
selected_action = menu.exec_(event.globalPos())
if selected_action == action1:
print("You have selected the first option")
if selected_action == action2:
print("You have selected the second option")
if selected_action == action3:
print("You have selected the third option")
return super().eventFilter(source, event)
def showWindow():
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
showWindow()
It is not enough to install the event filter on the button, and you certainly cannot install it on the QAction, which will never trigger mouse events since it does not inherit from QWidget. You have to install the filter on the menu itself.
In order to allow correct tracking of menus (and properly react to their events), it's also better to keep more static references to the menus too.
Obviuosly, you could create a subclass of QMenu, which will probably make things easier.
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.createWidgets()
def createWidgets(self):
self.my_button = QtWidgets.QPushButton(self)
self.my_button.setText("My Widget")
self.buttonMenu = QMenu(self.my_button)
self.buttonMenu.addAction("Option 1")
self.buttonMenu.addAction("Option 2")
self.buttonMenu.addAction("Option 3")
self.subMenu = QMenu(self.buttonMenu)
self.subMenu.addAction("Sub Option 1")
self.subMenu.addAction("Sub Option 2")
self.subMenu.addAction("Sub Option 3")
self.my_button.installEventFilter(self)
self.buttonMenu.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QEvent.ContextMenu:
if source == self.my_button:
self.buttonMenu.exec_(event.globalPos())
return True
elif source == self.buttonMenu:
self.subMenu.exec_(event.globalPos())
return True
return super().eventFilter(source, event)
I am creating a launcher, in the style of Albert, Alfred or uLauncher. My application runs in the background and shows up when a hotkey is pressed. I use pynput to listen to hotkeys. I cannot use PyQt5 hotkey's feature (can't I?) because I need to listen to keyboard events in the system scope, not only the application's scope.
When the shortcut is pressed, it calls the show() method of my widget. The only issue is that I can't get the focus back on my window, despite the use of raise_, setFocus and activateWindow.
I found a (ugly) workaround that consists in openning a QMessageBox (+ tweaking its appearance to make it invisible, but I didn't put that in the example code) and closing it immediately after.
When I was working on Linux, that workaround was doing the job, and I was ready to forget how ugly it is for it does the job. But I switched to Windows (on which my app must run too), and now this cheeky trick seems to cause freeze then crash of my application. Karma? For sure.
Any ways, my application is useless if it cannot catch focus, so I'm asking two questions, and I'd be happy with only one being solved. :)
Do you know why showing the QMessageBox causes a crash?
Do you know any other way to get the focus back on my application?
Here is an example code to play with.
Thank you very much :)
EDIT: I just found out that even with deactivating the QMessageBox workaround, the application eventually crashes (after 5, 20, 30 calls of the hotkey). So the issue might as well be in the way I bind my shortcut to the GUI, I fear a thread issue, but this is way beyond my knowledge :/
import sys
from PyQt5.QtWidgets import QLineEdit, QApplication, QMessageBox
from PyQt5.QtCore import QSize, Qt, QEvent
from pynput import keyboard
class Launcher(QLineEdit):
def __init__(self):
super().__init__()
self.setFixedSize(QSize(600, 50))
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setPlaceholderText('Search...')
self.installEventFilter(self)
self.set_shortcut('<ctrl>+`')
def set_shortcut(self, shortcut):
def for_canonical(f):
return lambda k: f(listener.canonical(k))
hotkey = keyboard.HotKey(
keyboard.HotKey.parse(shortcut),
self.wake_up)
listener = keyboard.Listener(
on_press=for_canonical(hotkey.press),
on_release=for_canonical(hotkey.release))
listener.start()
def wake_up(self):
print('Waking up')
self.show()
self.cheeky_focus_stealer()
def cheeky_focus_stealer(self):
self.setFocus()
self.raise_()
self.activateWindow()
# Working of linux, but causes freeze/crash on Windows 10
message_box = QMessageBox(self)
message_box.show()
message_box.hide()
def eventFilter(self, obj, event):
if obj is self and event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Escape:
self.hide()
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
window = Launcher()
window.show()
app.exec_()
if __name__ == "__main__":
main()
I found my error, so I'm posting an updated piece of code here for it could be helpful to anyone trying to bind a global hotkey to a function that affects a GUI, aka two different thread communicating.
My mistake was indeed to bind the hotkey triggered action straight to my show() method, which implies that the pynput listenner thread will attempt to communnicate with the QApplication.
The trick is to use a pyqtSignal() and to ask it to trigger the show() method. The signal itself being trigger by the hotkey.
After doing that in a clean way, my cheeky_focus_stealer works again, because it is ran from the GUI thread.
import sys
from PyQt5.QtWidgets import QLineEdit, QApplication, QMessageBox
from PyQt5.QtCore import QSize, Qt, QEvent, QObject, pyqtSignal
from pynput import keyboard
class Forwarder(QObject):
signal = pyqtSignal()
class Launcher(QLineEdit):
def __init__(self):
super().__init__()
self.setFixedSize(QSize(600, 50))
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setPlaceholderText('Search...')
self.installEventFilter(self)
self.set_shortcut('<ctrl>+`')
def set_shortcut(self, shortcut):
# The forwarder must be parented to the Launcher
forwarder = Forwarder(parent=self)
forwarder.signal.connect(self.wake_up)
def for_canonical(f):
return lambda k: f(listener.canonical(k))
hotkey = keyboard.HotKey(
keyboard.HotKey.parse(shortcut),
forwarder.signal.emit)
listener = keyboard.Listener(
on_press=for_canonical(hotkey.press),
on_release=for_canonical(hotkey.release))
listener.start()
def wake_up(self):
print('Waking up')
self.show()
self.cheeky_focus_stealer()
def cheeky_focus_stealer(self):
self.setFocus()
self.raise_()
self.activateWindow()
# Working of linux, but causes freeze/crash on Windows 10
message_box = QMessageBox(self)
message_box.show()
message_box.hide()
def eventFilter(self, obj, event):
if obj is self and event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Escape:
self.hide()
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
window = Launcher()
window.show()
app.exec_()
if __name__ == "__main__":
main()
I am programming a simple GUI, that will open a opencv window at a specific point. This window has some very basic keyEvents to control it. I want to advance this with a few functions. Since my QtGui is my Controller, I thought doing it with the KeyPressedEvent is a good way. My Problem is, that I cannot fire the KeyEvent, if I am active on the opencv window.
So How do I fire the KeyEvent, if my Gui is out of Focus?
Do I really need to use GrabKeyboard?
The following code reproduces my Problem:
import sys
from PyQt5.QtWidgets import (QApplication, QWidget)
from PyQt5.Qt import Qt
import cv2
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.first = True
def openselect(self):
im = cv2.imread(str('.\\images\\Steine\\0a5c8e512e.jpg'))
self.r = cv2.selectROI("Image", im)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Space and self.first:
self.openselect()
self.first = False
print('Key Pressed!')
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
The keyPressEvent method is only invoked if the widget has the focus so if the focus has another application then it will not be notified, so if you want to detect keyboard events then you must handle the OS libraries, but in python they already exist libraries that report those changes as pyinput(python -m pip install pyinput):
import sys
from PyQt5 import QtCore, QtWidgets
from pynput.keyboard import Key, Listener, KeyCode
class KeyMonitor(QtCore.QObject):
keyPressed = QtCore.pyqtSignal(KeyCode)
def __init__(self, parent=None):
super().__init__(parent)
self.listener = Listener(on_release=self.on_release)
def on_release(self, key):
self.keyPressed.emit(key)
def stop_monitoring(self):
self.listener.stop()
def start_monitoring(self):
self.listener.start()
class MainWindow(QtWidgets.QWidget):
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
monitor = KeyMonitor()
monitor.keyPressed.connect(print)
monitor.start_monitoring()
window = MainWindow()
window.show()
sys.exit(app.exec_())
I'm working in PyQt5 and would like to be able check/uncheck a QCheckBox on a key prespress like with a QPushButton. I've checked the documentation and Google but cannot find a way to do this.
You have to overwrite the keyPressEvent method and call the nextCheckState() method to change the state of the QCheckBox:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class CheckBox(QtWidgets.QCheckBox):
def keyPressEvent(self, event):
if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
self.nextCheckState()
super(CheckBox, self).keyPressEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = CheckBox("StackOverflow")
w.show()
sys.exit(app.exec_())