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.
Related
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);
Problem
I am building in app (with Kivy) which renders some custom widgets to the screen. Unfortunately, their positions (relative to other widgets) changes when the tablet on which I am running the app is rotated. Now I have built a function that repositions them, and I would like to call this function when the tablet is rotated. How can I implement this?
My attempts so far...
I have seen from the Kivy documentation that the Window class has an on_rotate event, and have tried to incorporate this into my program in several different ways.
First, I tried implementing the callback after the if __name__ == '__main__' statement. Something like:
if __name__ == '__main__':
app = MainApp()
Window.on_rotate = lambda: app.reposition_widgets()
app.run()
That did nothing. In vain I also tried:
if __name__ == '__main__':
app = MainApp()
Window.on_rotate(app.reposition_widgets)
app.run()
I then attempted to bind the widgets to on_rotate events, something like:
class CustomWidget(Widget):
def __init__(self, **kwargs)
super().__init__(**kwargs)
self.bind(on_rotate = self.reposition_widgets)
That did nothing. Neither did:
class CustomWidget(Widget):
def __init__(self, **kwargs)
super().__init__(**kwargs)
on_rotate = reposition_widgets
My final attempt was to create a Window class in the accompanying kv file and the Python file, then specify the on_rotate event from there. Something like:
#kv file
Window:
on_rotate: app.reposition_widgets()
That didn't work either.
Possible work around
To be honest, rotating the screen is not all that useful for my app, and so it wouldn't be all that bad if I just disabled screen rotation. However, I was not able to find a good way of doing so in the documentation. Do you know how I would go about doing this?
Binding the widgets to the on_pos event - or something similar. Something like:
class CustomWidget:
def reposition_widgets():
# Code here
on_pos = reposition_widgets
The problem with this is that the widgets move about a lot, which means that the function gets called a lot - causing problems elsewhere.
Let me know if you would like to see more code. I have over 1000 lines spread over several files, and felt that just copy-and-pasting wouldn't be particularly useful.
I was able to solve this issue using the on_size event.
class CustomWidget(Widget):
def reposition_widgets(self):
# Reposition logic here
on_size = reposition_widgets
This worked quite well actually. I added an additional parameter in the reposition_widgets function to differentiate between when I was repositioning because of on_size and when I was repositioning because I was calling it (see example below). However, this is not always necessary.
class CustomWidget(Widget):
def reposition_widgets(self, rotation=False):
if rotation:
# When function is called due to on_size
else:
# When function is called in code
on_size = lambda self, *args: self.reposition_widgets(rotation=True)
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.
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.
I show Second QMainWindow after click on button in parent QMainWindow
def on_click(self):
window = second_window.MainWindow()
window.show()
Second window not shown (Without any error). But if in Second window I add line:
self.func = functools.partial(self.some_func)
All work correct.
Why it's happens?
I think the problem here is that you are creating the window as a local variable inside the on_click scope. As soon as on_click finishes the window attribute will be destroyed.
Try storing the window in an instance variable:
def on_click(self):
self._window = second_window.MainWindow()
self._window.show()
The functools.partial approach is probably working just because you are already storing it at the instance.