Python PySide Event Handling - python

I don't quite understand event handling in PySide.
When I try something like:
exitAction.triggered.connect(self.close)
where exitAction is previously made QAction that will be used to exit program it works just fine. But how come when I make something like
newAction.triggered.connect(QMessageBox.information(self, 'New document', "New document is being created...", QMessageBox.Ok))
it doesn't work? It shows QMessageBox whenever I start program, not when I click that QAction on toolbar/menu bar. How would I go on to implement QMessageBox as a result of clicking on an QAction? Any help is greatly appreciated!

When you connect a signal you must specify the slot to which it is connected without actually calling the slot. Note that in exitAction.triggered.connect(self.close) there are no parentheses after close. If you would write exitAction.triggered.connect(self.close()), the close method would be executed when you connect the signal (at the program start-up). This what happens in your second example.
If there doesn't exist a slot yet that does exactly what you want, you will have to create it yourself (with the parameters that the signal expects). For example:
import sys
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication, QMessageBox
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.button = QPushButton('Click me!')
self.setCentralWidget(self.button)
self.button.clicked.connect(self.btnClicked)
def btnClicked(self):
QMessageBox.information(self, 'New document',
"New document is being created...", QMessageBox.Ok)
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec_()
Note that Qt Signals and Qt Events are not the same! (your title says events but your post is about signals). In general I would use signals and only use events if there is no signal for what you want to do.

Related

How to capture PyQt5 QMainWindow losing focus

What I want to achieve: if a user clicks outside of the QMainWindow the window should hide.
How I tried to to tackle this problem: find a way to determine if the QMainWindow lost focus, and if so, hide the window using a followup function.
Unfortunately I can not totally grasp how to achieve this.
It can be done using the flag Qt::Popup but than I am not able to give any keyboard input to the widget my QMainWindow contains.
void QApplication::focusChanged(QWidget *old, QWidget *now)
This signal is emitted when the widget that has keyboard focus changed from old to now, i.e., because the user pressed the tab-key, clicked into a widget or changed the active window. Both old and now can be the null-pointer.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class MyWin(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setFocus()
QtWidgets.qApp.focusChanged.connect(self.on_focusChanged)
#QtCore.pyqtSlot("QWidget*", "QWidget*")
def on_focusChanged(self, old, now):
if now == None:
print(f"\nwindow is the active window: {self.isActiveWindow()}")
# window lost focus
# do what you want
self.setWindowState(QtCore.Qt.WindowMinimized)
else: print(f"window is the active window: {self.isActiveWindow()}")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = MyWin()
MainWindow.show()
sys.exit(app.exec_())

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)

Problems with connect in pyqt5

