PyQt widget keyboard focus - python

First off -- thanks for this group! I started delving into PyQt a month or so ago. In that time, I've bumped up against many questions, and virtually always found an answer here.
Until now.
I have a workaround for this, but I think it's a kluge and there probably is a proper way. I'd like to understand better what's going on.
Here's the code:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class FormWidget(QWidget):
def __init__(self, parent):
super(FormWidget, self).__init__(parent)
# Create view with image in it
self.image = QGraphicsPixmapItem(QPixmap())
self.scene = QGraphicsScene()
self.scene.addItem(self.image)
self.view = QGraphicsView(self.scene)
self.hlayout = QHBoxLayout()
self.hlayout.addWidget(self.view)
self.setLayout(self.hlayout)
# self.view.keyPressEvent = self.keyPressEvent
def keyPressEvent(self, event):
key = event.key()
mod = int(event.modifiers())
print(
"<{}> Key 0x{:x}/{}/ {} {} {}".format(
self,
key,
event.text(),
" [+shift]" if event.modifiers() & Qt.SHIFT else "",
" [+ctrl]" if event.modifiers() & Qt.CTRL else "",
" [+alt]" if event.modifiers() & Qt.ALT else ""
)
)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
form = FormWidget(self)
self.setCentralWidget(form)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
As is, all keyboard input is detected by the overloaded keyPressEvent() function, except arrow keys. I've found enough posts talking about this to have a sense that it is because the child widget (self.view) is receiving them. I presume the child widget is, in fact, receiving all the keystrokes, but ignoring the ones that are getting through, and sucking up the arrow keys, which is why they aren't getting to the parent's keyPressEvent() function. That seems to be so, because if I uncomment the line in the middle:
self.view.keyPressEvent = self.keyPressEvent
It behaves as I expect -- the parent's keyPressEvent() gets all the keystrokes, arrows included.
How would I tell the child widget to ignore all keystrokes? I thought maybe this:
self.view.setFocusPolicy(Qt.NoFocus)
When I add that, keyPressEvent() doesn't see any keystrokes at all.
I suppose I could overload keyPressEvent() for the child as well, and just explicitly pass everything up to the parent. But that doesn't seem better than my kluge.
I think I must be misunderstanding something here.
Thanks. Just looking to learn ...

By default, a QWidget does not accept the keyboard focus, so you need to enable it explicitly:
class FormWidget(QWidget):
def __init__(self, parent):
...
self.setFocusPolicy(Qt.StrongFocus)

Rather than subclassing the child widget or attempting to prevent keystrokes from reaching it, you should consider using an eventFilter to capture events on the child widget. You will see all events before the child widget, and you can suppress or transform them.
http://doc.qt.io/qt-5.5/qobject.html#eventFilter

Related

Qt ShortcutOverride default action

