185

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 windows 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 codes 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.

martineau
  • 112,593
  • 23
  • 157
  • 280
Chris Aung
  • 8,390
  • 30
  • 78
  • 122
  • 2
    Here is an excellent tutorial on tkinter GUI design, with a couple examples -- http://python-textbok.readthedocs.org/en/latest/Introduction_to_GUI_Programming.html Here is another example with an MVC design pattern -- https://sukhbinder.wordpress.com/2014/12/25/an-example-of-model-view-controller-design-pattern-with-tkinter-python/ – Bondolin May 18 '15 at 18:39

8 Answers8

370

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.

Bryan Oakley
  • 341,422
  • 46
  • 489
  • 636
  • 37
    @Bryan Oakley do you know any good sample codes on internet that i can study their structure? – Chris Aung Jul 05 '13 at 08:35
  • 6
    I second the object-oriented approach. However, refraining from using inheritance on your class that calls the GUI is a good idea, in my experience. It offers you more flexibility if both the Tk and Frame objects are attributes of a class which doesn't inherit from anything. This way you can access the Tk and Frame objects more easily (and less ambiguously), and destroying one won't destroy everything in your class if you don't want it to. I forgot the exact reason why this is vital in some programs, but it does allow you to do more things. – Brōtsyorfuzthrāx May 19 '14 at 21:38
  • 2
    won't simply having a class give you a private namespace? why subclassing the Frame improve on that? – gcb Sep 08 '15 at 03:21
  • 5
    @gcb: yes, any class will give you a private namespace. Why subclass a Frame? I'm typically going to create a frame anyway, so it's one less class to manage (subclass of Frame, vs a class inheriting from object, with a frame as an attribute). I've rephrased the answer slightly to make that more clear. Thanks for the feedback. – Bryan Oakley Sep 08 '15 at 11:17
  • 2
    OOP approach here is nice and dandy (and trivial), but what about assigning responsibilities? Which class should be responsible for creating each widget? Which class should be responsible for layouting them in the right way? How to manage controller-gui couplings in a way that doesn't break the boundaries inbetween them? – Błażej Michalik Jan 05 '17 at 11:56
  • 1
    I am using this structure but I am getting: AttributeError: 'MainApplication' object has no attribute 'main'. I have also called Frame constructor and set self.parent to parent in Main class's constructor. Am I missing something? – Suresh Subedi Apr 11 '17 at 10:44
  • @BryanOakley At the second piece of source code I would say that `self.parent = parent` is missing. Right below the call to the parent __init__ and before `self.statusbar = Statusbar(self, ...)` – madtyn Sep 25 '17 at 17:59
  • 4
    @madtyn: there's no need to save a reference to `parent`, unless you're going to use it later. I didn't save it because none of the code in my example required that it be saved. – Bryan Oakley Sep 25 '17 at 18:00
  • For complex multi-window applications, do you recommend separating each window-class into it's own file? (ie: `main_window.py`, `login_window.py`, `import_file_window.py`) – Stevoisiak Feb 20 '18 at 20:54
  • @StevenVascellaro: yes. – Bryan Oakley Feb 20 '18 at 20:59
  • @Bryan: FWIW, I think you should have saved `parent` even though the code doesn't use it because in the text immediately following the snippet, you mention the parent becoming the "controller" and use calling `self.parent.statusbar.set("Hello, world")` as an example. – martineau Mar 08 '19 at 08:34
  • 2
    @martineau: there's no reason for `MainApplication` to save `self.parent`. The point was that the _other_ windows would be passed itself as a parameter, and _they_ would save `self.parent`. Perhaps I can try to rephrase that last bit to make it more clear. – Bryan Oakley Mar 08 '19 at 13:38
  • 2
    @Bryan: Thanks...think I see your logic now. The motivation of my comment being due to confusion due to there being a `MainApplication` and a `Main` class, both of which are "windows" (subclasses of `tk.Frame`). As a result, when you wrote "...for example, the main window...", I mixed the two up and thought the `parent` in the `self.parent.statusbar.set()` call was `root`, rather than it being the instance of `MainApplication` that's being passed as the `self` argument in the `self.main = Main(self, ...)` call (and saved by the `Main` instance in a attribute of its own named `parent`). – martineau Mar 08 '19 at 16:39
  • @BryanOakley May I suggest add one more positional or keyword argument `parent` for `class` instantiations in `class MainApplication(tk.Frame)` initialization, if my understanding, which I think the `self` here is playing the role of so called `controller`, is correct. – Kuo Nov 22 '19 at 12:01
  • 1
    @Kuo: `self` in code like `Statusbar(self, ...)` is the parent. It _also_ can act as a controller. – Bryan Oakley Nov 22 '19 at 13:34
  • @BryanOakley Here is [my question](https://stackoverflow.com/q/59007013/6299165). It seems doesn't work by setting `self` as `parent`. Would you take a look and advise? – Kuo Nov 23 '19 at 12:04
  • I frequently land on this page which gives a good head start. But as my application grows, I need even more structure. For example, my menu items are in dedicated class, and so is my toolbar. Both handle (partly) the same actions. Is it good practice for example, to put shared event handlers yet again in separate class? – Ronald Jul 07 '20 at 12:19
  • Or consider some mouse events like `` and `` to draw a rectangle: I saw dozens examples where they are handled by separate functions like `onClick()` and `onMove()`. Doesn't it make more sense to handle them all in a single `drawrectangle()` function? Works also, but I just started wondering... – Ronald Jul 07 '20 at 12:28
  • Should we override `grid`/`pack`/`place` in our sub-`Frame`s or layout those controls in the constructor of the sub `Frame`? I am using your way to divide by `Labelframe`s, however the `grid` geometry manager is screwing up all the positions. The controls inside the `Labelframe` pop out of it magically – demberto Feb 13 '22 at 19:17
  • @demberto: I don't understand your question. You can use any geometry manager you want inside the subframes. – Bryan Oakley Feb 13 '22 at 19:51
  • @BryanOakley I have subframes inheriting `ttk.Labelframe` and I use `pack` in some of my subframes while I use `grid` in a few. In the main frame, I use the `pack` as I have made the frames in such a way that finally I only need to use my main frame like a C# `StackPanel`; so I use `pack`. But I can't avoid the convenience of `grid` in a few subframes either. I get this error: `_tkinter.TclError: cannot use geometry manager pack inside . which already has slaves managed by grid` – demberto Feb 15 '22 at 06:12
  • 1
    @BryanOakley Ahh, well nvm I figured out what went wrong, I was initialising widgets in the subframes like this: `self.sb = ttk.Label(text="Status Bar")` when I should have done it like this `self.sb = ttk.Label(self, text="Status Bar")`. I was thinking that providing `self` was useless since the widgets are bound to the subframe but they were getting bound to the parent window (i.e. main frame which uses pack) itself – demberto Feb 15 '22 at 06:27
  • @BryanOakley could you please elaborate on what you mean regarding the parent acting as the MVC controller. – user32882 Feb 23 '22 at 19:56
  • I get `object has no attribute '_tclCommands'` Update -- ooops, I forgot to inherit `tk.Frame` – Mote Zart May 20 '22 at 22:41
48

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.

Community
  • 1
  • 1
alecxe
  • 441,113
  • 110
  • 1,021
  • 1,148
  • Why should I use `root` as an argument for the `Demo1` object, and why not? As seen in many of the answers in the current thread and around the internet. – carloswm85 Jul 14 '21 at 23:52
8

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.

Mark Amery
  • 127,031
  • 74
  • 384
  • 431
Serial
  • 7,627
  • 13
  • 48
  • 67
2

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()

enter image description here

Reference: http://www.python-course.eu/tkinter_buttons.php

Trevor
  • 5,489
  • 2
  • 17
  • 43
  • 2
    You can only use `TKinter` on Python 2. I would recommend using `tkinter` for Python 3. I would also place the last three lines of code under a `main()` function and call that at the end of the program. I would **definitely** avoid using `from module_name import *` as it pollutes the global namespace and can reduce readability. – Alice Jan 13 '17 at 10:42
  • 1
    How could you tell the difference between `button1 = tk.Button(root, command=funA)` and `button1 = ttk.Button(root, command=funA)` if the `tkinter` extension module was also being imported? With the `*` syntax, both lines of code would appear to be `button1 = Button(root, command=funA)`. I wouldn't recommend using that syntax. – Alice Jan 13 '17 at 10:46
  • I wonder about the criteria for the `App(root)` approach instead of `App()` as in other examples. Also, why should `Frame` be a class variable, or not, something else. – carloswm85 Jul 14 '21 at 23:49
  • carloswm85 me too. Can @Bryan Oakley shed some light ? – pippo1980 Sep 13 '21 at 14:45
0

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()
muyustan
  • 1,303
  • 1
  • 6
  • 21
0

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/]

