QPushButton tab highlight signal - python

I'm using PyQt to design an app. For accessibility reasons, I want to speak the name of a button when it is highlighted (using navigation by tab key.)
I have the speech down okay using Windows Speech API. Now I want to use signals and slots, but QPushButton doesn't seem to have a signal for when it is highlighted. The ones I have found are clicked, destroyed, pressed, released, toggled. None of them work.
Is there any way to set up a custom signal that will be emitted when the button is highlighted by tab?

The QApplication is responsible for managing widget focus, so you could connect to its focusChanged signal:
QtGui.qApp.focusChanged.connect(self.handleFocusChanged)
The signal sends references to the previous/current widget that has lost/received the focus (by whatever means), so the handler might look like this:
def handleFocusChanged(self, old, new):
if old is not None and new is not None:
if isinstance(new, QtGui.QPushButton):
print('Button:', new.text())
elif isinstance(new, QtGui.QLineEdit):
print('Line Edit:', new.objectName())
# and so forth...
You can also get the widget that currently has the focus using:
widget = QtGui.qApp.focusWidget()

While the accepted answer by #ekhumoro works, and is the better way (in my opinion), it is also possible to achieve this by subclassing the QPushButton. Something like this:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class FocusButton(QPushButton):
def __init__(self, parent=None):
super(FocusButton, self).__init__(parent)
tabSignal = pyqtSignal()
def focusInEvent(self, QFocusEvent):
self.emit(SIGNAL('tabSignal()'))
It is now possible to create FocusButton objects instead of QPushButton, and they will emit the tabSignal whenever they receive focus.

Related

Sending a fake mouse click to an unfocused QLineEdit widget with PyQt

I am using PyQt5. I want to simulate a mouse click by a user on a specific QLineEdit widget programmatically, expecting the same behavior as for the real click.
The code below is my attempt, where this action is bound to a QPushButton. It works correctly when the QLineEdit widget target is focused. But if some other widget has focus when the button is pressed, both the focused widget and the target widget get the "focused" frame:
What should I do to avoid this problem and perform a simulated click correctly?
Note: it is not clear to me why calling .setFocus() in on_button_clicked below is necessary. I thought that a mouse click on a widget with a Qt.StrongFocus focus property is sufficient to switch focus, but the simulated click seems to be completely ignored by the target widget if the focus is not manually switched.
import functools
from PyQt5.QtWidgets import *
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt
app = QApplication([])
win = QMainWindow()
widget = QWidget()
win.setCentralWidget(widget)
layout = QVBoxLayout()
widget.setLayout(layout)
target = QLineEdit('Widget to be controlled')
layout.addWidget(target)
layout.addWidget(QLineEdit('(widget to test focus)'))
button = QPushButton('Click')
layout.addWidget(button)
def on_button_clicked(target):
# This minimal example always uses the same position in the widget
pos = QtCore.QPointF(25, 10)
# Transfer focus, otherwise the click does not seem to be handled
target.setFocus(Qt.OtherFocusReason)
press_event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, pos,
Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
target.mousePressEvent(press_event)
release_event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonRelease, pos,
Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
target.mouseReleaseEvent(release_event)
button.clicked.connect(functools.partial(on_button_clicked, target))
win.show()
app.exec_()
(If it helps or if there are easier ways to do this, the thing I am actually trying to do is to construct a widget which behaves almost like a QLineEdit, but completely ignores single mouse clicks (both press and release events), while the double click works like a single click on a QLineEdit. Transforming the double click event into a single click event on the underlying QLineEdit widget is the part I struggle with.)

pyqt5 - connect a function when QComboBox is clicked [duplicate]

I have been trying to get a QComboBox in PyQt5 to become populated from a database table. The problem is trying to find a method that recognizes a click event on it.
In my GUI, my combo-box is initially empty, but upon clicking on it I wish for the click event to activate my method for communicating to the database and populating the drop-down list. It seems so far that there is no built-in event handler for a click-event for the combo-box. I am hoping that I am wrong on this. I hope someone will be able to tell me that there is a way to do this.
The best article I could find on my use-case here is from this link referring to PyQt4 QComboBox:
dropdown event/callback in combo-box in pyqt4
I also found another link that contains a nice image of a QComboBox.
The first element seems to be a label followed by a list:
Catch mouse button pressed signal from QComboBox popup menu
You can override the showPopup method to achieve this, which will work no matter how the drop-down list is opened (i.e. via the mouse, keyboard, or shortcuts):
from PyQt5 import QtCore, QtWidgets
class ComboBox(QtWidgets.QComboBox):
popupAboutToBeShown = QtCore.pyqtSignal()
def showPopup(self):
self.popupAboutToBeShown.emit()
super(ComboBox, self).showPopup()
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.combo = ComboBox(self)
self.combo.popupAboutToBeShown.connect(self.populateConbo)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.combo)
def populateConbo(self):
if not self.combo.count():
self.combo.addItems('One Two Three Four'.split())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
However, for your particular use-case, I think a better solution might be to set a QSqlQueryModel on the combo-box, so that the items are updated from the database automatically.
Alternative Solution I :
We can use frame click, the code is to be used in the container of the combo box (windows/dialog/etc.)
def mousePressEvent(self, event):
print("Hello world !")
or
def mousePressEvent():
print("Hello world !")
Alternative Solution II :
We could connect a handler to the pressed signal of the combo's view
self.uiComboBox.view().pressed.connect(self.handleItemPressed)
...
def handleItemPressed(self, index):
item = self.uiComboBox.model().itemFromIndex(index)
print("Do something with the selected item")
Why would you want to populate it when it's activated rather than when the window is loaded?
I am currently developing an application with PySide (another Python binding for the Qt framework), and I populate my comboboxes in the mainwindow class __init__ function, which seems to be the way to go, judging by many examples.
Look at the example code under "QCombobox" over at Zetcode.

