Skip to content

Instantly share code, notes, and snippets.

@jdittrich
Last active September 17, 2025 08:51
Show Gist options
  • Save jdittrich/7f03622695256bf6e56a459e1187a433 to your computer and use it in GitHub Desktop.
Save jdittrich/7f03622695256bf6e56a459e1187a433 to your computer and use it in GitHub Desktop.
an example for an MVC (where view+controller are combined) with python and tkinter. I saw approaches separating View and Controller, but they seemed leaky, too.
import tkinter as tk
from tkinter import ttk
from collections.abc import Callable
# A simple demo app.
# Press the button to update the label
class AppView():
def __init__(self, title="apptitle"):
root = tk.Tk()
root.title(title)
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.minsize(200, 100)
main_frame = ttk.Frame(root, padding="3 3 3 3", relief="raised")
main_frame.grid(column=0, row=0, sticky=("nesw"))
self.root = root
self.main_frame = main_frame
def change_titel(self,title="apptitle"):
self.root.title(title)
def start(self):
self.root.mainloop()
# Views allow to retrieve a tk frame
# if the view can be used to append other
# tk elements to it as parent
# this is leaky in regards to tk
# but it is far less code than a complex wrapper.
# The approach also allows to retrieve different tk frames
# e.g. by having a get_left_panel method.
def get_main_tk(self):
return self.main_frame
class LabelView():
#views that are not the app view need to pass a parent_tk element.
def __init__(self,parent_tk,model):
parent_tk = parent_tk
label = ttk.Label(parent_tk, text=model.get_label())
label.grid(column=0, row=0)
button = ttk.Button(parent_tk,text="update!", command=self.trigger_update_label)
button.grid(column=0,row=1)
self.button = button
self.label = label
self.model = model
model.on("update",self.handle_update_label) # to update the view on model update
def handle_update_label(self,model,*args):
self.label.config(text = model.get_label())
def trigger_update_label(self): #upon press of the button
self.model.update_lable("new label")
# Models inherent from Observeable model to
# inform subscribers like views about the updated data
# You can do something like on("update", self.handle_model_update)
class ObservableModel():
_event_types = {}
def __init__(self):
pass
def on(self,event_type:str,listener:Callable):
if event_type in self._event_types:
self._event_types[event_type].append(listener)
else:
self._event_types[event_type] = [listener]
def off(self,eventtype:str,listener:Callable):
self._event_types[eventtype].remove(listener)
def trigger(self,eventtype:str,*args, **kwargs)->None:
for event_handler in self._event_types[eventtype]:
event_handler(*args,**kwargs)
class LabelModel(ObservableModel):
label = "testlabel" #default label
def __init__(self, label, *args,**kwargs):
super().__init__(*args,**kwargs)
self.label = label
def get_label(self):
return self.label
def update_lable(self,new_label):
self.label = new_label
self.trigger("update",self) #inform subscribers about update!
#-- Setup ---
app = AppView()
labelModel = LabelModel("my old label");
labelView = LabelView(parent_tk=app.get_main_tk(),model=labelModel)
app.start()
# would it not be simpler to ditch the button and just do a labelModel.update_label("newlabel") here?
# nope.
# a) I want to demonstrate controller actions, so the button is useful and, more relevant,
# b) app.start() (or rather the tk mainloop it wraps) does not return as long as the programm is running
# so code after start() is never reached while the app runs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment