Model / View / Controller

Model-View-Controller [L1 ] (often abbreviated "MVC") is a way of working with GUIs that provide access to complex data, and it seems to scale really quite well indeed. You do it by separating the code to implement the Model (your underlying data is, say, an updatable list or a read-only tree) from the View (how your data looks when drawn on the screen) and the Controller (how your model responds to updates to the view) though often the view and controller are joined together.

Some features of Tk operate in this way; notably the -variable option to entry, the -textvariable option to label, button et al, and the -listvariable option to listbox.

It's wise to design the rest of your interface that way as well. The trace command (particularly [trace variable]) is very useful for implementing triggers that update a view when the underlying model changes. The bindtags command is useful for associating a widget with one or more controllers other than the standard ones that Tk provides; occasionally, you also need to resort to overloading a widget command. For a worked example, see the page entitled, "Text variable for text widget". In that example, the widget command is overloaded to notify the model when the user attempts to change the text contents. A trace notifies the view when the model is updated. A bind tag is also used so that everything gets cleaned up correctly when the window is destroyed.


See a tiny example at a little multiplication toy, a slightly bigger one at Revello


MVC is starting to accumulate heterodoxy, including "MVC considered harmful" [L2 ], ...

EKB That's an interesting article. It's about an alternative approach called "context-oriented programming". I made a short example on ContexTcl: Playing with Context-oriented Programming.


PYK 2014-05-20: One possibly-useful guideline is that what to display belongs in the model or the controller , and how to display it belongs in the view . This guideline helps keep program logic from creeping into the view .


EKB One way to use the MVC pattern that I apply frequently is to make a core program with programmatic hooks and no user interface elements at all. After making it, I build a command-line program that sources a script that accesses the model. Basically, this means using it as a mini-language, which I think is a particularly Tclish (and Lispish) thing to do. Often the development stops there, because it's all I need, but it doesn't have to. Because it uses the MVC pattern, it would be easy to make a Tk interface that makes use of the core program. There's an example at Mini-language as Model-View-Controller.

Lars H: Hmm... As used above, "mini-language" seems almost a synonym of "package" (although perhaps not registered via the package mechanism). Is there some other distinction between the two that you feel is noteworthy but perhaps not immediately obvious, or is it just a matter of terminology? One distinction could be that a package is often set up to support multiple clients (if it keeps state, then the client might need to provide a handle to the particular context to use) whereas a mini-language has a global context shared by all (at least in that interpreter), but that could just as well be irrelevant.

EKB Not in terms of the code, but in terms of presentation and use, yes. On one hand I find that if I think about a package as a mini-language then I implement the package and the application somewhat differently. On the other hand, I find that exposing a package as a mini-language (or "domain language") and then presenting it to users that way, without ever mentioning Tcl, leaves them very impressed. It's still good advertising for Tcl/Tk, because later when they ask about it, I say, "Oh, yes, that's quite straightforward in Tcl." This ties into two things that I see in software development discussions: a) the use of domain languages as a strategy (highly recommended in the popular "Pragmatic Programmers" books); b) Lisp as the natural platform for making mini-languages (although not by the Pragmatic Programmers -- they like Ruby). But Tcl is also a natural platform for it. As I was thinking about the mini-language code I've written, I realized that it might be possible to add a (c) to this list, that a mini-language is a natural way to implement MVC that is (IMHO), more transparent and easier to implement than implementing MVC in, say, Java.

So, the crass view of saying "yeah, it's just a package, but..." is that it's a way to do some Tcl evangelism using some popular concepts: domain language and MVC. Something like, "TCL can implement MVC via a domain language at a lower TCO than alternatives and with lower barriers to entry." That sort of thing. The non-crass view of it is that thinking about an existing strategy in a new way affects my overall development process.


WWS 2009-04-24

