PyQt5 - Hover signal for QPushButton created via Qt Designer - python

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)).

Related

PySide2 main window not working after pop-up

I am building a graphical interface for an application using PySide2. My main window is a QMainWindow and I am trying to open a pop-up window, which is a QDialog, whenever a specific action is performed on the main window.
The pop-up opens perfectly fine. However, after it is open, the main window is no longer responsive. I believe the problem is that my application is overwriting the main window with the popup window.
The error message whenever I try to change the main window's stackedWidget index is:
AttributeError: 'Ui_popupHideSuccess' object has no attribute 'stackedWidget'
The code I am using to open the main window is the following:
if __name__ == '__main__':
app = QApplication(sys.argv)
myWindow = MainWindow()
myWindow.show()
sys.exit(app.exec_())
And the code I am using to open the pop-up window is the following:
def showPopupSuccessHide(self):
self.window = QDialog()
self.ui = Ui_popupHideSuccess()
self.ui.setupUi(self.window)
self.window.show()
The code for the windows themselves are on other files (as I am using QtDesigner for developing them). I believe it to be unnecessary for resolving this issue, but I can provide it if needed. What am I doing wrong here? I need to open pop-ups and still interact with the main window after.
I have no idea how to actually resolve this. I believe my error to be in the code I am using to open the pop-up window, but I'm not sure how to tweak it for it to work properly.
TL;DR
Do not overwrite self.ui.
Explanation
How uic composition works
One of the common ways of properly using pyuic generated files is to use composition (as opposed to multiple inheritance):
from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog
from ui_mainWindow import Ui_MainWindow
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.myLineEdit.setText('some text')
This is perfectly fine, and makes sense: the concept is that an instance of the pyuic class (sometimes called "form class") is created and then the actual window is "set up" using that instance, with the self.ui object containing references to all widgets.
Note that making the ui persistent (using an instance attribute) is actually not a strict requirement, but it is usually necessary in order to be able to directly access the widgets, which is normally important to create signal connections or read properties.
But, if that's not required, it will work anyway: the widgets are automatically "reparented" to the main window (or their direct parents), and the garbage collection is not an issue as Qt will keep its own references internally (in Qt terms, "the window takes ownership").
Technically speaking, this is completely valid:
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
Ui_MainWindow().setupUi(self)
Then, we can still access the widgets using findChild and their object names (those set in Designer):
self.findChild(QLineEdit, 'myLineEdit').setText('some text')
Obviously, it is not very practical.
Creating "child" windows
When there is the need to create a child window (usually, a dialog), it's normally suggested to use an instance attribute to avoid garbage collection:
def createWindow(self):
self.window = QDialog()
self.window.show()
If that dialog also has a Designer file, we need to do something similar to what done at the beginning. Unfortunately, a very common mistake is to create the ui instance by using the same name:
def createWindow(self):
self.window = QDialog()
self.ui = Ui_Dialog()
self.ui.setupUi(self.window)
self.ui.anotherLineEdit.setText('another text')
self.window.show()
This is theoretically fine: all works as expected. But there's a huge problem: the above overwrites self.ui, meaning that we lose all references to the widgets of the main window.
Suppose that you want to set the text of the line edit in the dialog based on the text written in that of the main window; the following will probably crash:
def createWindow(self):
self.window = QDialog()
self.ui = Ui_Dialog()
self.ui.setupUi(self.window)
self.ui.anotherLineEdit.setText(self.ui.myLineEdit.text())
self.window.show()
This clearly shows an important aspect: it's mandatory to always think before assigning attributes that may already exist.
In the code here above, this was actually done twice: not only we overwrote the self.ui we created before, but we also did it for window(), which is an existing function of all Qt widgets (it returns the top level ancestor window of the widget on which it was called).
As a rule of thumb, always take your time to decide how to name objects, use smart names, and consider that most common names are probably already taken: remember to check the "List of all members, including inherited members" link in the documentation of the widget type you're using, until you're experienced enough to remember them.
Solutions
The obvious solution is to use a different name for the ui of the dialog:
def createWindow(self):
self.dialog = QDialog()
self.dialog_ui = Ui_Dialog()
self.dialog_ui.setupUi(self.dialog)
self.dialog_ui.anotherLineEdit.setText(self.ui.myLineEdit.text())
self.dialog.show()
A better solution is to create a subclass for your dialog instead:
class MyDialog(QDialog):
def __init__(self, parent=None)
super().__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
class MyWindow(QMainWindow):
# ...
def createWindow(self):
self.dialog = MyDialog()
self.dialog.ui.anotherLineEdit.setText(self.ui.myLineEdit.text())
self.dialog.show()
Also remember that another common (and, to my experience, simpler and more intuitive) method is to use multiple inheritance instead of composition:
class MyDialog(QDialog, Ui_Dialog):
def __init__(self, parent=None)
super().__init__(parent)
self.setupUi(self)
class MyWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.myLineEdit.setText('some text')
def createWindow(self):
self.dialog = MyDialog()
self.dialog.anotherLineEdit.setText(self.myLineEdit.text())
self.dialog.show()
The only issue of this approach is that it may inadvertently overwrite names of functions of the "main" widget: for instance, if you created a child widget in Designer and renamed it "window". As said above, if you always think thoroughly about the names you assign to objects, this will probably never happen (it doesn't make a lot of sense to name a widget "window").

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: Get keystrokes from QPlainTextWidget created by Qt Designer

I need to read the keystrokes typed into a QPlainTextWidget from a form created by Qt Designer.
I know that I can read the entire resulting text with QPlainTextWidget.toPlainText(), but I need to read the letters individually as they're typed. (I'm writing a file-renaming program for photographers and would like to display to the user the new filename that results from their typing into the text box. I use the their entry as a base name for the filename.)
I tried connecting a subroutine to the text box with self.txtFilename.keyPressDown.connect(self.DiplayNewFilename), but that produces an error because the keyPressDown is not a slot.
I've read online that subclassing can create a solution, but I don't know how do to that, or where to put the subclassing code into my overall code. My program is a single-form app that uses a GUI form I created in Qt Designer. I want to create a flexible solution that would let me add coding solution in my main Python file, rather than editing the GUI code generated by Qt Designer.
My current code only has one class (for the main window). Where would I put the new subclassing code -- inside this existing class, or as a new class? How would I call it?
I'm running Ubuntu 16.04, Python 3, and Qt 4. photoOrg_MainWindow.Ui_MainWindow in the code below is the form created by Qt Designer. txtFilename is the text widget whose keytrokes I want to capture.
Cheers, and thanks very much for your wisdom!
TL/DR: Is there a way to access text box key strokes from a text widget created in Qt Designer?
Here's the essence of my code so far:
class MainWindow(QtGui.QMainWindow, photoOrg_MainWindow.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
self.actionOpen_Files.triggered.connect(self.GetPhotoFileNames)
self.txtFilename.keyPressDown.connect(self.DisplayNewFilename) #this doesn't work
more irrelevant routines and connect bindings code here...
def DisplayNewFilename(self):
Code that processes the keystroke to display it as part of a sample new filename displayed in a QLabel
def main():
app = QtGui.QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
if __name__ == '__main__':
main()
I think you are thinking about the problem too much :) .. an eventFilter can help you a lot. Here is a small example which can help a bit
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.setGeometry(300, 300, 250, 150)
vLayout = QtGui.QVBoxLayout(self)
plainTextEdit = QtGui.QPlainTextEdit()
self.newName = QtGui.QLineEdit()
vLayout.addWidget(plainTextEdit)
vLayout.addWidget(self.newName)
plainTextEdit.installEventFilter(self)
self.show()
def eventFilter(self, sourceObj, event):
if event.type() == QtCore.QEvent.KeyPress:
if event.text() == "z":
self.newName.setText("do you mean zoo ?")
return QtGui.QWidget.eventFilter(self, sourceObj, event)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
And keyPressDown is not a signal its event .. please read the docs for more hints

