I'm working on a project using Python and Tkinter. I want to modularize it.
One of the main problems is that the implementation of my Toplevel widget is too big.
I heard that it's possible to put this widget in a new class. The problem is I don't know how.
Here is how I define my main window:
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
Config(self)
So for my Toplevel widget I tried:
class Config(tk.Toplevel):
def __init__(self, main):
tk.Toplevel.__init__(self)
Is it the right way to do this ?
Yes, that is the right way to do it. Though, you might want to keep a reference to the window so you can call methods on it later:
self.config = Config(self)
Related
Forgive me for overcomplicating this, thats just how it happened. so i have these two classes one's windowm and the other's modelm, i am trying to get it to re-start whenever newGame() is called so here are some snippets of code:
class windowm(QMainWindow):
def __init__(self):
super(windowm, self).__init__()
# a generic widget for the center of the window
widget = QWidget()
self.setCentralWidget(widget)
and the other class:
class Model:
def __init__(self):
# setup is just starting a new game, so use that method
self.newGame()
def newGame(self):
super(windowm, self).__init__()
yes i understand it is complicated and forgive me for that, that's just how the assignment is. So i understand that this has been answered before i just have this one annoying unique scenario. so as you can see in the second code snippet i am attempting to get it to jump back into the class "windowM" and into the function init(self) to re-start the game. please help and thank you!
You'll have to create a new instance of your other class and then use that to call the game function.
I think you'll want to change your game class though, so it's easier to start a new game from the other class.
class Model:
def startNewGame(self):
# setup is just starting a new game, so use that method
self.newGame()
def newGame(self):
super(windowm, self).__init__()
Then it can be used like this:
class windowm(QMainWindow):
def __init__(self):
super(windowm, self).__init__()
# a generic widget for the center of the window
widget = QWidget()
self.setCentralWidget(widget)
# create an instance of the other class
ng = Model()
# start a new game
ng.startNewGame()
How do I create GUI classes for windows which have subwindows/classes that can access the main GUI's functions?
I have the below code which modifies the compiled .ui code from designer. What I want it to do is, when clicking the top-right "X", or using File -> Exit function, to close the window comprising the Window_SecondWindow class, and show the main window again--effectively calling the main window's show() from the subclass. I want to show only one window at a time.
When the code is run as-is, the Window_SecondWindow class hides, but immediately shows again, leading me to believe super is acting as self.
from PyQt5 import QtWidgets
from GUI import compiled_MainWindow
from GUI import compiled_SecondWindow
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.ui = compiled_MainWindow.Ui_MainWindow()
self.ui.setupUi(self)
self.ui.closeEvent = self.clicked_EXIT
# connect widgets
self.ui.Btn.clicked.connect(self.clicked_Btn)
self.ui.actionExit.triggered.connect(self.clicked_EXIT)
# add windows
self.SecondWindow = SecondWindow()
# more windows attached to main window
def clicked_Btn(self):
self.hide()
self.SecondWindow.show()
def clicked_EXIT(self):
self.close()
class Window_SecondWindow(Window_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.ui = compiled_SecondWindow.Ui_MainWindow()
self.ui.setupUi(self)
self.ui.closeEvent = self.clicked_EXIT
self.ui.actionExit.triggered.connect(self.clicked_EXIT)
def clicked_EXIT(self):
self.hide()
super().show()
Before answering your question, I'd like to address some important aspects.
First of all, never edit the generated code from pyuic to create your programs. They are intended to be used as imported modules, mostly as "resources": you import and integrate them into your code, but you should always leave them as they are. See the documentation on using Designer for more insight about this.
Be careful in overriding functions within the __init__: some functions are not "virtual" (thus, cannot be overwritten in such a way) and in some cases Qt always calls the base class function name anyway; just overwrite the method and call the base class implementation with super() if required. Also, closeEvent has the close event as a mandatory argument, and you have to add that to your overridden function, even if you don't use it (in the following examples I'm just using *args). That said, you should never use an overridden function as a slot that has a different argument, or viceversa.
Finally, you should not use capitalized names for attribute and variable names, as it is confusing and prone to errors (capitalization is mostly used for class names only, not their instancies).
Now, the answer
You are almost right, super() acts "as self", in the sense that it just calls the inherited show() method of the class against the instance. So, it calls the show method of Window_MainWindow, but since the instance is the second window, it's the same as doing Window_MainWindow.show(self), with self being the Window_SecondWindow instance; it is exactly as doing self.show().
There are two (and a half) possibilities.
The first, more obvious solution, is to give a reference of the main window instance to the second one:
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.secondWindow = Window_SecondWindow()
self.secondWindow.mainWindow = self
class Window_SecondWindow(Window_MainWindow):
# ...
def clicked_EXIT(self, *args):
self.hide()
self.mainWindow.show()
Be aware that while, as #noras points out in the comment, you could set the main window as a parent in the init argument, but this only works as expected with QMainWindow and QDialog descendants; if the child widget is of any other kind, it will be shown inside the parent, not as a separate window.
The second (and more "Qt-wise correct") is to create a signal for the second class that is emitted when it's closed, and connect it in the main window so that it's shown again when that happens:
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.secondWindow = Window_SecondWindow()
self.secondWindow.closed.connect(self.show)
class Window_SecondWindow(Window_MainWindow):
closed = QtCore.pyqtSignal()
def clicked_EXIT(self, *args):
self.hide()
self.closed.emit()
The second-and-a-half solution is to use an event filter:
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.secondWindow = Window_SecondWindow()
self.secondWindow.installEventFilter(self)
def eventFilter(self, source, event):
if source == self.secondWindow and event.type() == QtCore.QEvent.Close:
self.show()
return super().eventFilter(source, event)
so I was wondering how I could make a subclass of a widget
For example if I wanted to create a widget, that inherited methods and attributes from QtWidgets.QPushButton, however I would create extra methods and attributes on top of that.
class Coord(QtWidgets.QPushButton):
def __init__(self):
super(Coord, self).__init__()
self.coordinates = []
#basically adding attributes to the object "QPushButton"
def set_text(self,text):
self.setText(text)
chrcount = 100 / len(text)
self.setStyleSheet("font-size: {}".format(chrcount))
#This will set the text of the button, yet will resize it appropriatly
This is an example. However, it creates the "button" widget as a new window. I was wondering how I could get it to act like QPushButton would anyway, just with the extra features I'd like to add to it
Edit: Fixed-
replaced my "super" function from
def __init__(self):
super(Coord, self).__init__()
to
def __init__(self,parent):
super(Coord, self).__init__(parent)
Don't really know how that fixed it but hey ho!
you can use the qt designer to create a button and set all possible features of a button there. If that is not enough you can adjust the button in your coding like self.button.whatever.set....If that is not enough attache the button to a class in qt designer, create a module and a class and do adjust whatever you want.
I have a simple graphical user interface using tkinter which gives a blank canvas on which the cursor places a green square wherever it is clicked.
I am trying to code in a "Clear all" button at the top of the interface which clears the entire canvas (whilst retaining the cursor's ability to draw squares when it clicks on the canvas again):
import tkinter as tk
class Interface(tk.Frame):
def __init__(self, master):
super().__init__(master)
tk.Button(self, text="Clear all", command=self.clearall).pack(side = tk.LEFT)
#What the heck do I do here?
def clearall(self):
#self._canvas.delete(tk.ALL)
pass
class App(object):
def __init__(self, master):
master.title("Doodle test")
master.geometry("600x600")
self._interface = Interface(master)
self._interface.pack()
self._canvas = tk.Canvas(master, bg='white')
self._canvas.pack(fill=tk.BOTH, expand=1)
self._canvas.bind("<Button-1>", self.leftclick)
# And would I need to put something here?
def plz_run_me_when_clearall_method_is_called(self):
self._canvas.delete(tk.ALL)
def leftclick(self, arg):
d = 10
self._canvas.create_rectangle([(arg.x, arg.y), (arg.x+d, arg.y+d)], fill="green")
root = tk.Tk()
app = App(root)
root.mainloop()
Ordinarily, this would be a simple task if the entire program was constructed within a single class, e.g.:
def clearall(self):
self._canvas.delete(tk.ALL)
Since there are two classes, I cannot call the .delete(tk.ALL) method for _canvas since it is declared in the App class. So how could I convey a button to execute function X which affects item Y from another class?
EDIT: Still trying to think of an appropriate title for this post.
A simple rule of python (not just tkinter) is that, if an object needs a resource in another object, that first object needs a reference to the second object.
That means either passing in the instance of App to the instance of Interface, or passing in the canvas itself.
Using tight coupling
For example, you could pass in the canvas this way:
class Interface(tk.Frame):
def __init__(self, master, canvas):
...
self._canvas = canvas
...
def clearall(self):
self._canvas.delete(tk.ALL)
class App(object):
def __init__(self, master):
...
self._canvas = tk.Canvas(master, bg='white')
self._interface = Interface(master, self._canvas)
...
There are many ways to accomplish a similar thing. For instance, instead of passing in the canvas you can pass in the reference to the app itself (eg: self._interface = Interface(master, self). You could then either makeself._canvas` "public", or you could provide a getter that the returns the canvas.
The potential downside to the above approach is that it tightly couples the two classes together. That is, if App some day is remade to draw on an image object rather than a canvas object, Interface will also have to be modified since it depends on a specific implementation in App (namely, that it does all its work on a canvas).
Using loose coupling
Loose coupling means that Interface doesn't know or care how App is implemented, it only knows that App promises to provide an API that Interface can use. In this way, App can completely change its implementation without breaking Interface, as long as it continues to provide the same API.
To keep these classes loosely coupled, you need to move add a clearall method into App. With this, Interface doesn't need to know that App uses a canvas, it just needs to know that App provides a way to clear the drawing surface, no matter what that drawing surface is.
class Interface(tk.Frame):
def __init__(self, master, app):
...
self._app = app
...
def clearall(self):
self._app.clearall()
class App(object):
def __init__(self, master):
...
self._canvas = tk.Canvas(master, bg='white')
self._interface = Interface(master, self)
...
def clearall(self):
self._calvas.delete(tk.ALL)
I'm working on a Gui and I'd like to know how to create a class that would implement frame.
e.g.
class WindowContent(Tkinter.?)
""" This class would create a frame for my program window """
class App(Tkinter.Tk):
""" main window constructor """
def __init__(self):
Tkinter.Tk.__init__(self)
program_window = WindowContent ?
self.config(window = window_content) ?
rgds,
I found the answer :
class WindowProgram(Tkinter.Frame)
""" This class creates a frame for my program window """
def __init__(self, parent):
Tkinter.Frame.__init__(self, parent)
class App(Tkinter.Tk):
""" application constructor """
def __init__(self):
Tkinter.Tk.__init__(self)
self.window_program = Window_Program(self)
Why do you want a class that creates several frames? Creating one class that creates multiple frames is not a very good solution. You don't need a single class for that.
Either create separate classes for each frame, or just create methods in your app to create each frame. I prefer the latter, but if you want a frame that can be used in multiple contexts it sometimes makes sense to create a class.
When I do a GUI I structure my code like this:
class App(Tkinter.Tk):
def __init__(self):
Tkinter.Tk__init__(self)
self.menubar = self.makeMenubar(...)
self.frame1 = self.makeFrame1(...)
self.frame2 = self.makeFrame2(...)
self.configure(menu=self.menubar)
self.frame1.grid(...)
self.frame2.grid(...)
In this way, each major section gets its own method to hide the details of widget creation. You could, of course, have each frame be a custom object but usually that's unnecessary.