I am no expert, but when I write a GUI app I structure in a sort of "Micky Mouse" MVC (MMMVC?) I structure it the following way, both mentally and in terms of laying out the code (e.g., in the program text I set off these sections clearly with comments.)

  • SECTION 1, Model Definition: First I have a single array variable that I usually call "MODEL". This has all the data, names of databases, etc that might get passed around. DB connections etc are not made yet, though. There will usually be an element called FINIS (e.g. set MODEL(FINIS) 0); I will vwait on this being changed to end the script event handling. I use CAPITALS for sort of global variables that hold state like MODEL.
  • SECTION 1.5, Utility Procs: We all know what these are -- something that uppercases something, etc.
  • SECTION 2, Model Updating Procs: I then write proc's that access MODEL via an upvar statement. I do the upvar thing because (1) globals really, really annoy me and (2) it makes the procs easier to unit test. These proc's are used by the various widgets as commands (e.g. button .b1 -command {addOne MODEL}). Callbacks for fileevents get defined here too. One of these must update MODEL(FINIS) in order to exit the script.
  • SECTION 3, Traces: Here go trace commands on the MODEL elements that update stuff. Usually they update various "views" of the data that are displayed by widgets when MODEL elements change. For example, if someone pushes a button, which in turn updates a counter, there might be a trace on that counter to redisplay something in a text widget.
  • SECTION 4, Widgets and Inputs (event handlers): Finally I define the widgets using the "model updating procs" defined earlier as their commands, and usually pack them along with their definitions. Also, sockets or fileevents get set up here, with callback procs defined earlier. Widgets and callbacks NEVER cross update each other or any globals -- they update the MODEL through upvars, traces on the model then percolate the changes down to other widgets; this avoids headaches for me.
  • SECTION 5, Execution and Loop: Finally we get to do something! Here is where real control starts. Since I usually run my scripts from the command line, I go ahead and use parameters passed in through the shell. Generally this section goes: check parameters, open stuff like databases, vwait on MODEL(FINIS) to handle events, after returning from vwait clean up and exit.

I will post an example someday.

I look forward to comments!

--- APE 2020-03-12 I also always doubt about how to build a Tcl application, when it begins to have several complex widgets, interactions and a complex data model. Tcl/Tk is intrinsectly driven by MVC pattern and I try to mix WWS above SECTIONS and a MVC model from one which is quite well explained : pureMVC . The result hereafter is a tentative to write an example which comply with these both sources, and take benefit from the language mechanisms.

# --------------------------------MODEL----------------------------------------
# (+ data object)

namespace eval Model {
        # SECTION 1
        # pureMVC data object (here simple variable)
        variable myData ""

        # SECTION 2
        # pureMVC proxy (one proxy per data object)
        proc proxy.setData {val} {
                variable myData
                set myData $val
        }
        proc proxy.getData {} {
                variable myData
                return $myData
        }

        # SECTION 3
        # pureMVC notification send (proxy cannot receive notifications = no bindings)
        trace add variable Model::myData write "Model::proxy.dataModified"
        event add <<dataModified>> virtual
        
        proc proxy.dataModified {args} {
                puts "proxy generate <<dataModified>> event with data [Model::proxy.getData]"
                event generate . <<dataModified>> -data [Model::proxy.getData]
        }
}

# ---------------------------------VIEW----------------------------------------
# (+components)

# SECTION 4
# pureMVC mediator
# one component per widget
namespace eval View {
        proc mediator.component.createView {} {
                puts "Create View component"
                pack [label .l -text [Model::proxy.getData]]
        }
        proc mediator.component.setLabel {body} {
                if {[winfo exists .l]} {
                        .l configure -text $body
                }
        }
        # pureMVC mediator listNotificationInterests + handleNotification (bindings)
        bind . <<dataModified>> "View::mediator.component.setLabel %d"

        # SECTION 5
        # mediator command register, associated to view and notification
        # primary mediators notification handling on keyboard and mouse events
        # other mediators notification handling can be defined with bind to virtual events
        bind . <Key> "Controller::setCommand %K"
}

# SECTION 5 (continued)
# ------------------------------CONTROLLER-------------------------------------

# pureMVC commands (simple and macro commands) through Controller
namespace eval Controller {
        proc startupCommand {} {
                puts "Controller startup"
                Model::proxy.setData 0
                View::mediator.component.createView
        }
        proc setCommand {val} {
                Model::proxy.setData $val
                # might broadcast other notification : event generate ... to perform other actions
        }
}

# pureMVC facade 
event add <<Startup>> virtual

# events binds to . so wait visibility
tkwait visibility .

# facade init actions
bind . <<Startup>> "Controller::startupCommand"
event generate . <<Startup>>