Gab ПК
  • 13
  • 3
-4

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.

Inbar Rose
  • 39,034
  • 24
  • 81
  • 124
  • 17
    -1 for using the Zen of Python. While it's all good advice, it doesn't directly address the question that was asked. Take the last paragraph out and this answer could apply to almost every python question on this site. It's good, positive advice, but it doesn't answer the question. – Bryan Oakley Jul 13 '13 at 21:31
  • 1
    @BryanOakley I disagree with you on that. Yes, the Zen of Python is broad and can be used to address many questions. He did mention in the final paragraph to opt for classes or placing the functions in separate modules. He also mentioned PEP8, a style guide for Python, with references to it. Although not a direct answer, I think this answer is credible in the fact that it mentions many different routes that can be taken. That's just my opinion – Alice Jan 13 '17 at 10:39
  • 2
    I came here looking for answers to this specific question. Even for an open-ended question, I can't do anything with this response. -1'd from me as well. – jonathan Mar 16 '18 at 21:32
  • 1
    No way, the question is about to structure a **tkinter** app, nothing about styling/coding/zen guidelines. Easy as quoting @Arbiter "Although not a direct answer", so, it's NOT an answer. This is like "maybe yes and maybe no", with zen prepended. – m3nda Aug 12 '18 at 01:02
