I'm fairly new to python and try to build a simple GUI following an object oriented approach. Therefor I let my widget classes inherit from tk.Frame and create an application controller to build the GUI.
My application contains the following two files:
mainModule.py
# coding: utf8
from testPackage import myGUI
import Tkinter as tk
root = tk.Tk() # Main window
my_gui = myGUI.MainApplication(root)
root.mainloop() # Hold window open until we close it
myGUI.py
# coding: utf8
import Tkinter as tk
# Application initializer
class MainApplication(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, master)
self.configure_gui()
self.pack() # <--- CODE IN QUESTION
self.create_widgets()
def configure_gui(self):
self.master.title("Any title")
self.master.geometry('800x600')
self.master.minsize(600, 100)
def create_widgets(self):
self.main_window = MainWindow(self)
self.main_window.pack()
# Main Data Window
class MainWindow(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, master)
self.main_window = tk.Label(self, text="This is a test")
self.main_window.pack(side="top", fill="x")
Initially running my code without self.pack() (marked as #Code in question) in the MainApplication class definition gave me only the root basic window without the Label created from MainWindow class.
As simple as the answer may be but why?
I used a few sources to get into the topic inbefore and some of them didn't use any geometry manager on the root window (or i'm to inexpierienced to see it. Source below for examples).
Source:
https://www.begueradj.com/tkinter-best-practices.html
http://python-textbok.readthedocs.io/en/latest/Introduction_to_GUI_Programming.html#putting-it-all-together
Tkinter example code for multiple windows, why won't buttons load correctly?
Only after reading following answers regarding widget creation I came aware of the missing part:
Best way to structure a tkinter application
creating a custom widget in tkinter
Figuring that pack() on initialization would be the same as
MainApplication(root).pack(side="top", fill="both", expand=True)
or
Example(root).place(x=0, y=0, relwidth=1, relheight=1)
for a grid based layout.
I guess it's a very simple or obvious element i don't see relating to inheritance but i'm not entirely sure why have to pack() the MainApplication Frame and furthermore why it seems to work for example without this step.
Because MainWindow inherits from Frame, it is itself a frame. If you never call pack, place, or grid on it, it will be invisible. This is no different than if you created a button or scrollbar or any other widget and then don't call one of those methods on it.
Since all of the other widgets are a children of this frame, they will be invisible since their parent is invisible.
Somewhat unrelated to the question being asked, self.pack() is a code smell. Generally speaking, a class should never call pack, place or grid on itself. This tightly couples itself to the caller (meaning, this widget has to know that the caller is using one of those methods).
In other words, if you decide that MainApplication wants to switch from pack to grid for all of its children, you can't just update MainApplication, you also have to update MainWindow. In this case, the root window has only one child so the problem is fairly small, but by doing it this way you are starting a bad practice that will eventually cause you problems.
The rule of thumb is that the function that creates a widget should be responsible for adding it to the screen. That means that it would be better to do it like this:
root = tk.Tk()
my_gui = myGUI.MainApplication(root)
my_gui.pack(fill="both", expand=True)
You would then need to remove self.pack() from MainApplication.__init__.
You also have some very misleading code that might be contributing to the confusion. Take a look at this code:
# Main Data Window
class MainWindow(tk.Frame):
def __init__(self, master):
...
self.main_window = tk.Label(self, text="This is a test")
self.main_window.pack(side="top", fill="x")
Notice how you have a class named MainWindow. Within that you have a label that is named self.main_window, but self.main_window is not a MainWindow. This is especially confusing since the function that creates MainWindow also creates an instance variable named self.main_window.
You might want to consider renaming this label to be something else (eg: self.label, self.greeting, etc).
Related
I have a class where I create a Label on the init method:
import tkinter as tk
from tkinter.filedialog import askopenfilename
class app(tk.Tk):
def __init__(self, master):
label1 = tk.Label(master, text="Select file...")
label1.pack()
Then with a button I call a method to choose a file and change the label text to filename path.
def files(self):
filename = askopenfilename()
self.label1.config(text=filename)
The problem is that when I choose the file, the app gets closed without errors, so I don't know what's going on.
Outside the class I have:
root = tk.Tk()
app_gui = app(root)
root.mainloop()
In your specific case, there are two problems. The first is that you're creating two instances of tk.Tk. You should never do that.
The second is that you aren't creating self.label1 so any attempt to modify it will fail.
The solution is to first remove tk.Tk as a superclass for app. The second is to properly define self.label1
class app():
def __init__(self, master):
self.label1 = tk.Label(...)
...
On a side note, you should seriously consider following the naming conventions of PEP8 and name your main class App. PEP8 is nearly universal in the python world, and deviating from it makes your code harder to read.
I'm developing a program using Tkinter for my GUI. Right now I'm conceptualizing the entire project, so the code I will provide is very simplistic.
Essentially, I wanted to create a main window that has several widgets each written in their own classes. One of the widgets would be the navbar where "File" -> "Save As" would exist.
My issue with this is if I have navbar as a separate class that is instantiated in the master class, the save function written in the navbar would be unable to view the variables in the other classes.
I have thought of potentially two solutions for this, but I am not sure if either one is necessarily the right thing to do for best Software Engineering practices.
Potential solution 1: Create a separate thread that constantly waits for the user to click save. Once it clicks save it changes an event flag which causes the main class to call some save function to save all variables. My issue with this is it is a constant waste of resources. The thread will be wasting resources constantly waiting for the save button to be clicked.
Potential solution 2: Create the navbar in a separate class, but define the navbar functions in the main class that instantiates it. My issue with this is that it makes the main class colluded with extraneous functions that I would like to have defined elsewhere for better practices. Additionally, I am not entirely sure how I would do this but I am sure there is some way that it could be done if I spent time looking into it.
class Main:
def __init__(self, rt):
self.rt = rt
self.navbar = navbar.NavBar(rt)
self.rt.mainloop()
class NavBar(tkinter.Frame):
def __init__(self, master):
tkinter.Frame.__init__(self, master)
self.master = master
self.bg='red'
self.text = tkinter.Text(self, height=1, width=30)
self.text.insert(tkinter.END, "File")
self.text.pack()
self.grid(row=0, column=0)
if __name__ == "__main__":
root = tkinter.Tk()
root.title('Automation')
main = Main(root)
Here, Navbar would have a button named File which if you scrolled over would generate a list of other buttons, one of which being "Save As". If this Save As button is clicked, I would want to be able to save all variables belonging to Main.
TLDR: I want to figure out the best way for me to create a save file functionality, being able to save variables from different classes that are all instantiated under a main umbrella class.
Normally I will setup the Main class as the controller of all the Frames, and have the Main inherits from Tk directly. Below is a sample on how to access attributes from other classes using this approach:
import tkinter as tk
class Main(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.navbar = NavBar(self)
self.another_frame = AnotherFrame(self)
self.navbar.grid(row=0, column=0)
self.another_frame.grid(row=1, column=0)
class NavBar(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
tk.Button(self,text="Button of first frame",command=self.print_other_frame).pack()
def print_other_frame(self):
print (self.master.another_frame.entry.get()) #access master attributes
class AnotherFrame(tk.Frame):
def __init__(self,master):
tk.Frame.__init__(self, master)
self.entry = tk.Entry(self)
self.entry.pack()
self.entry.insert(0,"Test from 2nd frame")
if __name__ == "__main__":
root = Main()
root.title('Automation')
root.mainloop()
I'm creating a json editor in python using tkinter.
I've added a scrollbar by creating a Canvas, and putting a Frame inside it.
Then I set the Scrollbar command to canvas.yview.
Theres two things that are messing up, and I have no idea why.
When I press the scroll buttons (up and down arrows) the canvas is not scrolling
I am packing the scrollbar onto the window (root) right now instead of the frame, because whenever i pack it onto the frame, the tkinter application does not open, and my computer fan starts turning on... Anyone know what is going on here? (Therefore the scrollbar is tiny if you try to run the code)
Here is my code:
EDIT> Code shortened
import Tkinter as tk
import webbrowser
import os
import bjson as bj
class App:
def __init__(self, master):
self.window = master
self.window.geometry("800x450")
self.canvas = tk.Canvas(self.window, width=800, height=400)
self.master = tk.Frame(self.canvas, width=800, height=400)
self.canvas.pack()
self.master.place(x=0, y=0)
scrollbar = tk.Scrollbar(self.window)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
scrollbar.config(command=self.canvas.yview)
def init(self):
master = self.master
self.frames = {
"Home": HomeFrame(master)
}
self.openFrame = None
self.loadFrame("Home")
def loadFrame(self, frame):
self.openFrame = self.frames[frame]
self.openFrame.display()
def setTitle(self, t):
self.window.title(t)
class Frame:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(master)
self.frame.grid(row=0, column=0, sticky='news')
self.init()
self.frame_create()
def display(self):
self.frame.tkraise() #raises frame to top
self.frame_load() #initializes the frame
def clear(self):
for widget in self.frame.winfo_children():
widget.destroy()
def init(self): pass
def frame_load(self): pass
def frame_create(self): pass
class HomeFrame(Frame):
def frame_create(self):
p = self.frame
for i in range(20):
tk.Label(p, text="This is content... " + str(i)).pack()
for j in range(2):
LineBreak(p)
def LineBreak(p):
tk.Label(p, text="").pack()
root = tk.Tk()
glob = {}
app = App(root)
app.init()
root.mainloop()
It is a bit long, and a bit messy, but you should see how I'm adding the scrollbar in the __init__ of App
Anyone have any idea what's going on, and how to fix it?
Thanks in advance!
There are many things wrong with your code. However, the problem with the scrollbar not working properly has to do with two things you are neglecting to do:
First, scrollbars and widgets require two way communication. The canvas needs to be told about the scrollbar, and the scrollbar needs to be told about the canvas. You are doing one but not the other:
self.canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.configure(command=self.canvas.yview)
Second, you need to configure the scrollregion attribute of the canvas. This tells tkinter what part of the larger virtual canvas you want to be viewable. Typically this is done in a binding on the <Configure> method of the canvas, and usually you will want to set it to the bounding box of everything in the canvas. For the latter you can pass the string "all" to the bbox method:
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
If you know the exact size of the area you want to be scrollable, you can simply set it to that value (eg: scrollregion=(0,0,1000,1000) to scroll around in a region that is 1000x1000 pixels).
The reason for point #2 is that you can't use both pack and grid for widgets that share the same parent. When you do, you'll get the behavior you describe. That is because grid will try to layout all of the widgets. This may result in some widgets changing size. pack will notice the change in the size of one or more widgets and try to re-layout all of the widgets. This may result in some widgets changing size. grid will notice the change in the size of one or more widgets and try to re-layout all of the widgets. And so on.
I've been struggling with this for a while. I think I'm missing some simple piece of information and I hope you guys can help clear this up for me.
I'm trying to get tkinter to display different frames which I will eventually place widgets inside of. Here's what I did:
I've made a class that is supposed to initialize the window and make all the different frames the program will run.
I've made a separate class for each frame(I'm intending to have variables associated with the different classes when the program is done), and assigned a variable that will start that class up and make it run it's init function
I ended the StartUp class by telling it to tkraise() the frame I want displayed, and that's where things stop working correctly.
I set each frame to a different color, so when you run this program you will see that they split the screen space up instead of one being raised to the top. What am I missing?
One last point, I am purposely trying to spell everything out in my program, I learn better that way. I left it so I have to type tkinter.blah-blah-blah in front of each tkinter command so I can recognize them easily, and I decided not to have my classes inherit Frame or Tk or anything. I'm trying to understand what I'm doing.
import tkinter
class StartUp:
def __init__(self):
self.root = tkinter.Tk()
self.root.geometry('300x300')
self.container = tkinter.Frame(master=self.root, bg='blue')
self.container.pack(side='top', fill='both', expand=True)
self.page1 = Page1(self)
self.page2 = Page2(self)
self.page1.main_frame.tkraise()
class Page1():
def __init__(self, parent):
self.main_frame = tkinter.Frame(master=parent.container, bg='green')
self.main_frame.pack(side='top', fill='both', expand=True)
class Page2():
def __init__(self, parent):
self.main_frame = tkinter.Frame(master=parent.container, bg='yellow')
self.main_frame.pack(side='top', fill='both', expand=True)
boot_up = StartUp()
boot_up.root.mainloop()
When you do pack(side='top', ...), top doesn't refer to the top of the containing widget, it refers to the top of any empty space in the containing widget. Page initially takes up all of the space, and then when you pack Page2, it goes below Page1 rather than being layered on top of it.
If you are using the strategy of raising one window above another, you need to either use grid or place to layer the widgets on top of each other. The layering is something pack simply can't do.
Your other choice is to call pack_forget on the current window before calling pack on the new windowl
I am quite new to tkinter and I wonder how I should create frames.
If one is prefered over the other one, why is it so?
Is it better like this:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent):
main_frame = tk.Frame(parent)
root = tk.Tk()
main_app = MainApplication(root)
root.mainloop()
or like this:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
root = tk.Tk()
main_app = MainApplication(root)
root.mainloop()
The first one creates two frames -- the instance itself (main_app) is a frame, and it contains a child frame (main_frame). Though, because you don't call the __init__ of Frame, the first frame is not properly constructed.
If you are immediately going to create an internal frame and put everything inside it, it's pointless to inherit from Frame.
By the way, your code doesn't quite work. If you want to actually see the application widget (and its children) then you're going to need to call pack, place or grid on the widget.
For example:
root = tk.Tk()
main_app = MainApplication(root)
main_app.pack(fill="both", expand=True)
root.mainloop()