Python: Get keystrokes from QPlainTextWidget created by Qt Designer - python

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

Related

PyQT with QT Creator

I am trying to develop a small gui using PySide and the QT Creator.
As the base implementation, I have choosen a QMainWindow.
The problem is that adding any elements to that MainWindow inside the Editor results in an empty window when I run the code.
The initially generated python code looks like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.load_ui()
def load_ui(self):
loader = QUiLoader()
path = os.path.join(os.path.dirname(__file__), "form.ui")
ui_file = QFile(path)
ui_file.open(QFile.ReadOnly)
loader.load(ui_file, self)
ui_file.close()
if __name__ == "__main__":
app = QApplication([])
widget = MainWindow()
widget.show()
sys.exit(app.exec_())
I know that loader.load(ui_file, self) returns a widget. My solution was:
def load_ui(self):
loader = QUiLoader()
path = os.path.join(os.path.dirname(__file__), "form.ui")
ui_file = QFile(path)
ui_file.open(QFile.ReadOnly)
self.window = loader.load(ui_file, self)
self.window.show()
ui_file.close()
if __name__ == "__main__":
app = QApplication([])
widget = MainWindow()
#widget.show() <<<<<<--- removing this
sys.exit(app.exec_())
This works for me but this clearly cannot be the way this is supposed to work.
I am confused on why QT Creator gives me this non-working templaet.
Furthermore I am curious on how custom functions should be implemented.
When clicking on the clicked() slot for a button, it tells me
No ui_form.h found! (translated)
I ended up writing the functions into my MainWindow class like this:
self.window.menuopen_button.clicked.connect(lambda x: self.menu_animation(x))
self.window.minimise_button.clicked.connect(lambda x: self.menu_buttons('minimise'))
Your initial code only loads the UI file into the QUiLoader object - you don't do anything else with it. When you call widget.show(), you're calling the QMainWindow's show method which is a default window (and, hence, empty) since you haven't added the loaded widget to it.
Your solution similarly loads the UI but displays it via the widget object's show method. However, you're not amending it to your main window, so you're basically tossing your main window and just using the widget.
There are a couple ways to correctly accomplish what you want:
One way would be to, as #musicamante suggested, add the loaded UI widget to your MainWindow object (and by the way, this is supported by PySide2: https://doc.qt.io/archives/qtforpython-5.12/PySide2/QtUiTools/QUiLoader.html - the example in the detailed description shows exactly how to do this).
Or the other way, which I prefer, would be to use the uic utility, PySide2-uic, in the build process to generate a UI Python class based on the UI file.
The Qt documentation has tutorials showing both methods. The linked documentation shows PySide6 as support for PySide2 has been discontinued with the release of Qt6. The API for PySide2, PySide6, and PyQt5 are all very similar, so if you're migrating from either PySide2 or PyQt then the change should be relatively painless. I recommend using PySide6 as this is the Python binding officially supported by Qt.

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.

How to run a pyqt5 application properly?

I can't get the GUI for my application to run in the manner that I need it to. My question is, given the below criteria, how do I go about setting up and running the GUI properly. The lack of good documentation that I have found is insanely frustrating (maybe I'm looking in the wrong places?).
I have a main window in a file called MainCustomerWindow.py containing a class by the same name. This is where all the code from the qt designer is. I have another class file called GUIController. The GUIController class does just that, controls the multiple GUI windows. It is in this GUIController class that I am trying to instantiate and run the MainCustomerWindow. Here is the code I have been trying.
def setup_window(self):
APP = QtWidgets.QApplication(sys.argv)
Window = MainCustomerWindow()
Window.setupUi(QtWidgets.QMainWindow)
Window.show()
sys.exit(APP.exec_())
Just as a side note, I come from JavaFX and Swing, and don't fully understand the workflow for pyqt5. So if someone could add an explanation for that as well it would be greatly appreciated.
The class generated by Qt Designer is not a widget, it is a class used to fill an existing widget, so you must create an object in the window, assuming you have used the "Main Window" template, then the widget must be QMainWindow (if it is another maybe you should use QDialog or QWidget), then you have to create another class that belongs to the design, and using the method setupUi() you must pass the widget to fill it:
def setup_window(self):
app = QtWidgets.QApplication(sys.argv)
# create window
window = QtWidgets.QMainWindow()
ui = MainCustomerWindow()
# fill window
ui.setupUi(window)
window.show()
sys.exit(app.exec_())
Although a better option is to create a new class and have it inherit from both:
class MainWindow(QtWidgets.QMainWindow, MainCustomerWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
If you want to get detailed information I recommend you read the following:
http://pyqt.sourceforge.net/Docs/PyQt5/designer.html
You can try taking the code you have there and adding it to the main statement at the end of your app's script. You can also have this statement instantiate your class where the init method contains the setupui() call. For example:
if __name__ == '__main__':
app = QWidgets.QApplication(sys.argv)
window = QMainWindow()
main_window = MainCustomerWindow()
window.show()
sys.exit(app.exec())
This code first sets up the PyQt app as an instance of QApplication. Next it instantiates an instance of QMainWindow so that PyQt knows what to display as the main app starts. In my experience, I've put setupui() in the init method of the app class. In your case, in the init method of MainCustomerWindow Finally, window.show() tells PyQt to begin rendering the main window.

Multiple windows in PyQt4?

I've just begun using pyqt4. I followed a tutorial (http://zetcode.com/tutorials/pyqt4/)
One thing that puzzles me is this part:
def main():
app = QtGui.QApplication(sys.argv)
ex = GUI()
sys.exit(app.exec())
And the reason for this I explain here:
I have made a small program that opens four more windows except for the first main window.
So I tried to replicate what I saw worked with main-window and created a class for every new window and tried to do like with the above. Currently it looks like this:
def main2():
#app = QtGui.QApplication(sys.argv)
ex2 = Settings()
sys.exit(app.exec())
As you can see I have modified it. If I left the first line in the function uncommented the program would crash. I tried to do without the sys.exit(app.exec_())-part but that would only make the new window close milliseconds after it showed.
This way though, everything runs and works. Only that in the command window, an error message displays. I don't know how to fix this, since I cannot remove the last line, and I dont't know what to replace "app" with.
I know I'm probably doing the new windows wrong from the beginning, but I don't know how to make these windows open from the original window in any other way. I haven't been able to get anything else to work, and this at least runs and works right now. So the only problem is error messages in the prompt, it would be nice to get rid of them :)
Thanks for any help (complicated and easy ones)!
Forgot to mention, I made the classes start like this:
class GUI(QtGui.QMainWindow):
def __init__(self):
super(GUI, self).__init__()
self.initUI()
and
class Settings(QtGui.QWidget):
def __init__(self):
super(Settings, self).__init__()
...here goes some more...
self.initUI2()
and I open Settings-window by calling main2()
You must create one and only one QApplication in your program.
Keep in mind that GUI programming is event-driven, you first declare widgets and then run the main loop with app.exec(), when the user quit your application, app.exec() returns.
The QApplication purpose is to handle user events and propagate them to your code with Qt signals. I suggest you check Qt documentation, it's very complete, even if it's targetting C++ programmers.
So for instance, a way to create two widgets would be:
def main():
app = QtGui.QApplication(sys.argv)
ex = QtGui.QWidget()
ex.show()
ex2 = QtGui.QWidget()
ex2.show()
sys.exit(app.exec())

Categories