Python: capture re-ordering event in a QListWidget?

I need to respond to a drag-and-drop re-ordering of items in QListWidget. I can't find an QEvent to use. Any help would be appreciated!
I'm using an eventFilter routine to capture events in my GUI:
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
def eventFilter(self, sourceObj, event):
if event.type()== QtCore.QEvent.Drop:
print("got to drop event")
(processing code here)
Perhaps this event doesn't trigger on a drag-and-drop re-order of items because the drag-and-drop is internal to the widget (e.g., nothing from outside the widget is dropped on it)?
I've tried QtCore.QEvent.Drop and QtCore.QEvent.MouseButtonRelease without success. Which event should I use?
(I'm using Python3, PyQt4, on Ubuntu 16.10. My eventFilter routine works for other actions in the GUI like clicks, lostFocus ,etc.)
Here's the solution I found to work. The solution was to use the eventFilter to listen for the ChildRemoved QEvent. Apparently, QT triggers this event when items in a QListWidget are re-ordered via drag-and-drop.
Step 1. In the init(self) for your class that includes the QListWidget, install the event filter for the widget whose event you want to capture.
def __init__(self):
self.lstYourWidgetNameHere.installEventFilter(self)
Step 2. In the eventFilter routine in the same class, check for the ChildRemoved QEvent.
def eventFilter(self, sourceObj, event):
objName = str(sourceObj.objectName())
if event.type()== QtCore.QEvent.ChildRemoved:
if objName == "lstYourWidgetNameHere":
(your code here)

How to pass along menu shortcuts from child widget holding focus in PyQt4?

I have a QMainWindow with a menu bar, including menu items for Save, Open and Quit with the usual shortcuts. It creates a QTableWidget that lists a bunch of different categories from which the user can choose (at his option).
If the user clicks into the QTableWidget to change categories, the widget takes the focus. That's mostly what I want, but unfortunately it also seems to steal the menu shortcuts, so that pressing Ctrl+S no longer triggers the save.
I experimented with keyPressEvent to solve this, but it seems like overkill even if I do get it working. Isn't there a way to delegate all the control/menu keys back to the QMainWindow ?
There must be an issue with how you are creating your QMenuBar. Here is an example that works just fine for me. The Save continues to function regardless of focus being in the table:
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.resize(640,480)
menuBar = self.menuBar()
menu = menuBar.addMenu("&File")
action = menu.addAction("&Save", self.doAction)
action.setShortcuts(QtGui.QKeySequence.Save)
self.view = QtGui.QTableWidget(5,5)
self.setCentralWidget(self.view)
def doAction(self):
print "Save"

How to detect mouse click on images displayed in GUI created using PySide

Firstly, I'm new to Python, Qt and PySide so forgive me if this question seems too simple.
What I'm trying to do is to display a bunch of photos in a grid in a GUI constructed using PySide API. Further, when a user clicks on a photo, I want to be able to display the information corresponding to that photo. Additionally, I would like the container/widget used for displaying the photo to allow for the photo to be changed e.g. I should be able to replace any photo in the grid without causing the entire grid of photos to be created from scratch again.
Initially I tried to use QLabel to display a QPixmap but I realized (whether mistakenly or not) that I have no way to detect mouse clicks on the label. After some searching, I got the impression that I should subclass QLabel (or some other relevant class) and somehow override QWidget's(QLabel's parent class) mousePressEvent() to enable mouse click detection. Problem is I'm not sure how to do that or whether there is any alternative widget I can use to contain my photos other than the QLabel without having to go through subclass customization.
Can anyone suggest a more suitable container other than QLabel to display photos while allowing me to detect mouse clicks on the photo or provide some code snippet for subclassing QLabel to enable it to detect mouse clicks?
Thanks in advance for any replies.
I've added an example of how to emit a signal and connect to another slot. Also the docs are very helpful
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)
picture = PictureLabel("pic.png", self)
picture.pictureClicked.connect(self.anotherSlot)
layout.addWidget(picture)
layout.addWidget(QLabel("click on the picture"))
def anotherSlot(self, passed):
print passed
print "now I'm in Main.anotherSlot"
class PictureLabel(QLabel):
pictureClicked = Signal(str) # can be other types (list, dict, object...)
def __init__(self, image, parent=None):
super(PictureLabel, self).__init__(parent)
self.setPixmap(image)
def mousePressEvent(self, event):
print "from PictureLabel.mousePressEvent"
self.pictureClicked.emit("emit the signal")
a = QApplication([])
m = Main()
m.show()
sys.exit(a.exec_())
Even if the question has been answered, i want to provide an other way that can be used in different situations (see below) :
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)
picture = QLabel()
picture.setPixmap("pic.png")
layout.addWidget(picture)
layout.addWidget(QLabel("click on the picture"))
makeClickable(picture)
QObject.connect(picture, SIGNAL("clicked()"), self.anotherSlot)
def anotherSlot(self):
print("AnotherSlot has been called")
def makeClickable(widget):
def SendClickSignal(widget, evnt):
widget.emit(SIGNAL('clicked()'))
widget.mousePressEvent = lambda evnt: SendClickSignal(widget, evnt)
a = QApplication([])
m = Main()
m.show()
sys.exit(a.exec_())
This way doesn't imply subclassing QLabel so it can be used to add logic to a widget made with QtDeigner.
Pros :
Can be used over QTdesigner compiled files
Can be applied to any kind of widget (you might need to include a super call to the overrided function to ensure widget's normal behavior)
The same logic can be used to send other signals
Cons :
You have to use the QObject syntax to connect signals and slots

Categories