Python: capture re-ordering event in a QListWidget? - python

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)

Related

periodic polling of port or hardware IO point on Raspberry Pi

In developing an application using Qt5 with Python, you are generally event driven. No sweat, works like a charm. However, there are instances when you need to poll the status of some hardware GPIO (i.e. a button push), or get some information from a serial port, or something like a gpsd daemon.
What is the preferred way to handle this? Via a QTimer, say, running every 50 msec? Or is there some other method I haven't found? Is it better to set up a trigger on a GPIO pi (https://www.ics.com/blog/control-raspberry-pi-gpio-pins-python) or is there any conflict with the Qt5 Gui?
Basic documentation doesn't look horrible, and I can follow some examples, of course, but didn't know if there was a better/canonical/more Pythonic method.
https://doc.qt.io/qtforpython/PySide2/QtCore/QTimer.html
https://python-catalin.blogspot.com/2019/08/python-qt5-qtimer-class.html
I don't think there is a pythonic solution, not because you can't use python but because python is not relevant to the topic. And there is no canonical solution either, everything will depend on the application.
From my experience I have found it much easier to reuse libraries that handle GPIOs like Rpi.GPIO or gpiozero. These libraries have as a strategy to create threads where the state of the pins is monitored, so you cannot use the callbacks directly to update the GUI but you must implement wrapper(see this for example).
trivial example:
import sys
from gpiozero import Button
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtWidgets import QMainWindow, QApplication
class ButtonManager(QObject):
pressed = pyqtSignal()
def __init__(self, parent=None):
super(ButtonManager, self).__init__(parent)
self._button = Button(20)
self._button.when_pressed = self._on_when_pressed
def _on_when_pressed(self):
self.pressed.emit()
class MainWindow(QMainWindow):
#pyqtSlot()
def on_pressed(self):
print("pressed")
def main():
app = QApplication(sys.argv)
w = MainWindow()
button_manager = ButtonManager()
button_manager.pressed.connect(w.on_pressed)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
If you are going to use serial ports then the best option is to use Qt Serial Port since unlike pyserial it is not necessary to use threads but the notification is through signals(See this for example)
Using a QTimer is another option.

PyQt5 - Hover signal for QPushButton created via Qt Designer

I just created my first PyQt app used to store personnal data.
On the New Entry Dialog there is a button that when clicked, fills in QLineEdits with default values.
I would like to implement a feature so that when the mouse cursor hovers this Default button, you get a preview (probably via setPlaceholderText) of what the QLineEdits will be set to.
After looking around for a solution I came across this solution : How to Catch Hover and Mouse Leave Signal In PyQt5
to subclass the PushButton and reimplement enterEvent and leaveEvent.
However I have created my GUI with Qt Designer and am a bit confused as to how I can apply this solution since the QPushButton is created inside the Designer's .ui file where I can't really make changes...
Here's an extract of the .ui file when converted to .py with pyuic5
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
self.pushButton_contact_defaut = QtWidgets.QPushButton(self.groupBox_client)
self.pushButton_contact_defaut.setGeometry(QtCore.QRect(80, 130, 165, 22))
self.pushButton_contact_defaut.setMouseTracking(True)
self.pushButton_contact_defaut.setAutoDefault(False)
self.pushButton_contact_defaut.setObjectName("pushButton_contact_defaut")
As I said, I can't really make changes there as the code is reseted everytime I make changes to the ui file...
And here is also an extract of my main python file where I ''handle'' all the connections and logic.
I am obviously not too familiar with Python and PyQt (or anything related to programming really!)
Is there a way to ''redefine'' the PushButton from within my code and is that the best way to approach the problem, or is there something else I am missing?
class NewEntry(NE_Base, NE_Ui):
def __init__(self):
super().__init__()
QDialog.__init__(self, parent=main_window)
self.ui = NE_Ui()
self.ui.setupUi(self)
self.setWindowModality(0)
self.ui.pushButton_contact_defaut.clicked.connect(self.contact_defaut)
Thanks for your help!
EDIT : Based on musicamante's answer I got it to work just fine for my app where I have 2 buttons that "fill in" different lineEdit by doing the following.
I applied .installEventFilter(self) on both pushButton and added :
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.Enter and source == self.ui.pushButton_contact_defaut:
self.ui.contact_text.setPlaceholderText(self.contact_base)
self.ui.cell_text.setPlaceholderText(self.cell)
self.ui.email_text.setPlaceholderText(self.courriel)
if event.type() == QtCore.QEvent.Enter and source == self.ui.pushButton_copy_adress:
self.ui.street_text.setPlaceholderText(self.street)
self.ui.city_text.setPlaceholderText(self.city)
self.ui.postal_text.setPlaceholderText(self.postal)
elif event.type() == QtCore.QEvent.Leave:
self.ui.contact_text.setPlaceholderText('')
self.ui.cell_text.setPlaceholderText('')
self.ui.email_text.setPlaceholderText('')
self.ui.street_text.setPlaceholderText('')
self.ui.city_text.setPlaceholderText('')
self.ui.postal_text.setPlaceholderText('')
return super().eventFilter(source, event)
It seems a bit awkward to handle multiple pushButton this way and hopefully someone can enlighten me on that problem as well, but in the meantime, it works!
You can install an event filter on the button you want to track. An event filter is a system that "monitors" events received by the watched object and can eventually do something afterwards (including ignoring the event itself).
In your case, you'll want to check for Enter and Leave events, which are fired each time the mouse enters or leaves the widget (they are usually implemented in enterEvent and leaveEvents subclasses).
class NewEntry(QDialog, NE_Ui):
def __init__(self, parent=None):
super().__init__(parent)
# Don't do the following, is unnecessary: you already called __init__
# QDialog.__init__(self, parent=main_window)
self.ui = NE_Ui()
self.ui.setupUi(self)
self.ui.pushButton_contact_defaut.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QEvent.Enter:
self.ui.lineEdit.setPlaceholderText('Default text')
elif event.type() == QEvent.Leave:
self.ui.lineEdit.setPlaceholderText('')
# *always* return a bool value (meaning that the event has been acted upon
# or not), it's common to call the base class implementation and then
# return the result of that
return super().eventFilter(source, event)
NEVER edit the files generated by pyuic, nor start to use them as a start for your code. As you've already found out, they're cleared each time you change the ui, and it's always better to import them as modules (or use them through uic.loadUi('somefile.ui', self)).

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.

QPushButton tab highlight signal

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.

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