I understand that QEvent::ShortcutOverride occurs when there is a shortcut registered in the parent and the child wants to "go against the rule". The example given on the Qt Wiki is of a media player which pauses with Space but a QLineEdit might want to use the Space, which makes a lot of sense.
Furthermore, if the event is accepted then a QEvent::KeyPress is generated to the child widget so you can treat your particular case.
Now, my question is, why does it seem that the default action is to actually accept the QEvent::ShortcutOverride when a standard shortcut is used? This seems to me like the opposite of what the name suggest, i.e., it's overriden by default and you have to treat the event to let the shortcut pass.
In the code below, if you don't install the event filter you don't see the message.
from PySide.QtGui import QApplication
from PySide import QtGui, QtCore
app = QApplication([])
class Test(QtGui.QWidget):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
self.setLayout(QtGui.QVBoxLayout())
self.w_edit = QtGui.QLineEdit(parent=self)
self.layout().addWidget(self.w_edit)
# If we install the event filter and ignore() the ShortcutOverride
# then the shortcut works
self.w_edit.installEventFilter(self)
# Ctrl+Left is already in use (jump to previous word)
shortcut = QtGui.QShortcut(QtGui.QKeySequence('Ctrl+Left'), self)
shortcut.setContext(QtCore.Qt.ApplicationShortcut)
shortcut.activated.connect(self.test_slot)
def test_slot(self):
print('ctrl+left pressed!')
def eventFilter(self, obj, event):
if obj is self.w_edit and event.type() == QtCore.QEvent.ShortcutOverride:
# Send the event up the hierarchy
event.ignore()
# Stop obj from treating the event itself
return True
# Events which don't concern us get forwarded
return super(Test, self).eventFilter(obj, event)
widget = Test()
widget.show()
if __name__ == '__main__':
app.exec_()
My actual scenario is a tab widget for which I want to use Ctrl+Left/Right to cycle through the tabs, which works unless something like a QLineEdit has focus. I feel that there should be a better way other than calling event->ignore(); return true on all QLineEdits and any other widgets which could use the key combo, am I missing something here?
Thanks!
You can set one event filter on the application instance and then filter accordingly:
QtGui.qApp.installEventFilter(self)
# self.w_edit.installEventFilter(self)
...
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.ShortcutOverride:
# filter by source object, source.parent(), or whatever...
if isinstance(source, QtGui.QLineEdit):
event.ignore()
return True
return super(Test, self).eventFilter(source, event)

refresh(update) widget in Nuke (vfx soft)

