Using signal to pop-up a screen - python

I'm having a little trouble using a signal to make a little screen appear.
Shortening all i have so far, this following code should show my problem.
import sys
from PyQt4 import QtGui, QtCore
qApp = QtGui.QApplication(sys.argv)
class InformatieVenster(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setWindowTitle('Informatie')
self.setGeometry(100,100,300,200)
informatie = InformatieVenster()
class MenuKlasse(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
about = QtGui.QAction('About...', self)
about.setShortcut('Ctrl+A')
about.setStatusTip('Some text, haha')
self.connect(about, QtCore.SIGNAL('clicked()'), QtCore.SIGNAL(informatie.show()))
menubar = self.menuBar()
self.Menu1 = menubar.addMenu('&File')
self.Menu1.addAction(about)
Menu = MenuKlasse()
Venster = QtGui.QMainWindow()
Venster.menuBar().addMenu(Menu.Menu1)
Venster.setGeometry(200, 200, 300, 300);
size = Venster.geometry()
Venster.show()
qApp.exec_()
When this program is runned, the 'informatie' window automatically pops-up.
However... i only want this to happen every time I click on 'about...' in the menu, or when i use the assigned shortcut.
How may i improve my code such that my problem will be made history?
Greets!

The window is shown, because you are actually calling .show() during your connect. You have to pass a function object, not the result of a function invocation, as argument to .connect(). Moreover the function to be invoked, if a signal is emitted, is called "slot", the second SIGNAL() is completely misplaced.
Replace the connect line with:
self.connect(about, QtCore.SIGNAL('triggered()') informatie.show)
Even better, use the modern connection syntax:
about.triggered.connect(informatie.show)
Btw, do not use absolute sizes in GUI programs. Instead use layout management.

Related

Prevent QCompleter's popup from hiding itself on connected QLineEdit's text changes

I'm trying to have a QCompleter that's updated with suggestions from a remote api that's being requested on every text change (e.g. like the google search bar).
This is a bit problematic, because the completer hides its popup after text is added to the line edit, and then doesn't show the popup after the model is updated in the slot connected to the reply's finished signal.
The completer not showing after the update can be solved by a call to its complete method, but this causes flicker on the popup because of the hiding before a response is received.
I believe the popup is being hidden here https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/util/qcompleter.cpp?h=6.3.1#n1392, but overriding the event filter to show the popup still causes some flicker, and the suggestions disappear until the model is updated in the update_callback.
from PySide6 import QtCore, QtWidgets
from __feature__ import snake_case, true_property # noqa: F401
class Window(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.main_widget = QtWidgets.QWidget(self)
self.set_central_widget(self.main_widget)
self.layout_ = QtWidgets.QVBoxLayout(self.main_widget)
self.line_edit = QtWidgets.QLineEdit(self)
self.layout_.add_widget(self.line_edit)
self.completer_model = QtCore.QStringListModel(self)
self.completer = QtWidgets.QCompleter(self.completer_model, self)
self.line_edit.textEdited.connect(self.update_model)
self.line_edit.set_completer(self.completer)
def update_model(self, query: str):
"""Simulate the network requests that calls self.update_callback when finished."""
QtCore.QTimer.single_shot(0, lambda: self.update_callback(query))
#self.completer_model.set_string_list([query + "completedtext"])
def update_callback(self, query):
self.completer_model.set_string_list([query + "completedtext"])
app = QtWidgets.QApplication()
window = Window()
window.show()
app.exec()
By using setCompleter() the line edit automatically calls setCompletionPrefix() anytime its text is changed. This is done right after the text is updated, so the completer is always updated "too soon" with the previous text, and that can cause flickering.
A possible solution is to always trigger the completer manually, which is achieved by using setWidget() on the completer (instead of setCompleter()) and explicitly calling complete().
class Window(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.completer.setWidget(self.line_edit)
# ...
def update_callback(self, query):
self.completer_model.setStringList([query + "completedtext"])
self.completer.complete()

How do I get the text of an QLineEdit into a method?

I'm still not able to understand how to properly connect Qt_pushButton or Qt_LineEdit to methods. I would be so glad to get a explanation which even I do truly understand...
I've put together a pretty basic UI with Qt Designer. It contains a lineEdit called "lineEditTest" which I can indeed change by typing
self.lineEditTest.setText("changed Text")
However, I'm totally stuck with getting the text which the user entered back into my program. I would like to automatically submit the entered text into my function which sets a var to this value and returns it into my UI class. QLineEdit's signal editingFinished sounds perfect for that I guess? But it won't pass the text which the user entered into my function.
QLineEdit does have a property called "text" right? So it seems logical to me that I just have to pass another arg - apart from self - to my function called text.
My code does look like this but it obviously won't work at all:
from PyQt5 import QtWidgets, uic
import sys
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('test.ui', self)
self.lineEditTest.setText("test")
self.lineEditTest.editingFinished.connect(self.changeText(text))
self.show()
def changeText(self):
currentText = text
print (currentText)
return currentText
app = QtWidgets.QApplication(sys.argv)
window = Ui()
app.exec_()
This will just crash with:
NameError: name 'text' is not defined`
The problem seems to be that the OP doesn't understand the logic of the signals and slots (I recommend you check here). The signals are objects that emit information, and the slots are functions (generally callable) that are connected to the signals to receive that information. And the information transmitted by the signal depends on each case, for example if you check the docs of editingFinished signal:
void QLineEdit::editingFinished() This signal is emitted when the
Return or Enter key is pressed or the line edit loses focus. Note that
if there is a validator() or inputMask() set on the line edit and
enter/return is pressed, the editingFinished() signal will only be
emitted if the input follows the inputMask() and the validator()
returns QValidator::Acceptable.
That signal does not send any information so do not expect to receive any information except knowing that the edition has ended by the user. So how can I get the text? Well, through the text() method of QLineEdit:
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi("test.ui", self)
self.lineEditTest.setText("test")
self.lineEditTest.editingFinished.connect(self.changeText)
self.show()
def changeText(self):
text = self.lineEditTest.text()
print(text)
And how to do if the signal sends information? Then the slot (the function) that this connected will have as arguments to that information, for example if you use the textChanged signal that is emitted every time the text of the QLineEdit is changed, it should be as follows:
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi("test.ui", self)
self.lineEditTest.setText("test")
self.lineEditTest.textChanged.connect(self.changeText)
self.show()
def changeText(self, text):
print(text)
# or
# text = self.lineEditTest.text()
# print(text)
The way you're binding your callback isn't correct. When you bind a callback (and this is true for other frameworks, not just for binding callbacks in PyQt), you want to bind the function which should be triggered when a certain event occurs. That's not what you're doing - you're explicitly invoking the callback self.changeText(text) (note: the parentheses invoke the function). So, you're invoking (calling) the function, evaluating it and placing the return value in ...editingFinished.connect().
Don't explicitly invoke the function. Just pass the function's name. This makes sense if you think about it: we're passing a callable object to connect - this is the function which should be called at a later point in time, when the editingFinished event occurs.
You also don't need to pass lineEditTest's text into the callback, since you can just get whatever text is in the widget via self.lineEditTest.text() from inside the callback.
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
from PyQt5 import uic
super(MainWindow, self).__init__()
uic.loadUi("mainwindow.ui", self)
self.lineEditTest.setPlaceholderText("Type something...")
self.lineEditTest.editingFinished.connect(self.on_line_edit_finished_editing)
#pyqtSlot()
def on_line_edit_finished_editing(self):
text = self.lineEditTest.text()
print(f"You finished editing, and you entered {text}")
def main():
from PyQt5.QtWidgets import QApplication
application = QApplication([])
window = MainWindow()
window.show()
return application.exec()
if __name__ == "__main__":
import sys
sys.exit(main())

Python3 PyQt5 setEnabled of a QAction causing crashes

For a project I'm creating a GUI using Python 3 and PyQt5. Because it has to be usable by people outside of my immediate team, I want to disable actions on the menu until they've already filled out some forms in other parts of the program (e.g. disabling the final solution view when they haven't set up the initial data connection). The issue is that when I try to call the QAction's setEnabled function outside of the function that created it (but still inside the overall class), it's causing my script to crash with no error code, so I'm having trouble understanding the issue. In the snipit below, I'm trying to set the "View Solution" menu option as true. There are some more options in that menu, but I deleted them here to make it more easy to read.
The code is structured something like this:
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QMessageBox, QStackedLayout
class MediaPlanner(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# Menu bar example from: zetcode.com/gui/pyqt5/
exitAction = QAction('&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
newProject = QAction('&New Project', self)
newProject.setShortcut('Ctrl+N')
newProject.setStatusTip('Start A New Project')
newProject.triggered.connect(self.createNewProject)
openProject = QAction('&Open Project',self)
openProject.setShortcut('Ctrl+O')
openProject.setStatusTip('Open A Saved Project')
openProject.setEnabled(False)
viewSolution = QAction('&View Solution',self)
viewSolution.setStatusTip('View the Current Solution (If Built)')
viewSolution.setEnabled(False)
self.statusBar()
menubar = self.menuBar()
filemenu = menubar.addMenu('&File')
filemenu.addAction(newProject)
filemenu.addAction(openProject)
filemenu.addAction(exitAction)
viewmenu = menubar.addMenu('&View')
viewmenu.addAction(viewSolution)
self.setGeometry(300,300,700,300)
self.setWindowTitle('Menubar')
self.show()
def createNewProject(self):
print('Project Created')
self.viewSolution.setEnabled(True)
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = MediaPlanner()
sys.exit(app.exec_())
The problem is that viewSolution is a variable, but it is not a member of the class so you will not be able to access it through the self instance. One possible solution is to make viewSolution member of the class as shown below:
self.viewSolution = QAction('&View Solution',self)
self.viewSolution.setStatusTip('View the Current Solution (If Built)')
self.viewSolution.setEnabled(False)
...
viewmenu.addAction(self.viewSolution)
Another possible solution is to use the sender() function, this function returns the object that emits the signal, using the following:
def createNewProject(self):
print('Project Created')
self.sender().setEnabled(True)

How to implement an always-on-top widget that hides when losing focus

I need to implement a top-level widget (fixed position on screen) that hides whenever the user clicks somewhere else in the desktop, but it should hide gradually, so the widget should still be visible when it happens. To simplify, I want something like the Windows 8 right sidebar, when the user pushes a button, like the Super key it comes up, when clicking somewhere else it fades away, but is still visible in the process.
This is, I want to have an always-on-top window that hides when it loses focus. I have implemented this in pyqt4 but it is not working.
import sys
from PyQt4 import QtGui, QtCore
class Signals(QtCore.QObject):
close = QtCore.pyqtSignal()
class Menu(QtGui.QWidget):
def __init__(self, signals):
super(Menu, self).__init__()
self.signals = signals
def mousePressEvent(self, event):
# Just simplificating the gradual hiding effect for the moment
self.signals.close.emit()
def focusOutEvent(self, event):
print "FocusOut"
self.signals.close.emit()
def main():
app = QtGui.QApplication(sys.argv)
signals = Signals()
signals.close.connect(app.quit)
w = Menu(signals)
w.setWindowFlags( QtCore.Qt.SplashScreen )
w.resize(200, 200)
w.move(0, 0)
w.setWindowTitle('Test')
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
However, this is not working. When I click somewhere else the widget won't enter the focusOutEvent. I've also tried installing an eventFilter but since the window is a SplashScreen it won't work.
Any ideas on how to tackle this?

Why is my PyQt4 program exiting the main loop for no apparent reason?

I'm (trying to) make a small program that resides in the system tray and checks a list of Twitch channels to see if they're online every once in a while.
I'm currently doing the GUI (in PyQt4), but it's exiting for no reason.
Here's my code so far:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
class TwitchWatchTray(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
super(TwitchWatchTray, self).__init__(icon, parent)
self.menu = QtGui.QMenu(parent)
settings_action = self.menu.addAction("Settings")
settings_action.triggered.connect(self.open_settings)
self.menu.addSeparator()
exit_action = self.menu.addAction("Exit")
exit_action.triggered.connect(QtCore.QCoreApplication.instance().quit)
self.setContextMenu(self.menu)
self.show()
def open_settings(self):
settings = SettingsDialog()
settings.show()
class SettingsDialog(QtGui.QWidget):
def __init__(self):
super(SettingsDialog, self).__init__()
self.resize(300, 300)
self.setWindowTitle('TwitchWatch Settings')
vbox = QtGui.QHBoxLayout()
self.channels_list = QtGui.QListView(self)
vbox.addWidget(self.channels_list)
self.add_box = QtGui.QLineEdit(self)
vbox.addWidget(self.add_box)
self.setLayout(vbox)
self.show()
def main():
app = QtGui.QApplication(sys.argv)
widget = QtGui.QWidget()
tw = TwitchWatchTray(QtGui.QIcon("icon.png"), widget)
app.exec_()
print("Done!")
if __name__ == '__main__':
main()
When I right click the tray icon and click "Settings", it flashes a white box (my dialog), then immediately exits and prints "Done!".
Why is this, and how do I fix it?
There are two reasons why your code exits immediately after you open the settings dialog.
The first problem is with your open_settings method:
def open_settings(self):
settings = SettingsDialog()
settings.show()
This creates a dialog and makes it visible. show() returns immediately after showing the window; it doesn't wait for the window to be closed. The settings variable goes out of scope at the end of the method, and this causes the reference count of your SettingsDialog to drop to zero and hence become eligible for garbage collection. When Python deletes the SettingsDialog object, PyQt will delete the underlying C++ object, and this is what causes the dialog to close again.
I would recommend having your settings dialog subclass QDialog rather than QWidget (it is a dialog, after all). Instead of calling settings.show() you can then call settings.exec_(). settings.exec_() does wait for the dialog to be closed before it returns. It also returns QDialog.Accepted or QDialog.Rejected depending on whether the user clicked OK or Cancel. I'd also recommend getting rid of the call to self.show() in your SettingsDialog constructor.
The second problem is that your QApplication is set to quit when the last window is closed. This is the default behaviour, which is what a lot of applications need, but not yours. Even if your dialog stayed open and you could close it, you wouldn't want your application to exit immediately after you close the settings dialog. Call app.setQuitOnLastWindowClosed(False) to fix this.

Categories

Resources