How to Autoresize QLabel pixmap keeping ratio without using classes?

We are making a GUI using PyQt and Qt Designer. Now we need that an image(pixmap) placed in a QLabel rescales nicely keeping ratio when the window is resized.
I've been reading other questions/answers but all of them use extended classes. As we are making constant changes in our UI, and it's created with Qt Creator, the .ui and (corresponding).py files are automatically generated so, if I'm not wrong, using a class-solution is not a good option for us because we should manually change the name of the class each time we update the ui.
Is there any option to autoresize the pixmap in a QLAbel keeping the ratio and avoiding using extended clases?
Thanks.
There are a couple of ways to do this.
Firstly, you can promote your QLabel in Qt Designer to a custom subclass that is written in python. Right-click the QLabel and select "Promote to...", then give the class a name (e.g. "ScaledLabel") and set the header file to the python module that the custom subclass class will be imported from (e.g. 'mylib.classes').
The custom subclass would then re-implement the resizeEvent like this:
class ScaledLabel(QtGui.QLabel):
def __init__(self, *args, **kwargs):
QtGui.QLabel.__init__(self)
self._pixmap = QtGui.QPixmap(self.pixmap())
def resizeEvent(self, event):
self.setPixmap(self._pixmap.scaled(
self.width(), self.height(),
QtCore.Qt.KeepAspectRatio))
For this to work properly, the QLabel should have its size policy set to expanding or minimumExpanding, and the minimum size should be set to a small, non-zero value (so the image can be scaled down).
The second method avoids using a subclass and uses an event-filter to handle the resize events:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
...
self._pixmap = QtGui.QPixmap(self.label.pixmap())
self.label.installEventFilter(self)
def eventFilter(self, widget, event):
if (event.type() == QtCore.QEvent.Resize and
widget is self.label):
self.label.setPixmap(self._pixmap.scaled(
self.label.width(), self.label.height(),
QtCore.Qt.KeepAspectRatio))
return True
return QtGui.QMainWindow.eventFilter(self, widget, event)
Set background-image:, background-repeat: and background-position QSS properties for your label. You may do it via Forms editor or in code QWidget::setStyleSheet.
A good starting point for QSS (with examples) - http://doc.qt.io/qt-5/stylesheet-reference.html
One way is to create a QWidget/QLabel subclass and reimplement the resizeEvent.
void QWidget::resizeEvent(QResizeEvent * event) [virtual protected]
This event handler can be reimplemented in a subclass to receive widget resize events which are passed in the event parameter. When resizeEvent() is called, the widget already has its new geometry. The old size is accessible through QResizeEvent::oldSize().
The widget will be erased and receive a paint event immediately after processing the resize event. No drawing need be (or should be) done inside this handler.
This would need to be done in C++ though, not PyQt.
Having that done, you could add your custom widget to the QtDesigner as follows:
Using Custom Widgets with Qt Designer
Incredibly, after seven years #ekhumoro's excellent answer is still pretty much the only working Python implementation that can be found around; everyone else tells what to do, but nobody gives the actual code.
In spite of this, it did not work at first for me, because I happened to have the pixmap generation somewhere else in the code - specifically, my pixmap was generated inside a function which was only activated when clicking on a button, so not during the window intialization.
After figuring out how #ekhumoro's second method worked, I edited it in order to accomodate this difference. In pratice I generalised the original code, also because I did not like (for efficiency reasons) how it added a new _pixmap attribute to the label, which seemed to be nothing more than a copy of the original pixmap.
The following his is my version; mind that I have not fully tested it, but since it is a shorter version of my original working code, it too should work just fine (corrections are welcome, though):
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# Initialize stuff here; mind that no pixmap is added to the label at this point
def eventFilter(self, widget, event):
if event.type() == QEvent.Resize and widget is self.label:
self.label.setPixmap(self.label.pixmap.scaled(self.label.width(), self.label.height(), aspectRatioMode=Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation))
return True
return QMainWindow.eventFilter(self, widget, event)
def apply_pixelmap(self, image): # This is where the pixmap is added. For simplicity, suppose that you pass a QImage as an argument to this function; however, you can obtain this in any way you like
pixmap = QPixmap.fromImage(image).scaled(new_w, new_h, aspectRatioMode=Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
self.label.setPixmap(pixmap)
self.label.pixmap = QPixmap(pixmap) # I am aware that this line looks like a redundancy, but without it the program does not work; I could not figure out why, so I will gladly listen to anyone who knows it
self.label.installEventFilter(self)
return
This works by setting the ScaledContents property to False and the SizePolicy to either Expanding or Ignored. Note that it might not work if the label containing the image is not set as the central widget (self.setCentralWidget(self.label), where self refers to MainWindow).

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