I need refresh(update) GUI widget(pySide) in Nuke(compositing software) after load or save nuke script.
callback: nuke.addOnScriptSave() and nuke.addOnScriptLoad()
import nuke
from PySide import QtGui, QtCore
from nukescripts import panels
class Info(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.initUI()
def scriptName(self):
sName = (nuke.root().name()).split('/')#split name from root
return sName
def initUI(self):
self.lbl1 = QtGui.QLabel("script name : " , self)
layout = QtGui.QHBoxLayout()#main layout
layout.addWidget(self.lbl1)
self.setLayout(layout)
self.updateInfo()
def updateInfo(self):
scriptName = self.scriptName()
self.lbl1.setText("script name : " + scriptName[-1].split('.')[0])#set name
panels.registerWidgetAsPanel('Info', 'Info_script', 'infoscript')
The simple solution is to add the callback registration to your widget's __init__:
class Info(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.initUI()
nuke.addOnScriptSave(self.updateInfo)
if nuke.root().name() == 'Root' and not nuke.modified():
# No reason to add a scriptLoad callback if opening a
# script would spawn a new Nuke process.
nuke.addOnScriptLoad(self.updateInfo)
# Rest of the class definition omitted for brevity
This approach does have a notable downside: Because Nuke's callback registry will now contain at least one reference to the widget's updateInfo method, the widget can never be garbage-collected by Python. This will probably never be too big of a deal in real life, since you will probably only ever create a very small number of panel instances, but if you ever created, say, 1000 instances, you would have 1000 callbacks registered that would never be unregistered (even if the panels were closed).
Unfortunately, Nuke doesn't really give you any hooks to implement when your widget is being removed from the UI, so it's tough to have a sure-fire way of knowing it's time to unregister your callbacks. The closest you can get is probably using a showEvent/hideEvent combo and some guesswork, like so:
class Info(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.initUI()
self.callbacksRegistered = False
def addCallbacks(self):
nuke.addOnScriptSave(self.updateInfo)
if nuke.root().name() == 'Root' and not nuke.modified():
# No reason to add a scriptLoad callback if opening a
# script would spawn a new Nuke process.
nuke.addOnScriptLoad(self.updateInfo)
def removeCallbacks(self):
self.removeOnScriptSave(self.updateInfo)
self.removeOnScriptLoad(self.updateInfo)
def showEvent(self, event):
if not (self.callbacksRegistered or event.spontaneous()):
self.addCallbacks()
self.callbacksRegistered = True
def hideEvent(self, event):
if self.callbacksRegistered and not event.spontaneous():
# "Spontaneous" hide events are sent when the Nuke
# window is minimized, which we don't really care
# about.
self.removeCallbacks()
self.callbacksRegistered = False
# Rest of the class definition omitted for brevity
The callback registration is moved into the showEvent method, and the callbacks are unregistered when the widget is hidden by something other than an OS-level window operation. This is pretty reliable, except that your callbacks will also be unregistered when you change to another tab in the widget's pane (if it's docked). They will obviously be re-added when your tab is activated again though. This is a pretty minor penalty, but I still think it's worth pointing out that while this approach is pretty close to ideal, it isn't quite perfect.
Anyway, I'll leave it up to you to decide which of these two you like better, but I hope this helps.

QGraphicsItemGroup.removeFromGroup -- child items not correctly reparented to Scene

I'm trying to remove a QGraphicsItem from a QGraphicsItemGroup. When calling removeFromGroup, the item is removed (of course). However, it's then no longer visible in the Scene. I have to call Scene.addItem(item) in order for it to appear again. This is apparently something you shouldn't do (I'm given a warning for doing that). But I can't seem to find another workaround.
Here's a minimal example:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.view = QGraphicsView()
self.scene = QGraphicsScene()
self.view.setScene(self.scene)
self.setCentralWidget(self.view)
def add_group(scene):
group = QGraphicsItemGroup()
text = QGraphicsTextItem()
text.setPlainText("I'm visible")
group.addToGroup(text)
scene.addItem(group)
# After this, text is no longer in group. However, it is no longer visible.
group.removeFromGroup(text)
assert not text in group.childItems()
# But text is still in scene.
assert text.scene() == scene
# this works (i.e. text becomes visible again). However, it also produces a
# warning: QGraphicsScene::addItem: item has already been added to this scene.
# The docs also advice against it.
scene.addItem(text)
# According to the docs, I thought this might work, but it gives me a TypeError.
# text.setParentItem(0)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
add_group(main.scene)
main.show()
sys.exit(app.exec_())
Tips and hints are very welcome.
The QGraphicsTextItem can never be parented to a scene, because it's parent must be a QGraphicsItem (which QGraphicsScene does not inherit).
When the QGraphicsTextItem is created, it's parent is None. Its parent is set to group when it is added to it (QGraphicsItemGroup is a subclass of QGraphicsItem), then set back to None when it's removed from group.
Calling scene.addItem() is actually a NO-OP. Qt checks whether scene is the same as text.scene(), and if it is, it prints the warning and returns without doing anything.
The fact that it seems to "work" in some circumstances, is just an artifact of python's garbage-collecting mechanism.
If your test is re-cast in a more realistic way, the QGraphicsTextItem remains visible after removal from the group:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.view = QGraphicsView(self)
self.scene = QGraphicsScene(self.view)
self.view.setScene(self.scene)
self.setCentralWidget(self.view)
self.group = QGraphicsItemGroup()
self.text = QGraphicsTextItem()
self.text.setPlainText("I'm visible")
self.group.addToGroup(self.text)
self.scene.addItem(self.group)
self.group.removeFromGroup(self.text)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
The problem is that text is deleted since you don't have any reference to it after you remove it from the group, try this:
...
text = QGraphicsTextItem()
scene.text = text #just to keep the reference, ideally should be self.text = text
...
Now you don need scene.addItem(text)

PyQt4 how to restore a minimized window?

PyQt4 how to do if the window is minimized, call a method expand that window
please tell me how to do it
I will be very grateful
You can check the current state of a QWidget by calling its windowState() method. To change the state you pass a new state to setWindowState().
Here's an example app that checks every 5 seconds to see if it's minimised. If it is then the window is restored.
This is just as an example - checking every 5 seconds for a minimised window and restoring it would be an evil thing to do in an app ;).
import sys
import time
from PyQt4.QtGui import QApplication, QWidget
from PyQt4.QtCore import QTimer, Qt
class MyWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.timer = QTimer()
self.timer.setInterval(5000)
self.timer.timeout.connect(self.check_state)
self.timer.start()
def check_state(self):
if self.windowState() == Qt.WindowMinimized:
# Window is minimised. Restore it.
self.setWindowState(Qt.WindowNoState)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
The most accurate way to do this would be to watch for the QWindowStateChangeEvent of the widget, and respond immediately when it happens. You can do this more than one way.
Here is how you can re-implement the event method of the target widget:
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
def event(self, e):
if e.type() == e.WindowStateChange:
if self.windowState() & QtCore.Qt.WindowMinimized:
print "Minimized"
# self.showMaximized()
# call the super class event() no matter what
return super(Window, self).event(e)
Now if you have some other widget that you want to watch for Minimize, and you don't want to have to define a new event method on that object, you can create an object that simply watches events for multiple other objects. It is called an event filter:
class Watcher(QtCore.QObject):
def eventFilter(self, obj, e):
if obj.isWidgetType() and e.type() == e.WindowStateChange:
if obj.windowState() & QtCore.Qt.WindowMinimized:
print "Minimized"
# obj.showMaximized()
return False
app = QtGui.QApplication([])
aWindow = QtGui.QWidget()
aWatcher = Watcher(aWindow)
aWindow.installEventFilter(aWatcher)
aWindow.show()
app.exec_()
Note that when checking the windowState, you should use & to compare instead of ==, because the state can be a combination of more than one value and you need to check it with a mask to see if the value is present amongst the others. i.e. if you maximize the window first and then minimize it, it will have multiple window states.
On a side note, you also have the option of customizing the actual window properties in the first place. So if your goal is to prevent minimizing, you could disable the button for it:
aWindow = QtGui.QWidget()
flags = aWindow.windowFlags()
aWindow.setWindowFlags(flags ^ QtCore.Qt.WindowMinimizeButtonHint)
This will subtract the minimize button flag from all the other flags.
Hi guys me better come this way:
if self.windowState() == QtCore.Qt.WindowMinimized:
# Window is minimised. Restore it.
self.setWindowState(QtCore.Qt.WindowActive)
certainly not always this function is `working
probably the problem in the python
**thanks to all**

Stealing wheelEvents from a QScrollArea

I want to put my custom widget in a QScrollArea, but in my custom widget, I reimplemented wheelEvent(e) and it never gets called.
I'm fine with the scroll area not having its mouse wheel scrolling functionality. I just need those wheelEvents to call my handler. I tried handling the events out at the level of the main window but I only got them when the scroll widget was at one of its extremes and couldn't have moved any further anyways, I need all of them.
Heres a simplified version of my code:
class custom(QWidget):
def __init__(self, parent=None):
super(custom, self).__init__(parent)
self.parent = parent
def wheelEvent(self,event):
print "Custom Widget's wheelEvent Handler"
class mainw(QMainWindow):
def __init__(self, parent=None):
super(mainw, self).__init__(parent)
scroll = QScrollArea()
self.tw = thread_widget(scroll)
scroll.setWidget(self.tw)
self.setCentralWidget(scroll)
def wheelEvent(self,event):
print "Main Window's wheelEvent Handler"
Can someone explain to me how it is determined which event handler gets the events in this situation?
I figured out that its got something to do with the installEventFilter method of QObject, but I couldn't get the example to work so I said to hell with this and changed my plan completely.
problem solved
You can install a eventFilter in your custom class
class custom(QWidget):
def __init__(self, parent=None):
super(custom, self).__init__(parent)
self.parent = parent
self.installEventFilter(self)
def eventFilter(self, qobject, qevent):
qtype = qevent.type()
if qtype == QEvent.Wheel:
... wheel event logic
return True
# parents event handler for all other events
return super(custom,self).eventFilter(qobject, qevent)

Categories