[Tkinter-discuss] Tkinter + MVC

John W. Shipman john at nmt.edu
Sun Jun 16 04:50:22 CEST 2013


On Sun, 16 Jun 2013, Bhaskar Chaudhary wrote:

+--
| I am trying to work out an MVC structure for a tkinter program.
+--

I hope you'll excuse a philosophical digression first, but later
in this post I'll address your problem.

I've never really understood the MVC structure, and I would be
most grateful if somebody here could explain it.

I'll use as my example an online boardgaming system.  Suppose an
online game has two kinds of participants: players and
spectators.  Spectators can't do anything but watch.  Players can
make moves but the rules of the game itself restrict what choices
they can make.

Here, the M part is clear to me: the model encapsulates and
enforces the workflow rules.  It keeps track of the actual state
of the gameboard, accepts commands from the players, and accepts
queries from players or spectators about the state of play.

The part of MVC that isn't clear to me is the distinction between
view and controller.  The view can query the model and display
things, but it can't change the state of the model.

But what defines the controller?  Clearly, it can affect the
model, but doesn't it also have to have query functions?  A
player can't figure out what move to make without view-type
functions to see the state of the gameboard.

In any case, I've found it profitable in my designs to think of
it as just MC: there is a model, and it may have one or more
types of controller that have different access rights.  Maybe I
don't understand what a view really is, but I think of it as a
controller that doesn't have write access to the model.

In the case of a Tkinter GUI, the model is typically some class,
and the controller is the Tkinter logic that lets the user see
and possibly also change the state of the model.

I'm getting close to finishing a modest-size project (maybe 100
widgets), but you are all welcome to look at it (and critique
it):

     http://www.nmt.edu/tcc/projects/cmsadds/

I prefer to use a stepwise refinement approach where I group
related widgets in a class that acts like a widget, specifically
like a Frame.  The application is the class of the whole; it is
divided into subwidgets and sub-subwidgets and so forth until we
get down to actual Tkinter widgets.  So in the example, there's a
class App that is the application as a whole.  Some of its
components are basic widgets like Label, but it has two major
divisions called ReqPanel and SwapArea.  ReqPanel in turn
contains some basic widgets and some groupings like SectionList
and SemesterPicker.

For control linkages, when one of the major subdivisions X of the
application needs to do something in another of the major
subwidgets Y, there are two approaches:

1.  Class X has a 'callback' argument, a function that will be
     called when the user does something to X that needs to do
     something to Y.  Class App creates an instance of X, and
     specifies as its callback a method of App that in turn calls
     the appropriate method of Y.

2.  Class X has some control, like a button, that needs some
     action in Y.  In this case, the command handler for that
     control calls a method of App that in turn calls the
     appropriate method of Y.

I'm following the Law of Demeter here:

     http://en.wikipedia.org/wiki/Law_of_Demeter

Although these pass-through methods in the App class are short
and seem silly, what I'm trying to avoid is the situation where a
widget has to know about classes that are not its immediate
parent or child widget.  In my early attempts at larger Tkinter
applications I had the problem that too many widgets needed to be
able to refer to too many other widgets, leading to an
object-oriented version of spaghetti code.

Following the Law of Demeter, a class that represents a
functional grouping of widgets has to know only about the class
that encloses it, and any contained groupings that it encloses.

Getting back to Bhaskar Chaudhary's original question, I would
suggest a parent class containing both the controller and the
view.  I always put the call to .bind() adjacent to the code
that creates the widget.  It's always bound to another method
of the same class.

The event handler has three ways to change the state of the
application:

  -  For widgets that are in the same class as the handler, it will
     use their methods directly.

  -  For a child widget grouping, it calls a method on that
     grouping.

  -  For actions not in that class or its child widgets, it calls a
     method on its parent class, which propagates the request
     through these same three choices until it reaches the class
     that contains the actual basic Tkinter widget.

Thanks to anybody who got this far.  Today's Young People (I'm
63, so that does not include me) have a popular acronym 'tl;dr'
(Too Long, Didn't Read).  Beats me how they cope with complex
technology with that attitude.

I would greatly appreciate any comments or suggestions, but this
is how I do it.

Best regards,
John Shipman (john at nmt.edu), Applications Specialist
New Mexico Tech Computer Center, Speare 146, Socorro, NM 87801
(575) 835-5735, http://www.nmt.edu/~john
   ``Let's go outside and commiserate with nature.''  --Dave Farber

P.S. Last winter I updated our local Tkinter reference to 8.5 and
included the new ttk "themed widgets".  If you are still using
the old 8.4 version, please refresh your link to:

     http://www.nmt.edu/tcc/help/pubs/tkinter/

P.P.S. The 'cmsadds' project referenced above exemplifies a new
approach I've been using to handle editing of XML through a
Tkinter interface.  For XML processing, I used to use lxml to
read the document, convert it into appropriate internal data
structures, and then re-serialize it back to XML after
modification.

In this project, my 'model' is a set of classes that inherit from
etree.Element, so the current values of things are represented as
XML elements, attributes, and textual content.

For example, if I need to edit an XML attribute value, my Tkinter
application has a corresponding Entry widget.  When the XML
element is brought up for editing, the attribute value is copied
into the Entry's control variable.  The Entry widget has a
binding to the <FocusOut> event that validates the text and then,
if valid, stores it into the XML attribute.

When it's time to write the XML back, that's one method call:
etree.ElementTree.write().  Based on two small projects, it makes
for a lot less code.


More information about the Tkinter-discuss mailing list