PyQt5 QWidget code uses un-initialized variable - python

I am following a tutorial on zetcode.com and I seem to have run into trouble. This code is supposed to be very straight forward and so I pasted it below.
This is all a part of an instance of the QWidget class (the base class of all UI objects). I have realized that this is one of the fundamental classes I need to understand in order to write a GUI, and I am simply puzzled on what is going on in the program.
The program is simple enough: PyQt opens a window, you are then able to exit out of that window via the 'x' button. And upon clicking the 'x' a message inquiring "Are you sure to quit?" allows you to continue to exit or cancel.
import sys
from PyQt5.QtWidgets import QWidget, QMessageBox, QApplication
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Message box')
self.show()
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes |
QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
So what doesn't make sense?
The closeEvent() method that QWidget calls. The method seemingly accepts a variable "event" that is never initialized but somehow passed into the function. The methods "event.accept()" and "event.ignore()" are then called on an object that previously was never initialized.
I am a noob in PyQt/Qt and maybe it's a misunderstanding of Python too. Here is documentation on the function http://doc.qt.io/qt-5/qwidget.html#closeEvent, that may clarify things.

The method seemingly accepts a variable "event" that is never
initialized but somehow passed into the function.
That is how a method works. Consider this simple function:
def print_a_word(word):
print(word)
It takes an argument word, that we did not initialized. When you call the function, then you need to define what word is:
word = "unicorn"
print_a_word(word)
If you look at the doc in more details, you'll see that event is a QCloseEvent, and it is "initialized" somewhere else in QWidget
The QCloseEvent class contains parameters that describe a close event.
Close events are sent to widgets that the user wants to close, usually
by choosing "Close" from the window menu, or by clicking the X title
bar button. They are also sent when you call QWidget::close() to close
a widget programmatically.

Related

QPushButton shortcut does not work properly

In this simple pyqt5 app, I have a QPushButton and I define a shortcut for it. I want to change its text every time it is triggered. Problem is that the shortcut works only for the first time. How can I fix it?
from PyQt5.QtWidgets import QPushButton, QMainWindow, QApplication
import sys
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.btn = QPushButton('&Connect', self)
self.btn.setShortcut('Ctrl+C')
self.btn.pressed.connect(self.btn_func)
def btn_func(self):
if self.btn.text() == '&Connect':
self.btn.setText('Dis&connect')
else:
self.btn.setText('&Connect')
def main():
app = QApplication(sys.argv)
win = window()
win.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
As explained in the text property documentation [emphasis mine]:
If the text contains an ampersand character ('&'), a shortcut is automatically created for it. [...] Any previous shortcut will be overwritten or cleared if no shortcut is defined by the text
I know that the above might seem confusing, as it seems that the shortcut is overwritten or cleared only if no shortcut is defined by the text, consider it like this:
Any previous shortcut will be overwritten, or it is cleared if no shortcut is defined by the text
The solution is to always reset the shortcut after setting the new text:
def btn_func(self):
if self.btn.text() == '&Connect':
self.btn.setText('Dis&connect')
else:
self.btn.setText('&Connect')
self.btn.setShortcut('Ctrl+C')
Note that using the button's text for comparison is considered bad practice, for three reasons:
the text of a button could (should) be localized;
you could easily forget to correctly update all the & texts, making the function behave in the wrong way;
some QStyles override existing mnemonics and change them by themselves, which also causes the text to change without any warning;
The most preferred way to achieve what you want would be to use an internal flag for the current state, and also a QAction with its own shortcut.
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.btn = QPushButton('&Connect', self)
self.btn.clicked.connect(self.btn_func)
self.connectAction = QAction('Toggle connection', self)
self.connectAction.setShortcut('Ctrl+c')
self.connectAction.triggered.connect(self.btn.animateClick)
self.addAction(self.connectAction)
self.connected = False
def btn_func(self):
self.connected = not self.connected
if self.connected:
self.btn.setText('Dis&connect')
else:
self.btn.setText('&Connect')
Also note that:
you should not use the pressed() signal, as it's standard convention to consider a button clicked when the user presses and releases the mouse button while inside the button area (so that the pressed action could be "undone" by moving the mouse away if the mouse button was pressed by mistake on the button); use the clicked() signal instead;
I changed the class name using a capitalized Window, as classes should always use capitalized names and lower cased names should only be used for functions and variables;
I used the animateClick slot to show that the button was clicked (as a visual feedback is always preferable), but you can directly connect to the function: self.connectAction.triggered.connect(self.btn_func);

