What is the best way to structure a Tkinter application? [closed]
Asked Answered
I

9

217

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.

Induration answered 4/7, 2013 at 9:21 Comment(1)
Here is an excellent tutorial on tkinter GUI design, with a couple examples -- python-textbok.readthedocs.org/en/latest/… Here is another example with an MVC design pattern -- sukhbinder.wordpress.com/2014/12/25/…Puparium
C
423

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.

Claypan answered 4/7, 2013 at 12:52 Comment(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.Damal
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?Bunch
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)Stoughton
@MartijnPieters: yes, my view changed, I explained that in the other answer.Claypan
@BryanOakley: facepalm So you do, sorry!Sachiko
@BryanOakley Could you please share a link to the other answer that you refer to?Unbowed
D
56

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.

Designed answered 4/7, 2013 at 9:40 Comment(2)
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.Josephinejosephson
@Josephinejosephson IMO, it depends on whether the class you create is a frame (then it needs to be placed in the parent (root), and thus root needs to be passed as an argument); or whether the class inherits tk itself, and hence you don't need to pass any argument, as your class will be the tk instace itself.Integral
H
9

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.

Horoscope answered 4/7, 2013 at 9:38 Comment(2)
"Thinking in Tkinter" advocates global imports, which I think is very bad advice.Claypan
Thts true i dont suggest you use globals just some of the main class methos structure youre right :)Horoscope
C
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

Cask answered 17/7, 2016 at 1:52 Comment(4)
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.Storey
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.Storey
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.Josephinejosephson
carloswm85 me too. Can @Bryan Oakley shed some light ?Absentee
L
1

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

Lenardlenci answered 20/1, 2022 at 23:28 Comment(2)
That tutorial most likely used Bryan's other answer right here. These posts came far before that tutorial you reference.Quatrefoil
@Quatrefoil obviously, that is why I cited Bryan Oakley in my first sentence "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 don't see your point here..Ruling
A
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()
Armchair answered 11/4, 2020 at 4:19 Comment(2)
This example probably works, but I was not able to make it work.Josephinejosephson
@Josephinejosephson No, it does not. It's full of errors. Just check out the other examples in this page.Cyanogen
D
0

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.

Deniable answered 24/6, 2023 at 11:49 Comment(0)
B
-5

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.

Bevins answered 4/7, 2013 at 9:36 Comment(4)
-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.Claypan
@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 opinionStorey
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.Phonemics
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.Wappes
S
-12

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.

Sherr answered 7/9, 2015 at 19:13 Comment(9)
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.Claypan
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.Claypan
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.Sherr
@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.Wappes
@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.Claypan
@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?Wappes
@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.Claypan
@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.Wappes
Gui stuff lends itself to OOP, that's probably why tkinter uses OOP with its own code: github.com/python/cpython/tree/master/Lib/tkinterHuss

© 2022 - 2024 — McMap. All rights reserved.