I have a problem. I am writing a simple app in Pyqt5. I am trying to do this block of code in PyQt:
QNetworkAccessManager manager;
QNetworkReply *response = manager.get(QNetworkRequest(QUrl(url)));
QEventLoop event;
connect(response,SIGNAL(finished()),&event,SLOT(quit()));
event.exec();
QString html = response->readAll();
But when I am trying to use "connect" IDE tells me that "MainWindow" don't have method. How can I do it ?? Please help
This is my code:
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent = None):
super(MainWindow, self).__init__()
# window settings
self.setWindowTitle("Hello world app")
# main layout
self.lay = QtWidgets.QVBoxLayout()
# main widgets
self.label = QtWidgets.QLabel("Enter URL:")
self.line = QtWidgets.QLineEdit()
self.label_conn = QtWidgets.QLabel("")
self.btn = QtWidgets.QPushButton("Connect")
self.btn.clicked.connect(self.btn_click)
# adding widgets to layout
self.lay.addWidget(self.label, alignment=QtCore.Qt.AlignBottom)
self.lay.addWidget(self.line)
self.lay.addWidget(self.btn)
self.lay.addWidget(self.label_conn, alignment=QtCore.Qt.AlignTop | QtCore.Qt.AlignCenter)
self.setLayout(self.lay)
self.connect()
The connect method belongs to the signal that you wish to connect to a specific slot, not to the MainWindow widget itself. (BTW, you should consider inheriting from QMainWindow instead.)
In your code, the MainWindow widget is not a signal, so does not have a connect method. Also, even if it did, you need to specify the slot to which you're trying to connect the signal, which is also missing.
In other words, you must declare a pyqtSignal, if you're not using a pre-existing one, and then connect it to the pyqtSlot of your choice. Whether this slot is pre-defined or a custom one is up to you.
Consider the following code snippet, which I tested in Python3:
#!/usr/bin/python3 -B
import sys
from PyQt5.QtWidgets import QApplication, QDialog, QPushButton
if __name__ == '__main__':
app = QApplication(sys.argv)
diag = QDialog()
diag.setWindowTitle('Signal Demo')
diag.resize(200,50)
btn = QPushButton(diag)
btn.setText('Close Dialog')
# connect button's clicked signal to dialog's close slot
btn.clicked.connect(diag.close)
diag.show()
diag.exec_()
Notice that the button's clicked signal, not the button, is what gets connected to the dialog's close slot, not the dialog itself.
EDIT 1:
Just noticed that the very code you've posted has an example of how to properly perform a connection.
If your code has not simply been copy-pasted from some other place, you should've noticed that you seem to know how to properly connect signals and slots already. This line plainly gives it away:
self.btn.clicked.connect(self.btn_click)
If your MainWindow does have a btn_click method, then it should get invoked after the QPushButton named btn gets clicked.
EDIT 2:
Based on your recent comment, you seem to simply be trying to translate a snippet for a larger application, so consider the following code:
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt5.QtCore import QEventLoop, QUrl
app = QApplication(sys.argv)
url = 'https://stackoverflow.com'
manager = QNetworkAccessManager()
response = manager.get(QNetworkRequest(QUrl(url)))
event = QEventLoop()
response.finished.connect(event.quit)
event.exec()
html = str(response.readAll()) # in Python3 all strings are unicode, so QString is not defined
print(html)
The code above was tested to work as expected.
PS: I did notice that some seemingly valid URLs were returning an empty response (e.g. http://sourceforge.net/), but others, such as the one above, worked fine. It seems to be unrelated to the code snippet itself.

PyQt: ListWidget.insertItem not shown

I have a fairly simply PyQt question. (Python 3.4, PyQt 4.11.3, Qt 4.8.5) I built a very simple dialog using Qt Designer (Ui_Dialog). This object has a QPushButton, a QLineEdit, and a QListWidget. I wrote another object that inherits from Ui_Dialog, and sets up a returnPressed signal from QLineEdit that should add some text to the QListWidget. Unfortunately, this does not work.
Here's my code:
import sys
from PyQt4 import QtGui
from dialog import Ui_Dialog
class ImDialog(QtGui.QDialog, Ui_Dialog):
def __init__(self):
super(ImDialog, self).__init__()
self.setupUi(self)
self.lineEdit.returnPressed.connect(self.additem)
self.pushButton.clicked.connect(self.listWidget.clear)
def additem(self):
text = self.lineEdit.text()
print(text)
self.listWidget.insertItem(0, text)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
ui = ImDialog()
ui.show()
sys.exit(app.exec_())
The text in the line editor prints fine to the terminal, but it is not added to the listWidget.
Interestingly, if I comment out the sys.exit line and run this in an IPython terminal, I can add as much text as I like to the listWidget without a problem.
[In 1]: %run that_program.py
[In 2]: ui.listWidget.insertItem(0, "Test") # This works fine
If anyone has any suggestions to get this to work (outside IPython), I would appreciate the help. Thanks
There is only one button in your dialog, and so it will become the auto-default. This means that whenever you press enter in the dialog, the button will receive a press event, even if it doesn't currently have the keyboard focus.
So the item does get added to the list-widget - it's just that it then immediately gets cleared by the auto-default button.
To fix this, reset the auto-default like so:
self.pushButton.setAutoDefault(False)
(NB: you can also change this property in Qt Designer).

Query regarding Pyside

In the below mentioned example when I click on 'Help' submenu under 'View' menu multiple times its creating multiple windows. Can anyone tell me how to resolve this issue?
import sys
from PySide import Qt Gui
from PySide.QtCore import Qt
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.menu_bar()
def menu_bar(self):
helpAction = QtGui.QAction('&Help', self)
helpAction.setShortcut('Ctrl+H')
helpAction.triggered.connect(self.add_helpWindow)
menu = self.menuBar().addMenu('View')
menu.addAction(helpAction)
def add_helpWindow(self):
window = QtGui.QMainWindow(self)
window.setWindowTitle('New Window')
window.show()
if __name__ == '__main__':
import sys
app=QtGui.QApplication.instance()
if not app:
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(300, 300)
window.show()
sys.exit(app.exec_())
You help window is just a QMainWindow, which is not modal and there are no restrictions on the number that can exist. Hence why if you select the help option multiple times, you get multiple windows.
You likely want to use a QMessageBox which has its modal property set. While there is nothing forcing only one dialog to exist at a time, being modal means that the use can only interact with that window so long as it is open. Example:
from Pyside.QtGui import QMessageBox
def add_helpWindow(self):
help_dialog = QMessageBox.information(self, 'Help', 'Some Help Text Here')
help_dialog.setModal(True)
return help_dialog.exec_()
You can also get a more generic dialog box using QDialog, which is the parent class of QMessageBox.
If that's not the behavior you want, you'll need to manually track whether the user has opened that window before, and then connect a signal that is emitted when the user closes the help window to a slot that reset the existence tracker. Here is an example using a non-modal QDialog:
from Pyside.QtGui import QDialog
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.menu_bar()
self.help_open = False # Tracks if the help dialog is already open
def help_closed(self):
self.help_open = False
...
def add_helpWindow(self):
if not self.help_open:
self.help_open = True
help_dialog = QDialog(self)
# Any other setup code here
help_dialog.setModal(False)
help_dialog.accepted.connect(self.help_closed)
help_dialog.rejected.connect(self.help_closed)
help_dialog.show()

Categories

Resources