Version 17 of Actions

Updated 2004-05-26 03:22:34

Bryan Oakley - 20-May-2004

An action is a pseudo-object that has properties that mostly resemble procs. In addition, actions have state and are generally associated with one or more widgets. The main advantage an action has over a proc is that it "knows" which widgets it is associated with and can enable or disable all of the widgets when the action itself becomes enabled or disabled.

Bryan Schofield posted to comp.lang.tcl a nice example of one implementation of actions [L1 ]. The use of actions is also mentioned at Tk coding styles and philosophies.

Bryan Schofield -- 24 May 2004

Here's the source for my implementation of an action package: ActionPackage and ActionPackageDemo

Following is a quick example of how actions can be used, using a simpler implementation than the one described by Bryan Schofield. This comes from the implementation I presently use (but is not yet publicly available). It's only 177 lines of code and is very straight-forward; I generally advise people to create their own implementation so they can appreciate how it works.

    button .cut -text Cut -command [list action invoke cutAction]
    .menubar.editMenu add command -label "Cut" -command [list action invoke cutAction]
    .popup add command -label "Cut" -command [list action invoke cutAction]

    action associcate cutAction .cut [list .menubar.editMenu "Cut"] [list .popup "Cut"]

    bind all <Control-x> [list action invoke cutAction]
    ...
    action define cutAction {} {
        <code to cut the selected text>
    }
    proc toggleSelection {} {
        # called whenever the selection changes
        if {<there is a selection>} {
            action enable cutAction copyAction
        } else {
            action disable cutAction copyAction
    }

Notice that the use of actions adds only one extra line of code in your application (the line that begins "action associate"), and is actually a net loss of several lines of code. Without actions you might have to code toggleSelection as:

    proc toggleSelection {} {
        if {<there is a selection>} {
            .cut configure -state normal
            .menubar.editMenu entryconfigure "Cut" -state normal
            .popup entryconfigure "Cut" -state normal
        } else {
            ...
        }

Not only that, but without actions you also have to add code to make sure that if there is no selection that the procedure that does the cutting won't do anything if called via the accelerator.

As you can see, actions make it much easier to maintain a consistent state across all widgets that implement a particular functionality. You only have to make a single function call to disable all controls that invoke the particular action. In addition, should you add new controls (speech recognition, gestures, additional buttons or menus, etc) you do not have to hunt down all the places where you might need to enable the widgets; associate the new control with the action and the action takes care of the rest.

In addition to the sheer convenience of actions, they also provide a nice API into your application, making your scripts scriptable. The scripts can be used to implement user defined macros, automated tests, or even compound actions.

The above is just one sort of implementation. Many people might prefer a more object oriented approach. For example:

    new Action cutAction {...}
    cutAction button .cut -label "Cut" ...
    cutAction menuitem .menubar.editMenu "Cut" ...
    cutAction menuitem .popup "Cut" ...
    cutAction accelerator <Control-x> .

No matter how actions are implemented, it's the concept that is important. Pick a style that you like and write an implementation. The first time I wrote an action library (and I'm on my 3rd or 4th iteration as I don't take code with me from employer to employer) I not only gave actions state, but I made the action definition responsible for the labels and images associated with the action. The theory was, if I wanted to change all "Cut" widgets to say "Slice" I could do "cutAction configure -label Slice" and they all would do just that.

Over time I realized that most menu items and buttons don't change their text once created so I abandoned that idea and eventually whittled my personal action library down to just a handful of lines of code.


NEM 22 May 2004: This is a really excellent idea. Perhaps you would consider adding your code to tklib? I can see a common application would be to keep toolbars and menus in sync. With that in mind, would it be feasible to go one step further and have the action command actually create the toolbutton and menu item? Maybe something like:

 action .edit.cut -state normal -label "Cut" -underline 0 -accelerator Ctrl-C \
     -command ... -image CutImg
 .edit.cut configure -state disabled

This would actually create two widgets and a "pseudo-widget" which just forwards on requests to the actual widgets. I can see this being perhaps a bit less flexible than the above approach, though. Also, the widget path would represent the grouping of the action (i.e. the above would create a menu item in the Edit menu, and a toolbutton grouped with other Edit actions), rather than the position in the widget hierachy, so perhaps a different separator to "." would be better. Definitely a cool idea though.

Bryan Schofield 24 May 2004 The problem with having actions create widgets is that you are actually limiting the action's usability. You are enforcing the action be tied to a menu and toolbar, when in fact it need not be applied to either. My implementation, ActionPackage is purposely made very generic where the action can be applied to any class of widget, even custom megawidgets, through the use of applicators. It's my thought if you wanted to create an action and at the same time create menu's, buttons, toolbar entries, or whatever, that you create a small helper proc:

  proc myAction {actName subpath args} {
       eval action::create $actName $args
       button .buttonFrame.$subpath
       action::apply $actName .menubar.$subpath .toolbar .buttonFrame.$subpath
  }

Using this approach you don't tie higher level functionality to a lower level package.

Bryan Oakley 25-May-2004: agreed. While actions can be tied to widgets, they don't have to be. A good implementation of actions, IMO, should allow actions to stand alone or be tied to almost any sort of object. With the code I use now I have the restriction that anything associated with an action must have a "configure" subcommand with a "-state" option, but even that restriction doesn't necessarily need to be there.

I also find it useful to use actions as an API for the GUI. Even without actions tied to widgets it's possible to use actions to write a script that manipulates data just as if the user were clicking on buttons.


PWQ 26 May 04, Also with regard to Tk coding styles and philosophies, it would seem that actions are infact a counter measure to make up for limitations in Tk widgets.

While -textvariable is fine for simple labels and simple entry fields, a real UI requires much finer control. This means that for most code, changing a varaible is not enough to effect the stylistic changes desired.

A proactive change would be to allow attaching variables to all widget properties, thus simplifing a grey out state change to:

     set State4CutCopy disabled

Assuming that widgets could be configured as:

        label .a.b.c.l1 -state @State4CutCopy
        frame .a.b.c.f2 -state @State4CutCopy
        label .a.b.c.f2.k1 -state @State4CutCopy
  (In the above example, the @ notation is used to save creating -xxxvariable option for each current option)

Category Actions | Category GUI