Qt canonical way of retrieving values from Wizard / Dialog on accepted / finished signal

I'm using PyQt, but I guess the same questions also applies to Qt C++.
Assume that I have a main window with a button that opens a wizard that collects data and that data needs to be used in the main window after the wizard has closed. standard procedure.
So there are multiple ways to do this. either I can pass a reference to the main window to the Wizard and it does all the work using the main window reference, but I'd say that breaks modularity. I can also wire up a callback to the wizard accepted rejected or finished signal, but in that callback, I don't have a reference to the wizard itself, so I cannot get to the data in the wizards fields. Unless I store a reference to the wizard as instance variable in order to access it again from the callback.
Another option is (even though I haven't fully understood it yet) to get a reference to the emitter of the signal (i.e. the wizard) in the callback using https://doc.qt.io/qt-5/qobject.html#sender. But that seems not recommended.
So whats the canonical way?
Premise: this is a bit of an opinion based question, as there is not one and only "good" way to do that. I just wanted to comment (opinion based answer/questions are discouraged here in SO), but the limited formatting isn't very helpful.
"Passing a reference" doesn't necessarily break modularity.
Instead, that's exactly what QDialog usually are initialized: the parent is the "calling" window, which is also how a QDialog can be "modal" to the parent or the whole application (meaning that no interaction outside the dialog is allowed as long as it is active).
AFAIK, I don't know if this is actually considered canonical, but the following is the most commonly suggested approach.
The idea is that you have a child object (a QDialog, usually) which might or might not be initialized everytime you need it, that's up to you; the important part is that you need a reference to it at least for the time required to update its result, which can even happen within the scope of a single method/slot.
from PyQt5 import QtWidgets
class MyWizard(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self.checkBox = QtWidgets.QCheckBox('check')
layout.addWidget(self.checkBox)
self.input = QtWidgets.QLineEdit()
layout.addWidget(self.input)
buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel)
layout.addWidget(buttonBox)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
def setData(self, **data):
self.checkBox.setChecked(data.get('check', False))
self.input.setText(data.get('text', ''))
def getData(self):
return {'check': self.checkBox.isChecked(), 'text': self.input.text()}
def exec_(self, **data):
self.setData(**data)
return super().exec_()
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
centralWidget = QtWidgets.QWidget()
self.setCentralWidget(centralWidget)
layout = QtWidgets.QHBoxLayout()
centralWidget.setLayout(layout)
self.showWizBtn = QtWidgets.QPushButton('Show wizard')
layout.addWidget(self.showWizBtn)
self.showWizBtn.clicked.connect(self.getDataFromWizard)
self.data = {}
def getDataFromWizard(self):
wiz = MyWizard(self)
if wiz.exec_(**self.data):
self.data.update(wiz.getData())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
Another possibility is to create a persistent child dialog (but keep in mind that if the data can be changed by the parent, you'll have to find a way to update it, at least when executed); the concept here is that you can exec the dialog whenever you need, and you have the accepted signal connected to a slot that can get the data from the dialog. This is not a common use (nor very suggested IMHO) and should be used only for very specific scenarios.
As you already found out, using sender is not suggested: signals are asynchronous, and while in normal conditions the sender is reliable it's better to avoid using it unless absolutely necessary.

What effect will exist when using any 'return' statement inside Qt's slot

For instance, I connect the 'clicked' signal of QPushButton to a function named 'func_with_return'. Assumes that there are just three statements in this function: The first one is 'print('start')', the second one is 'return 1' and the last one is 'print('end')'. There is my python code based on PyQt5.
import sys
from PyQt5.QtWidgets import QApplication, QFrame, QPushButton
class MyWindow(QFrame):
def __init__(self):
super(MyWindow, self).__init__()
self.layout_init()
self.layout_manage()
def layout_init(self):
self.setFixedSize(800, 600)
self.button01 = QPushButton('click!', self)
self.button01.setFixedSize(100, 100)
self.button01.clicked.connect(self.func_with_return)
def layout_manage(self):
pass
def func_with_return(self):
print('---------func_with_return starts---------')
return 1
print('---------func_with_return ends---------')
if __name__ == '__main__':
app = QApplication(sys.argv)
mywindow = MyWindow()
mywindow.show()
sys.exit(app.exec_())
Basically, there is no error after clicking on this button. What I am curious about is the interruption caused by 'return' inside a 'slot'. Will this interruption have collision with the signal&slot mechanism?
None. The signals only invoke the function, if the function returns Qt will not use it.
On the other hand in Qt/PyQt it is said that a function is a slot if you use the decorator #QtCore.pyqtSlot(). In your case it is a simple function. Even so for a signal will not serve the data that returns the slot or function invoked.
Will this interruption have collision with the signal&slot mechanism?
No, it does not have a collision. Returning to the beginning, middle or end is irrelevant, remember every function returns something (if you do not use return the function will implicitly return None at the end).
On the other hand in a GUI the tasks of the functions must be light, if they are heavy you must execute it in another thread.

Python Gtk 3 window is not defined, class instancing confusion

I am just starting to get into python and I am utterly confused as to how object creation works. I am trying to create user interface with GTK. Here is an example of the problem I am having:
from gi.repository import Gtk
def button_clicked(self, button):
self.button_label = button.get_label()
if self.button_label == "Login":
window.quit()
window2.start()
class LoginWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="AMOK Cloud")
self.connect("delete-event", Gtk.main_quit)
self.set_position(position = Gtk.WindowPosition.CENTER)
# Button
self.loginbutton = Gtk.Button(label="Login")
self.loginbutton.connect("clicked", button_clicked(self, self.loginbutton))
self.add(self.loginbutton)
self.show_all()
Gtk.main()
def quit(self):
self.close()
Gtk.main_quit()
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="AMOK Cloud")
self.connect("delete-event", Gtk.main_quit)
self.set_position(position=Gtk.WindowPosition.CENTER)
def start(self):
self.show_all()
Gtk.main()
window = LoginWindow()
window2 = MainWindow()
Error comes up as NameError: name 'window' is not defined
even though I did define window. I don't understand. If someone can explain it would mean the world to me. Thanks in advance.
EDIT:
Thanks guys this works fine now, took Gtk.main() out of both classes and added button_clicked method inside LoginWindow() class and now it works like a charm. I assumed I needed Gtk.main() for every window I open.
It is because you are starting the main loop (Gtk.main()) inside of LoginWindow.__init__(). That means that the window = LoginWindow() line doesn't finish executing until after the login window is closed. You should take Gtk.main() outside of the __init__ method, and move it to the last line in the file. As mentioned by PM 2Ring in the comments, you don't need to call Gtk.main() twice. Take it out completely in MainWindow.start() because the one now added to the last line in the file takes care of that. Also mentioned by PM, connect() calls a function when an event happens. When you give it button_clicked(...), that function is called and you are actually telling connect() to call whatever is returned, None. If you want special arguments, use a lambda, but you aren't even changing anything (those are the default arguments anyway), so you can simply do this:
self.connect("clicked", button_clicked)
I would also suggest that instead of making button_clicked a separate function, make it a static method of the class. You do that by placing it inside the class, but with #staticmethod just above the def line. That way, it makes sense for it to take the self argument, but you don't need two parameters to account for the same window.