-10

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.

gcb
  • 13,157
  • 7
  • 65
  • 89
  • 7
    What you say is simply not true. I've written hudreds of tk-based applications, both personal and commercial, and almost never have had to use threads. Threads have their place, but it is simply not true that you _must_ use them when writing tkinter programs. If you have long runnng functions you may need threads or multiprocessing, but there are many, many types of programs you can write that don't need threads. – Bryan Oakley Sep 07 '15 at 22:12
  • I think if you rephrased your answer to be a bit more clear on that, it would be a better answer. It would also really help to have a canonical example of using threads with tkinter. – Bryan Oakley Sep 07 '15 at 22:21
  • didn't care about being the best answer here because it is kinda off topic. but keep in mind that starting with threading/multip is very easy. if you have to add later, it is a lost battle. and nowadays, there absolutely no application that won't ever talk to the network. and even if you ignore and think 'i only have little disk IO', tomorrow your client decides that file will live on NFS and you are waiting for network IO and your app seems dead. – gcb Sep 08 '15 at 03:19
  • @BryanOakley Both of you are right. By the way, there are dozens of software examples of non threaded app's (ie, installers), but, every app connected with the network or doing IO writting will be lot faster using threads or subprocess. It's hard to say that if the black or white are the best, bet for gray. – m3nda Apr 14 '16 at 04:04
  • 3
    @erm3nda: _"every app connected with the network or doing IO writting will be lot faster using threads or subprocess"_ - that is simply not true. Threading won't necessarily make your program faster, and in some cases will make it slower. In GUI programming, the main reason to use threads is to be able to run some code that would otherwise block the GUI. – Bryan Oakley Apr 14 '16 at 10:57
  • @BryanOakley Is not true, is not false. Totally depends on the need of that app, but you is saying that threads are not needed at all. I don't know wich kind of softwared did build you, but I can only imagine installers, notepads, and other easy tools. The main loop should be uses only to maintain the GUI, not the work did by it. I am wrong again? – m3nda Apr 14 '16 at 19:59
  • 2
    @erm3nda: no, I am _not_ saying threads are not needed _at all_. They are definitely needed (well, threads or multiprocessing) for lots of things. It's just that there's a very large class of GUI applications where tkinter is suitable but where threads simply aren't needed. And yes, "installers, notepads,. and other easy tools" fall into that category. The world is made up of more of these "easy tools" than it is of things like word, excel, photoshop, etc. Plus, remember that the context here is _tkinter_. Tkinter typically is not used for very large, complex applications. – Bryan Oakley Apr 14 '16 at 20:12
  • @BryanOakley now Im okay with you. I use Tkinter for small things, or because other tools requested it. For large app's wxWidgets or even QT with templates are the right tool. Large applications with Tkinter needs large knowledge. "The world is made up of more of these". I use threads on every networking app and of course I've understand your position. Regards. – m3nda Apr 14 '16 at 22:20
  • Gui stuff lends itself to OOP, that's probably why tkinter uses OOP with its own code: https://github.com/python/cpython/tree/master/Lib/tkinter – PunkUnicorn Mar 22 '20 at 11:47