Reputation: 9492
The following is the overall structure of my typical Python Tkinter program.
def funA():
def funA1():
def funA12():
# stuff
def funA2():
# stuff
def funB():
def funB1():
# stuff
def funB2():
# stuff
def funC():
def funC1():
# stuff
def funC2():
# stuff
root = tk.Tk()
button1 = tk.Button(root, command=funA)
button1.pack()
button2 = tk.Button(root, command=funB)
button2.pack()
button3 = tk.Button(root, command=funC)
button3.pack()
funA
, funB
and funC
will bring up another Toplevel
window with widgets when user click on button 1, 2, 3.
I am wondering if this is the right way to write a Python Tkinter program? Sure, it will work even if I write this way, but is it the best way? It sounds stupid but when I see the code other people written, their code is not messed up with bunch of functions and mostly they have classes.
Is there any specific structure that we should follow as good practice? How should I plan before start writing a Python program?
I know there is no such thing as best practice in programming and I am not asking for it either. I just want some advice and explanations to keep me on the right direction as I am learning Python by myself.
Upvotes: 223
Views: 289399
Reputation: 11
On developments where I have design authority, I set up the GUI part of the work into two sets of code:
Set 1. The core code that draws the window containing the widgets and their bindings. I may have generated these using wxFormBuilder or Glade so I don't want any hand-coded amendments to the files. More likely, I've painfully hand-crafted the windows from scratch, and definitely don't want any inadvertent tinkering!
Set 2. The code in which I write the functionality driving the window.
By separating the core of the GUI definition from the work of building functionality, it means I don't 'accidentally' break the core window structure when I'm working on the functionality, and the code is far more managable and maintainable.
We all know that size and complexity of program files becomes a real problem very quickly. So, with the above separation of form and function (as it were), I simply import the core window code file and subclass it in the functional code file.
For example, 'frmWindow_core_code.py' keeps the core window functionality:
class frmWindow(tk.TK):
button1 = ttk.Button....
entry1 = ttk.Entry....
etc
etc
The file that drives the GUI functionality, e.g 'MyWindowProgram.py', imports and sub-classes the core file e.g.:
import frmWindow_cored_code as Dialogue
class ThisWindow(Dialogue.frmWindow): etc etc etc
Just a suggestion I hope may be useful.
Upvotes: 1
Reputation: 93
My preferred way of doing it is like Bryan Oakley's answer. Here's an example, made by Sentdex on Youtube, go check his "GUIs with Tkinter" playlist.
I think it's really relevant to put it here because it's a great example for the OP, and so it also answers this answer that was upped by 35 people and wasn't answered;
@Bryan Oakley do you know any good sample codes on internet that i can study their structure? – Chris Aung Jul 5 '13 at 8:35
import tkinter as tk
LARGE_FONT= ("Verdana", 12)
class SeaofBTCapp(tk.Tk):
"""
tkinter example app with OOP
"""
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for frame_class in (StartPage,PageOne, PageTwo):
frame = frame_class(container, self)
self.frames[frame_class] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
"""
Put specific frame on top
"""
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
"""
Starting frame for app
"""
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent,bg='grey')
label = tk.Label(self, text="Start Page", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button_page1 = tk.Button(self, text = 'Visit Page 1', command= lambda: controller.show_frame(PageOne))
button_page1.pack()
button_page2 = tk.Button(self, text = 'Visit Page 2', command= lambda: controller.show_frame(PageTwo))
button_page2.pack()
class PageOne(tk.Frame):
"""
First page of program
"""
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent,bg='light blue')
label = tk.Label(self, text="Page one", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button_home = tk.Button(self, text = 'Back to Home', command= lambda: controller.show_frame(StartPage))
button_home.pack()
button_home = tk.Button(self, text = 'Go to page2', command= lambda: controller.show_frame(PageTwo))
button_home.pack()
class PageTwo(tk.Frame):
"""
First page of program
"""
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent,bg='light green')
label = tk.Label(self, text="Page two", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button_home = tk.Button(self, text = 'Back to Home', command= lambda: controller.show_frame(StartPage))
button_home.pack()
button_home = tk.Button(self, text = 'Go to page1', command= lambda: controller.show_frame(PageOne))
button_home.pack()
app = SeaofBTCapp()
app.mainloop()
Find the code here also : [https://pythonprogramming.net/change-show-new-frame-tkinter/]
Upvotes: 3
Reputation:
Organizing your application using class make it easy to you and others who work with you to debug problems and improve the app easily.
You can easily organize your application like this:
class hello(Tk):
def __init__(self):
super(hello, self).__init__()
self.btn = Button(text = "Click me", command=close)
self.btn.pack()
def close():
self.destroy()
app = hello()
app.mainloop()
Upvotes: 1
Reputation: 8043
This isn't a bad structure; it will work just fine. However, you do have to have functions in a function to do commands when someone clicks on a button or something
So what you could do is write classes for these then have methods in the class that handle commands for the button clicks and such.
Here's an example:
import tkinter as tk
class Window1:
def __init__(self, master):
pass
# Create labels, entries,buttons
def button_click(self):
pass
# If button is clicked, run this method and open window 2
class Window2:
def __init__(self, master):
#create buttons,entries,etc
def button_method(self):
#run this when button click to close window
self.master.destroy()
def main(): #run mianloop
root = tk.Tk()
app = Window1(root)
root.mainloop()
if __name__ == '__main__':
main()
Usually tk programs with multiple windows are multiple big classes and in the __init__
all the entries, labels etc are created and then each method is to handle button click events
There isn't really a right way to do it, whatever works for you and gets the job done as long as its readable and you can easily explain it because if you cant easily explain your program, there probably is a better way to do it.
Take a look at Thinking in Tkinter.
Upvotes: 10
Reputation: 385970
I advocate an object oriented approach. This is the template that I start out with:
# Use Tkinter for python 2, tkinter for python 3
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
<create the rest of your GUI here>
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
The important things to notice are:
I don't use a wildcard import. I import the package as "tk", which requires that I prefix all commands with tk.
. This prevents global namespace pollution, plus it makes the code completely obvious when you are using Tkinter classes, ttk classes, or some of your own.
The main application is a class. This gives you a private namespace for all of your callbacks and private functions, and just generally makes it easier to organize your code. In a procedural style you have to code top-down, defining functions before using them, etc. With this method you don't since you don't actually create the main window until the very last step. I prefer inheriting from tk.Frame
just because I typically start by creating a frame, but it is by no means necessary.
If your app has additional toplevel windows, I recommend making each of those a separate class, inheriting from tk.Toplevel
. This gives you all of the same advantages mentioned above -- the windows are atomic, they have their own namespace, and the code is well organized. Plus, it makes it easy to put each into its own module once the code starts to get large.
Finally, you might want to consider using classes for every major portion of your interface. For example, if you're creating an app with a toolbar, a navigation pane, a statusbar, and a main area, you could make each one of those classes. This makes your main code quite small and easy to understand:
class Navbar(tk.Frame): ...
class Toolbar(tk.Frame): ...
class Statusbar(tk.Frame): ...
class Main(tk.Frame): ...
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.statusbar = Statusbar(self, ...)
self.toolbar = Toolbar(self, ...)
self.navbar = Navbar(self, ...)
self.main = Main(self, ...)
self.statusbar.pack(side="bottom", fill="x")
self.toolbar.pack(side="top", fill="x")
self.navbar.pack(side="left", fill="y")
self.main.pack(side="right", fill="both", expand=True)
Since all of those instances share a common parent, the parent effectively becomes the "controller" part of a model-view-controller architecture. So, for example, the main window could place something on the statusbar by calling self.parent.statusbar.set("Hello, world")
. This allows you to define a simple interface between the components, helping to keep coupling to a minimun.
Upvotes: 432
Reputation: 6101
OOP should be the approach and frame
should be a class variable instead of instance variable.
from Tkinter import *
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.button = Button(frame,
text="QUIT", fg="red",
command=frame.quit)
self.button.pack(side=LEFT)
self.slogan = Button(frame,
text="Hello",
command=self.write_slogan)
self.slogan.pack(side=LEFT)
def write_slogan(self):
print "Tkinter is easy to use!"
root = Tk()
app = App(root)
root.mainloop()
Reference: http://www.python-course.eu/tkinter_buttons.php
Upvotes: 3
Reputation: 14548
I personally do not use the objected oriented approach, mostly because it a) only get in the way; b) you will never reuse that as a module.
but something that is not discussed here, is that you must use threading or multiprocessing. Always. otherwise your application will be awful.
just do a simple test: start a window, and then fetch some URL or anything else. changes are your UI will not be updated while the network request is happening. Meaning, your application window will be broken. depend on the OS you are on, but most times, it will not redraw, anything you drag over the window will be plastered on it, until the process is back to the TK mainloop.
Upvotes: -12
Reputation:
Probably the best way to learn how to structure your program is by reading other people's code, especially if it's a large program to which many people have contributed. After looking at the code of many projects, you should get an idea of what the consensus style should be.
Python, as a language, is special in that there are some strong guidelines as to how you should format your code. The first is the so-called "Zen of Python":
- Beautiful is better than ugly.
- Explicit is better than implicit.
- Simple is better than complex.
- Complex is better than complicated.
- Flat is better than nested.
- Sparse is better than dense.
- Readability counts.
- Special cases aren't special enough to break the rules.
- Although practicality beats purity.
- Errors should never pass silently.
- Unless explicitly silenced.
- In the face of ambiguity, refuse the temptation to guess.
- There should be one-- and preferably only one --obvious way to do it.
- Although that way may not be obvious at first unless you're Dutch.
- Now is better than never.
- Although never is often better than right now.
- If the implementation is hard to explain, it's a bad idea.
- If the implementation is easy to explain, it may be a good idea.
- Namespaces are one honking great idea -- let's do more of those!
On a more practical level, there is PEP8, the style guide for Python.
With those in mind, I would say that your code style doesn't really fit, particularly the nested functions. Find a way to flatten those out, either by using classes or moving them into separate modules. This will make the structure of your program much easier to understand.
Upvotes: -4
Reputation: 473873
Putting each of your top-level windows into it's own separate class gives you code re-use and better code organization. Any buttons and relevant methods that are present in the window should be defined inside this class. Here's an example (taken from here):
import tkinter as tk
class Demo1:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.button1 = tk.Button(self.frame, text = 'New Window', width = 25, command = self.new_window)
self.button1.pack()
self.frame.pack()
def new_window(self):
self.newWindow = tk.Toplevel(self.master)
self.app = Demo2(self.newWindow)
class Demo2:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
self.quitButton.pack()
self.frame.pack()
def close_windows(self):
self.master.destroy()
def main():
root = tk.Tk()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()
Also see:
Hope that helps.
Upvotes: 58