Closing a QWidget opened from a QMainWindow

I need to show a QWidget, which code is written in another module, when a certain button is pressed. To accomplish this, I wrote this code:
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
#A lot of stuff in here
#The button is connected to the method called Serial_connection
self.connect(self.btn_selection_tool3, SIGNAL("clicked()"), self.Serial_connection)
def Serial_connection(self):
LiveData.LiveData(self).show()
Doing this, I open a QWidget and it works fine. But, when I want to close this QWidget, I can not do it. This is the code of the QWidget:
class LiveData(QWidget):
def __init__(self,parent = None):
super(QWidget, self).__init__(parent)
#Another stuff in here
#I create a "close" button connected to another method
self.connect(self.closeBtn, QtCore.SIGNAL("clicked()"), self.StopAndClose)
def StopAndClose(self):
print "Closing window"
self.close() #HERE IS WHERE I HAVE THE PROBLEM
I´ve tried several options like: self.close(), self.accept() or even sys.exit(1). The problem with the latter sys.exit(1) is that it closes the QWidget and the QMainWindow. So, how can I close this QWidget only? Hope you can help me.
You probably want your QWidget to be a QDialog. If it's a temporary modal widget, you should be calling the dialog like this
dialog = LiveData.LiveData(self)
dialog.exec_()
If you just want to show the dialog at the same time as your main window, and users are meant to interact with both (though from a design perspective, this doesn't sound like a great idea), you can continue to use .show()
Also, you should use the new-style signal/slot syntax. The old syntax hasn't been used for many years.
self.closeButton.clicked.connect(self.StopAndClose)
Though, for a QDialog you can just do
self.closeButton.clicked.connect(self.accept)

Categories

Resources