I’m trying to master tkinter in Python-3. I read some tutorials and have a working window made of 4 classes. (MenuBar class, ToolBar class, Main class and StatusBar class). These classes are instantiated from one main class (MainWindow).
I can run the script with no errors, but I’m stuck with the interaction between the objects. I would like to run a method from the 'main' object when I select something in the 'menubar' object. What’s the correct way to do this, or should I change the architecture of the script to make this possible? - Lets say I want to execute change_main from within the menubar.
I have been looking at static and class methods, but I don't know how to access the instances. I would like to be able to access the 'main' instance from within the 'menubar' instance. All instances are created in the MainWindow class.
EDITED: CODE UPDATED
I added a controller method in the MainWindow class and passed the mainwindow object to the menubar object. In the menubar object the controller method is called by command=controller.controller_test as mentioned by stovfl. The controller_test method calls the change_main method in the main object. Directly calling parent.main.change_main did not work as the main object is instantiated after the menubar object.
import tkinter as tk
class MainWindow(tk.Frame):
def __init__(self, parent):
parent.title("Application Title")
parent.geometry("500x400")
self.menubar = MenuBar(parent, controller=self)
self.toolbar = ToolBar(parent)
self.main = Main(parent)
self.statusbar = StatusBar(parent, 'status shown here...')
def controller_test(self):
self.main.change_main()
class MenuBar(tk.Frame):
def __init__(self, parent, controller=None):
menu = tk.Menu(parent)
parent.config(menu=menu)
submenu = tk.Menu(menu, tearoff=False)
menu.add_cascade(label='File', menu=submenu)
submenu.add_command(label='Test', command=controller.controller_test)
class ToolBar(tk.Frame):
def __init__(self, parent):…
class StatusBar(tk.Frame):
def __init__(self, parent, message=''):…
class Main(tk.Frame):
def __init__(self, parent):
self.main = tk.Frame(parent, bg='black')
self.main.pack(fill='both', expand=True)
label.pack()
def change_main(self, event=None):
self.main.pack_forget()
self.main = tk.Frame(self.parent, bg='blue')
self.main.pack(fill='both', expand=True)
if __name__ == '__main__':
root = tk.Tk()
MainWindow(root)
root.mainloop()
Related
So I heard there was a method in which you could make a separate tk.Toplevel class I have been attempting to use it but I dont know exactly whats wrong I really need help
here is the code as of now I will keep trying to make adjustments but its not easy
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self, title: str):
super().__init__()
self.title(title)
self.label = ttk.Label(self, text="Hello, World!")
self.label.pack(side=tk.TOP)
self.button = ttk.Button(self, text="Button", command=self.Alter)
self.button.pack(side=tk.RIGHT)
class ThemeWindow(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("NEW")
self.parent = parent
self.label = tk.Label(parent, text="Hello, World!")
self.label.pack(side=tk.TOP)
self.button = tk.Button(parent, text="Button", command=parent.Alter)
self.button.pack(side=tk.RIGHT)
I have edited and used examples given in a course I am taking but clearly I am misreading.
The key part is AttributeError: '_tkinter.tkapp' object has no attribute 'Alter'' - that's telling you that your App class (which inherits from Tk) has no attribute called Alter.
The problem is caused by your App class, and more specifically by self.button = ttk.Button(self, text="Button", command=self.Alter)
You're telling self.button to call the function self.Alter as its callback function (the command parameter), but you haven't defined a function called Alter within the App class.
You need to do something like this:
class App(tk.Tk):
def __init__(self, title: str):
super().__init__()
self.title(title)
self.label = ttk.Label(self, text="Hello, World!")
self.label.pack(side=tk.TOP)
self.button = ttk.Button(
self,
text="Button",
command=self.alter # don't use uppercase in function names!
)
self.button.pack(side=tk.RIGHT)
def alter(self): # define a function 'alter' within this class
print('Hello') # do whatever you need this function to do here
Now when button is pressed, it will be able to call alter, which (in this case) should print "Hello"
Likewise, your ThemeWindow class should be able to call this via parent.alter
Again, note that I've changed the name from "Alter" to "alter" to adhere to Python naming conventions.
In my program, I am creating a window from my root tkinter window, and hiding the root using the .withdraw() function. When I try to show the root window again by calling the root class, it does not show and my program exits. Here's a rough outline of my code describing the problem:
class MainGUI:
def __init__(self, master):
self.master = master
#....Create and .grid() all GUI Widgets....
# Button for switching to other window
button = Button(text="CLICKME", command=lambda: self.other_window())
# Call and define show function at the end of __init__
self.show()
def show(self):
self.master.update()
self.master.deiconify()
# Create other window and withdraw self on button click
def other_window(self):
OtherGUI(self.master)
self.master.withdraw()
class OtherGUI:
def __init__(self, master):
# Function for returning to main window, calls MainGUI class
# to create window and withdraws self.
def main_window():
MainGUI(self.master)
self.master.withdraw()
master = self.master = Toplevel(master)
#....Create and .grid() all GUI Widgets....
# Button for switching back to main window
button = Button(text="CLICKME", command=lambda: self.main_window())
Using print functions in the MainGUI, I was able to see that when trying to switch back to the main window, show() is actually called, and the entire class does appear to be entered.
This puzzles me as I've only really learn how to do this from other forum posts, and using root.update() and .deiconify() seemed to be the solution for most people, however I have no idea why this isn't working.
Does anyone have an idea as to where I'm going wrong here?
The example you presented will not work for several reason.
#really you should build your gui as an inherited class as it makes things much easier to manage in tkinter.
class MainGUI:
def __init__(self, master):
self.master = master
button = Button(text="CLICKME", command=lambda: self.other_window())
# no need for lambda expressions here.
# missing geometry layout... grid(), pack() or place()
self.show()
# self.show does nothing here because your show method is improperly indented.
# your other_window method is also not properly indented.
def show(self):
self.master.update()
self.master.deiconify()
def other_window(self):
OtherGUI(self.master)
self.master.withdraw()
class OtherGUI:
def __init__(self, master):
# this function should be its own method.
def main_window():
MainGUI(self.master)
self.master.withdraw()
master = self.master = Toplevel(master)
# this is not how you should be defining master.
button = Button(text="CLICKME", command=lambda: self.main_window())
# missing geometry layout... grid(), pack() or place()
# your button command is using a lambda to call a class method but your define it as a function instead.
Here is a simpler version of what you are attempting that will work:
import tkinter as tk
class MainGUI(tk.Tk):
def __init__(self):
super().__init__()
tk.Button(self, text="Open Toplevel", command=self.open_toplevel_window).pack()
def open_toplevel_window(self):
OtherGUI(self)
self.withdraw()
class OtherGUI(tk.Toplevel):
def __init__(self, master):
super().__init__()
tk.Button(self, text="Close top and deiconify main", command=self.main_window).pack()
def main_window(self):
self.master.deiconify()
self.destroy()
MainGUI().mainloop()
As you can see here when you inherit from the tkinter classes that control the main window and toplevel windows it becomes easier to manage them and less code to perform a task.
I'm trying to create a tkinter GUI application while organizing my code into classes for the main application, the frame, and the menu. For the menu, I'm subclassing tkinter.menu, but it's giving me a bunch of default menus and not including the menus and commands I've created. I've stripped it down as best I can to the following working example that illustrates the problem:
First I subclassed tkinter.TK to create the main widget, containing the main window and the menu. For illustration, I included one empty method to add as a command in the File menu. Next, I subclassed tkinter.Frame to create the main frame. For illustration, I added a text box to the frame. Finally, I subclassed tkinter.Menu to create the main menu and added the submenu "File" and a command "Open".
import tkinter
class Application(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
self.main = MainWindow(master=self)
self.menu = MainMenu(master=self)
self.main.pack(fill=tkinter.BOTH, expand=True)
def open(self):
pass
class MainWindow(tkinter.Frame):
def __init__(self, master):
tkinter.Frame.__init__(self)
self.master = master
self.textbox = tkinter.Text()
self.textbox.pack(fill=tkinter.BOTH, expand=True)
class MainMenu(tkinter.Menu):
def __init__(self, master=None):
tkinter.Menu.__init__(self, master=None)
self.master = master
self.file = tkinter.Menu(self)
self.add_cascade(label="File", menu=self.file)
self.file.add_command(label="Open", command=master.open)
if __name__ == "__main__":
Application().mainloop()
The result is a bunch of menus I didn't create, including a File menu without my "Open" command:
Here's a screen cap of the results.
Where am I going wrong?
Replace:
self.menu = MainMenu(master=self)
with:
self['menu'] = MainMenu(master=self)
# or self["menu"] = MainMenu(master=self)
# or self.config(menu=MainMenu(master=self))
# or self.configure(menu=MainMenu(master=self))
or add:
self['menu'] = self.menu
# or self["menu"] = self.menu
# or self.config(menu=self.menu)
# or self.configure(menu=self.menu)
anywhere after:
self.menu = ...
to have your menu assigned as the menu to your Toplevel-like widget.
I'm a python beginner making a program that is supposed to save and present reservations for a campingsite (just for fun...). I've structured it in an OOP-way meaning that I define a class for each seperate window. What I need to do is to update a TopLevel window (SubWindow2) presenting database entries, when another TopLevel window (created from Subwindow2) is closed.
import Tkinter as tk
class MenuWindow(tk.Tk):
def __init__(self, master):
self.master = master
#Widgets
def open_subwindow1(self):
self.window = Toplevel(self.master)
self.SubSubWindow1 = SubSubWindow1(self.window)
def open_subwindow2(self):
self.window = Toplevel(self.master)
self.SubSubWindow2 = SubSubWindow2(self.window)
class SubWindow1(tk.Tk):
def __init__(self, master):
self.master = master
#Widgets
class Subwindow2(tk.TopLevel):
def __init__(self, master):
self.master = master
#Widgets
self.button = tk.Button(master, text='Quit', command=open_subsub1)
def load_values(self):
#loading sqlite db-values into listboxes
def open_subsub1(self):
self.window = Toplevel(self.master)
self.SubSubWindow1 = SubSubWindow1(self.window)
class SubSubWindow1(tk.TopLevel):
def __init__(self, master):
self.master = master
#Widgets
self.button = tk.Button(master, text='Quit', command=on_quit)
def on_quit(self):
#Here I want to call a function that updates SubWindow2 (loads sqlite database values into several listboxes)
self.master.destroy()
root = tk.Tk()
myprog = MyProg(root)
root.mainloop()
How can i access a function in Subwindow2 from SubSubWindow1? self.master only refers to the TopLevel() instance right?
def on_quit(self):
self.SubWindow2.load_values()
self.master.destroy()
doesn't work, I get a TypeError: unbound method load_values() must be called with SubWindow2 instance as first argument (got nothing instead)
Is this an unvalid approch to "nesting" TopLevel-windows? What's the alternative?
Any remarks are greatly appriciated! Thanks for any help
I should preface this by claiming that I am also a novice, and I would greatly appreciate the advice of others in order not to spread misinformation.
From what I can see, you have some misunderstandings with how inheritance vs. encapsulation works in Python. Firstly, within a Tkinter application, there should only be a single instance of Tk(). Within your class definitions, you declare...
class SubWindow1(tk.Tk):
This means that whenever you create a new SubWindow1, a new instance of Tk will become instantiated with it, and SubWindow1 will inherit all of its properties.
If you would like to create a class that refers to, and has all the properties, of a Toplevel instance, your Subwindow2 was correct.
class Subwindow2(tk.TopLevel):
However, within the init, you must also initialize this instance of Toplevel as so:
class SubWindow2(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self)
self.master = master
Each 'master' refers to the element above it. Tk applications work as a tree hierarchy. This is why you should only have one instance of Tk(), which works as your 'root.' This instance of Tk contains windows within it, which contains windows or elements within them. So each window or element will have a parent, referred to as master, so you will be able to navigate around.
So when you create an instance of SubWindow2, this refers to everything within your SubWindow2, along with everything included in an instance of Toplevel. Because 'self' now refers to a Toplevel, you can pass it into the children to be a master, as such:
self.sub_sub_window1 = SubSubWindow1(self)
self.master only refers to the TopLevel() instance right?
Yes, but since you will be inheriting all of the Toplevel attributes through your SubWindow2 inheritance, you can add on more methods and still refer to them through your self.master tag.
Lastly, you should also call pack() on elements that you would like to show up correctly on your windows.
Altogether, I've made some edits to your program to try and demonstrate some of the concepts of inheritance and how it works within a Tkinter application. I hope you can look at this and take something from it. Please let me know if there are any elements you disagree with, as it is nowhere near perfect.
import Tkinter as tk
class MenuWindow():
def __init__(self, master):
self.master = master
self.sub_window_1 = SubWindow1(self.master)
self.sub_window_2 = SubWindow2(self.master)
class SubWindow1(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self)
self.master = master
class SubWindow2(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self)
self.master = master
self.sub_sub_window1 = SubSubWindow1(self)
def print_hello(self):
print "Hello!"
class SubSubWindow1(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self)
self.master = master
self.button = tk.Button(self.master, text='Say Hello & Destroy', command=self.on_quit)
self.button.pack()
def on_quit(self):
self.master.print_hello()
self.master.destroy()
root = tk.Tk()
myprog = MenuWindow(root)
root.mainloop()
I've programmed using tkinter before, but usually did a long procedural GUI class that implemented other non GUI classes I've created. This time I wanted to do it using more OOP making it more modular.
I ran into a problem, I've searched for answers and haven't found any, which usually means it's either really easy or I'm really wrong. I created an inherited classes from tk.LabelFrame and created GUI widgets in them. I also have methods to manipulate the widgets in the classes but I can't figure out how to execute a function in another inherited class, partly because I can't figure out how to correctly instantiate an object from the other class (which have tkinter ('parent') objects as parameters).
Would I do this by overloading constructors? I've seen something about #classmethods and *args, **kwargs but haven't acted on them as I'm not sure if that's the right route either. There's some debate about the best/correct way to implement an overloaded constructor in python. I'm stumped as to what is the most apropos for what I'm trying to accomplish...
Thanks
#python 2.7 on win7
import Tkinter as tk
class Testing(tk.LabelFrame):
buttonwidth = 10
def __init__(self, parent):
self.parent=parent
#results = Results(???) #<-- Don't know how to instantiate Results object
tk.LabelFrame.__init__(self, self.parent,
text="Test Operations",
padx=10,
pady=10,
)
self.taskButton = tk.Button(
self,
text="Do A Task",
width=self.buttonWidth,
command=self.doATask,
)
self.taskButton.pack()
def doATask(self):
#want to execute function in Results.getResult() but don't know how
#results.getResults() #<--what I want to do
print("place holder")
class Results(tk.LabelFrame):
def __init__(self, parent):
self.parent = parent
tk.LabelFrame.__init__(self, self.parent, text="Visual Results")
self.resultLbl = tk.Label(self, text="Result")
self.resultLbl.pack()
def getResult(self):
self.resultLbl.configure(bg='yellow')
class Application(tk.Frame):
def __init__(self, parent):
self.parent = parent
tk.Frame.__init__(self, self.parent)
self.Testing = Testing(self.parent)
self.Results = Results(self.parent)
self.Testing.pack(fill=tk.X)
self.Results.pack(fill=tk.X)
if __name__ == "__main__":
root = tk.Tk()
root.title("Modular GUI App")
Application(root).pack()
root.mainloop()
I'd recommend sticking to instance variables, which are created for each individual object, unlike class variables which are shared among all of a class's instantiations - just prepend those variable names with self. (e.g. self.results). Also, stick to naming conventions so you don't have a Testing class and a Testing object of that class.
You instantiate objects according to their __init__. The Results class has an __init__ defined as def __init__(self, parent):, so it needs a parent. If you want it to have the same parent as the Testing object that created it, simply do results = Results(parent). However, you don't want to do this (see below).
A problem that I encountered after making the above change was that the Application class instantiated its own Results object, and that was what was actually being displayed, not the one created by the Testing object. Refer back to that object instead of creating a new one. Pass the Application object to each of these classes so they can refer to each other. Now, having said that, it's generally better to have each class know as little about other classes as possible, so that making a change in one class doesn't require any changes in other classes.
The following code will make the label yellow when you click the button.
import Tkinter as tk
class Testing(tk.LabelFrame):
def __init__(self, parent, main):
self.buttonWidth = 10
self.parent=parent
self.main = main # save the instantiating class
tk.LabelFrame.__init__(self, self.parent,
text="Test Operations",
padx=10,
pady=10
)
self.taskButton = tk.Button(
self,
text="Do A Task",
width=self.buttonWidth,
command=self.doATask,
)
self.taskButton.pack()
def doATask(self):
#want to execute function in Results.getResult() but don't know how
self.main.results.getResult() #<--what you can do
class Results(tk.LabelFrame):
def __init__(self, parent, main):
self.parent = parent
self.main = main # save the instantiating class
tk.LabelFrame.__init__(self, self.parent, text="Visual Results")
self.resultLbl = tk.Label(self, text="Result")
self.resultLbl.pack()
def getResult(self):
self.resultLbl.config(bg='yellow')
class Application(tk.Frame):
def __init__(self, parent):
self.parent = parent
tk.Frame.__init__(self, self.parent)
self.testing = Testing(self.parent, self)
self.results = Results(self.parent, self)
self.testing.pack(fill=tk.X)
self.results.pack(fill=tk.X)
if __name__ == "__main__":
root = tk.Tk()
root.title("Modular GUI App")
Application(root).pack()
root.mainloop()
This worked for me
Results(None,None).getResult()
goodluck!