Design & Architecture¶
This document explains how myTk is built and why it is built that way. It sits between the narrative README and the per-module API Reference: read the README to see what myTk does, read this to understand its structure, then dive into the API for details.
Design goals¶
myTk exists to make Tkinter pleasant for scientists and other non-professional programmers who need a working desktop GUI without adopting a heavy framework. Five goals drive every design decision:
- G1 — Stay simple and portable.
Tk ships with Python and is portable across macOS, Windows and Linux. There is nothing to install to get a window on screen, and apps move between machines and operating systems without surprises. This is the single biggest reason myTk is built on Tk rather than Qt or wxWidgets.
- G2 — Bring modern macOS/Cocoa patterns to Tk.
Raw Tkinter scatters behaviour across loose callbacks. myTk borrows the patterns Apple matured over decades — Key-Value Observing (binding, bindable),
NSNotificationCenter(notifications), and delegation — because they organize event-driven code far better than ad-hoc callbacks.- G3 — Encapsulate, but never hide.
Every Tk widget is wrapped in a
Viewthat exposes convenient behaviour, yet the underlying Tk widget is always reachable via.widgetfor anything myTk does not cover. You are never boxed in and limited.- G4 — Remove friction.
Where Tk forces long verbose text for no reason, myTk supplies a sensible default and lets you override it. For example, Tk requires you to name a widget’s parent at creation; myTk does not. Complex widgets like
TableViewwork out of the box and let you refine behaviour through a delegate rather than wiring callbacks by hand.- G5 — Keep useful but less portable features optional.
3D rendering (
View3D) and OS drag-and-drop pull in large or platform-specific dependencies. These are loaded on demand and degrade gracefully: if the dependency is absent, the feature reports itself unavailable and the rest of the app keeps running.
Architectural overview¶
myTk is a thin, layered wrapper around Tk. From the bottom up:
Bindable— the foundation. A property-observer and two-way binding mechanism with no Tk dependency of its own.Base— combinesBindablewith two mixins (EventCapable,DragAndDropCapable) to give every widget a uniform interface for state, grid geometry, event binding and lifecycle.Viewand its many subclasses — everything visible on screen (except the window) is aViewthat wraps one Tk widget.AppandWindow— the application controller and the top-level container.
The complete class hierarchy (regenerate any time with python -m mytk -c,
which emits this Graphviz source). Solid arrows are subclassing; dashed arrows
are the mixins folded into Base and App:
myTk class hierarchy (python -m mytk -c)¶
Note that App is not a Base/View: it is a
controller, not something drawn on screen, so it derives from Bindable and
EventCapable directly. Likewise TabularData is a
pure model and derives only from Bindable.
Core mechanisms¶
These six mechanisms are the framework. Everything else is widgets built on top of them.
Binding and property observation¶
Bindable implements a Property-Value-Observer pattern
inspired by macOS Key-Value Observing. Any object can observe a property on
another object, and two properties can be bound so that changing one always
updates the other, whether the change comes through the interface or from your
own code.
add_observer()— register interest in a property.bind_properties()— keep two properties synchronized in both directions.bind_property_to_widget_value()— synchronize a model property with what a widget displays.
It works uniformly on plain Python attributes and on Tk Variable objects,
so reactive GUIs do not require you to think about StringVar/IntVar
plumbing. This is the mechanism to reach for when two pieces of state must stay
equal.
Notifications¶
NotificationCenter is a singleton providing
one-to-many, decoupled communication, modelled on NSNotificationCenter. A
notifier posts a named notification without knowing who, if anyone, is
listening; observers subscribe by name.
Notification names must be defined as
enum.Enumsubclasses, which keeps them discoverable and typo-proof.
Use notifications (rather than binding) when the relationship is one-to-many or
when the sender should know nothing about the receivers — e.g. a device
announcing will_move / did_move to whatever cares.
Delegation¶
For complex widgets, myTk prefers a delegate object over a pile of callbacks.
The widget implements sensible default behaviour and calls optional methods on
your delegate when it needs a decision. TableView is
the canonical example; a delegate may implement any subset of:
selection_changed(event)click_header(column)— default: sort rows by that columnclick_cell(item_id, column_id)— default: open the value if it is a URLdoubleclick_header(column)/doubleclick_cell(item_id, column_id)
This keeps related behaviour in one cohesive object and lets you override only what you care about.
Events and scheduling¶
EventCapable is a mixin (on both Base and
App) for timed callbacks and event wiring:
after(),
after_cancel() (and after_cancel_all),
bind_event(), and
event_generate(). It exists so that
timer and event management is consistent across widgets and the application
object, which otherwise handle their Tk widget very differently.
Configuration¶
Scientific applications are full of adjustable parameters — exposure time,
gain, wavelength, thresholds, paths. Configurable
turns a declared set of typed, validated properties into a working settings
dialog automatically:
ConfigurableProperty(and theConfigurableStringProperty/ConfigurableNumericPropertysubclasses) declare a value, its allowed type/range, and know how tosanitize()and validate it.Configurableaggregates them and exposesis_valid(),update_values(), andshow_config_dialog().ConfigModelandConfigurationDialogbuild and present the UI, so you never hand-write a settings dialog or its validation logic.
Drag and drop¶
OS-level file drops are not part of core Tk. DragAndDropCapable
is a per-widget mixin (mirroring EventCapable) that adds
accept_dropped_files(); the
low-level work of loading the tkdnd extension and parsing the payload lives
in mytk.dnd. The dependency (tkinterdnd2/tkdnd) is installed on
demand the first time it is used. If it cannot be enabled,
is_drag_and_drop_available()
returns False and the app continues without drops.
Data layer (model/view)¶
myTk separates tabular data from its display in a small MVC arrangement:
TabularDatais the model — an ordered, observable collection of records (append_record(),insert_record(),update_record(),records_as_namedtuples(), …). It derives fromBindable, so changes propagate automatically.TableViewis the view — it observes aTabularDatasource and reacts throughsource_data_changed(),source_data_added_or_updatedandsource_data_deleted, while a delegate customizes interaction.
Editing a cell, sorting, resizing columns and following URL cells are handled for you; the model stays the single source of truth.
Visualization¶
Figureembeds a Matplotlib figure (provide your ownplt.figureor use the one it creates) with an optional toolbar.View3Dembeds a 3D mesh viewer. It loads GLB/GLTF/OBJ/PLY/STL viatrimesh, renders off-screen (drag to orbit, scroll to zoom), and picks its backend automatically:pyrenderif available, otherwise a built-inmodernglrenderer. You may instantiateView3DPyrenderorView3DModernGLdirectly. Like drag-and-drop, the heavy dependencies are optional and loaded on demand.
Layout model¶
Positioning is the part of Tk that confuses newcomers most, so it is worth
stating plainly. Tk offers three geometry managers — grid, pack and
place — and myTk standardizes on grid. A view is divided into a grid of
rows and columns, and children are placed into cells with grid_into.
Key levers:
Resizing is governed by per-row/column
weight(which cells absorb extra space) and by each widget’sstickyoption (whether the widget grows to fill its cell).grid_propagatecontrols whether a container resizes to fit its children or holds a fixed size.rowspan/columnspanlet a widget occupy a range of cells.
A View can itself contain a grid, so a single grid cell can hold a
self-contained sub-layout. Recommended background reading: the
pythonguis.com grid/pack/place FAQ and
TkDocs.
Conventions & idioms¶
“View” means anything visible on screen except the window. The window is a
Window; everything inside it is aView.The ``.widget`` escape hatch. Reach through any
Viewto its raw Tk widget when you need behaviour myTk does not implement.Mixin composition. Cross-cutting capabilities (events, drag-and-drop) are small mixins folded into
Baserather than inheritance trees.Enum-named notifications. Notification identifiers are
Enummembers, not strings.Delegate methods over callbacks for non-trivial widgets.
Extending myTk¶
The fastest way to learn the extension points is to read
mytk/example_apps/ (mytk.py, lensviewer_app.py, filters_app.py,
microscope_app.py, dnd_app.py, view3d_app.py). In short:
A new widget subclasses
Base(or an existingView) and implementscreate_widgetto build its Tk widget.Custom table behaviour is added by setting a delegate on
TableView, not by subclassing it.A new notification is a new
Enummember posted through the sharedNotificationCenter.A new 3D backend follows the
View3DPyrender/View3DModernGLpattern.
A minimal application is just:
from mytk import App, Label
class MyApp(App):
def __init__(self):
super().__init__(geometry="600x400", name="MyApp")
label = Label("Hello, myTk")
label.grid_into(self.window, row=0, column=0)
MyApp().mainloop()
Design decisions & trade-offs¶
- Why three coordination mechanisms (binding, notifications, delegation)?
They solve different shapes of problem and overlap little. Binding is for “these two values must stay equal.” Notifications are for one-to-many, sender-knows-nothing broadcast. Delegation is for “this widget needs a decision from its owner.” Forcing all three through a single callback style is exactly the tangle myTk set out to avoid.
- Why Tk rather than Qt/wx?
Portability and durability. Tk is in the standard library, so there is nothing to install and apps survive moves between machines and OSes. The author’s experience is that Qt, while powerful, is heavier than most scientific apps need and more fragile to transport. Encapsulating Tk turned out easier than simplifying Qt.
- Why optional, on-demand dependencies?
3D rendering and OS drag-and-drop are valuable but expensive (large or platform-specific packages). Making them hard requirements would tax every user for features most do not need, and would undermine G1 (stay in the standard library by default). On-demand loading with graceful degradation keeps the baseline install tiny.