uniquename 2012sep18 - A Canonical Structure for Tk Code --- and variations
In essentially all of my posts of Tk code on this site (for various utilities), I have used a 'canonical' structure for the Tk code --- as follows:
-1) Put documentation comments in the script from the very start of creating the script. 0) Set general window parms (win-name, win-position, color-scheme, fonts, widget-geometry-parameters, win-size-control, text-array-for-labels-etc). 1a) Define ALL frames (and sub-frames, if any). 1b) Pack ALL the frames and sub-frames. 2) Define & pack all widgets in the frames --- typically going top-to-bottom and/or left-to-right, frame by frame. After ALL widgets are defined for a frame, pack the widgets in the frame. 3) Define key and mouse/touchpad/touch-sensitive-screen 'event' BINDINGS, if needed. 4) Define PROCS, if needed. 5) Additional GUI initialization (typically with one or more of the procs), if needed.
And to cover the case when I find that I need to make a new widget (from existing widgets), via a proc, then I insert the step:
1c) Define a 'new-widget' proc that is used to make one or more widgets in step 2.
I find this structure helps
As I mentioned at the top of one of my first code-postings here A non-obfuscated color selector GUI:
"Way back around 1998 when I was first learning the Tcl-Tk language, I would do web searches on keyword strings such as 'usr bin wish button' or 'usr bin wish scale rgb' to find examples of complete Tcl-Tk scripts --- because the code 'snippets' in Tcl-Tk books and tutorials were not very helpful in creating 'production' scripts (relatively quickly)."
I was always amazed (and dismayed) at the variety of coding styles that I found --- many of the authors apparently coming from the C-programming world and putting all their code in procs at the top of their scripts --- sometimes even putting the code defining the widgets for the Tk GUI in a proc --- and putting a single-word line at the bottom of their scripts calling a proc named 'Main' or 'main' or 'doit' or the like.
Others seemed to follow a 'whatever works' or 'stream of consciousness' coding method. From the same author, one script might have the Tk widget definitions at the top of the code --- another time in a proc in the middle of the code --- another time in statements at the bottom of the code.
That is all OK when you are writing code that is about a paragraph (or two or three) long. But I found that when I was writing my Freedom Environment code [L1 ] and was aiming for contributing the code to the public, I needed to come up with some Tk coding-structure standards before publishing the code to the world.
So I was strongly motivated to come up with a 'canonical' coding structure to use for Tk scripts in my Freedom Environment software --- as well as for the 2 reasons mentioned above --- (1) facilitation of the coding-and-testing process and (2) facilitation of finding useful code snippets in my own code (code re-use).
____________
Ousterhout's application of Occam's Razor:
There is no obvious event-handling loop in Tk scripts. At the bottom of my posting YAFSG - Yet Another Font Selector GUI, I gave praise to Ousterhout's wonderful implementation of Tk --- and how he has spared us programmers from writing superfluous code --- over and over and over. The hidden event handling loop is one of the wonderful decisions that he made --- along with 1000's of others.
Just look at the typical C or C++ program that has an event handling loop for
- window-manager events, or - OpenGL events, or - SDL events (where SDL = Simple DirectMedia Layer --- a library that provides low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D framebuffer across multiple platforms).
Here are a couple of examples:
while(!handler.quit) { if(handler.redraw) { redraw(); surface.swap(); handler.redraw = false; } // Wait for events and call event handlers window.wait(); // wait for events (using Display::wait) }
Here's an example from a game-programming site.
while ( game should run ) { ... renderFrame() // OpenGL frame drawing code goes here handleUserInput() ... }
In a Tk script, we never have to put loops like that in our code to handle the events specified in our 'bind' commands or in the bindings that are built into the Tk widgets.
Furthermore, we are not required to put a 'main' routine in every one of our Tk scripts.
Ousterhout apparently had the vision to see that the event-handling-loop (and the 'main' routine requirement) could be handled by the 'wish' interpreter and not by the programmer.
Thank you, Ousterhout.
By the way, I mention the hidden event handler because it is helpful to know what is going on 'underneath the covers' in order to make some sense out of what is possible and what is not possible when structuring one's Tk code.
______________
Variations on the Canonical Structure Above:
Experienced Tk scripters may have noticed that the sections in the canonical layout above can be put in almost any order --- with the exception of steps 1c (defining-widget-making procs) and 5 (calling-additional-GUI-initialization procs).
For example,the BINDINGS section can go almost any place, top to bottom.
This is because the 'bind' statements are not used until 'wish' drops us into the event-handling loop.
And all the window-and-frame-and-widget defining-and-packing --- steps 0, 1, and 2 --- those code sections can be placed almost anywhere, top to bottom --- because these definitions and packing are, typically, not required by any other section of code (procs and bindings) until 'wish' drops us into the event-handling loop and starts using the procs and bindings.
_____
Some people might want to put BINDINGS below PROCS. The main reason that I put BINDINGS first is that most of the code-detail is in the procs. I prefer saving the detail for last --- or as close to last as possible.
Since I typically use one or two procs in the PROCS section to do the 'additional-GUI-initialization', the code in that last section will 'throw an error' if the proc is not available.
So I DO need to keep the PROCS section before the 'additional-GUI-initialization' section.
Nicely enough, the code in the BINDINGS section does not 'throw errors' if the procs that are used in bindings are not defined yet (i.e if the procs are not above the 'bind' statements). So I can put the BINDINGS section almost anywhere.
Similarly, the 'wish' interpreter does not object if you use a proc in a '-command' option of a widget definition statement and the proc has not been defined yet (as the interpreter 'assimilates' the statements of the script, from top to bottom). So widget definitions (and packing) can go almost anywhere in the code --- that is, those statements can be placed above or below the PROCS and BINDINGS sections.
________
Summary (and conclusion):
In summary, I could try some other orderings of the 'canonical' structure at the top of this page. But that ordering has worked for me in making over a hundred Tk scripts. So I think I will stick with that for now.
One 'improvement' (that I see at this time - 2012sep), that I might add at some point, relates to the intialization of variables used in the various widgets of a GUI.
Sometimes I find that I would like to go back and change some intial settings for widgets --- like entry fields, checkbuttons, radiobuttons, scale variable, initial listbox selection, etc. --- either temporarily during testing or just before 'release' as I settle on appropriate initial settings.
Currently, I tend to put the initial values for widgets just before the definition of each widget. This scatters the initializations throughout code section 2, which can be a quite extensive section.
Instead, I may someday break section-2 into two sections:
2a) Define & pack all widgets in the frames. 2b) Set initial values for the widget variables.
____
uniquename 2012oct28 update - initializing application/widget variables :
I have recently found that it is most helpful to put the section labelled (2b) above down in the section 5 - the 'additional GUI initialization' section.
In fact, I have found that for some GUI's it is helpful to have multiple sets of initial values --- to be used as 'use-cases' for testing the Tk script under a variety of conditions, simulating various possible user inputs.
For example, in testing a plot utility with entry fields for titles, x-axis data range, y-axis data range, x-axis data, y-axis data, and tic-mark parameters, one can have many sets of data/parameters with which to test the Tk scripts.
Just put each set of data/parameters in an
if {0} { .... }
clause, and change the zero to a one for the set of data/parameters that you would like to test with next. Then start up the Tk script.
So in the future, I will probably be putting statements that 'set initial values for the widget variables' down at the bottom of my Tk scripts --- in so-called section 5.
Conclusion:
I have been a quite 'happy camper' so far with the canonical structure shown at the top of this page. It helps my productivity tremendously, while being quite flexible --- thus appealing to my appreciation of freedom in essentially all things.
uniquename 2012oct14 update - comments on documentation :
Recently I ran across an Ousterhout document that reminded me that I should have put a 'step-minus-one' before 'step-zero' in the canonical code-structure list at the top of this page.
'Step-minus-one' is: "Put documentation comments in the script from the very start of creating the script." My reasons for that are the same ones that Ousterhout outlined in his September 1994 document 'Tcl/Tk Engineering Manual' that he authored when he as at Sun Microsystems.
In particular, I am referring to section 6.5 of that document --- titled 'Document as you go'.
I think it is worthwhile to reproduce the text of that section, here:
"It is extremely important to write the documentation as you write the code. It’s very tempting to put off the documentation until the end; after all, the code will change, so why waste time writing documentation now when you’ll have to change it later? The problem is that the end never comes – there is always more code to write. Also, the more undocumented code that you accumulate, the harder it is to work up the energy to document it. So, you just write more undocumented code. I’ve seen many people start a project fully intending to go back at the end and write all the documentation, but I’ve never seen anyone actually do it.
If you do the documentation as you go, it won’t add much to your coding time and you won’t have to worry about doing it later. Also, the best time to document code is when the key ideas are fresh in your mind, which is when you’re first writing the code. When I write new code, I write all of the header comments for a group of procedures before I fill in any of the bodies of the procedures. This way I can think about the overall structure and how the pieces fit together before getting bogged down in the details of individual procedures."
Those are my feelings exactly.
There are drawbacks. The main one that I encounter is that during the testing and debugging phase, I frequently revise some of the GUI design, change some of the bindings, and/or change the procedures (including adding new procs or consolidating previously written procs). As a consequence, some of my original documentation is no longer quite correct --- and frequently I forget to change all affected parts of the documentation.
But I would rather err on the side of having some slightly-out-of-sync documentation than have no documentation at all.
uniquename 2012nov09 update - on internationalization :
One new thing that I have added is the 'text-array-for-labels-etc' item in section 0. Using 'set' statements for an array of text items (I use variable name 'aRtext') , located all together, in one section of the code, can make it easier for people to internationalize my scripts. I may be using a text-array like this in most of my scripts in the future.
uniquename 2013aug24 update - ON NAMING.
Anyone who has seen my script donations on the wiki may have noticed that I consistently use the prefix .fR for names of frames. This is because many of the times that I looked at other peoples' code, when I saw a name like '.c' I would generally have to look through the code to see if that name was referring to a frame --- or was it a widget in the window? I find that it is much nicer to see a widget name and know immediately, from the name, that the widget is a frame --- or a non-frame, if the name does not start with '.fR'.
(I originally tried 'FR' but found that 'leading' capital letters for widgets result in errors because the name clashes with the fact that 'class names' for widgets are the same name, but starting with a capital letter. '.fR' works fine for me.)
I have slowly over the past several years been developing other naming conventions that I like to use, and here are some that I have started using in the last several months:
** I have started using the prefix TAG for all tag names. With all sorts of names populating Tk scripts, it is helpful for me to see a name starting with 'TAG' and know RIGHT AWAY (without scanning back and forth through the code) that the named item is a 'tag'.
** I have started using the prefix VAR for the variable used by a scale widget or a set of radiobutton widgets. Again, with so many names of various types within a large Tk script, it is helpful to know right away that the name is the name of a variable --- probably a variable related to a 'scale' or radiobuttons.
** I have started using the suffix 0or1 for the variable used by a checkbutton widget. For example, for a checkbutton that is used to indicate whether outlines should be drawn around 'oval' or 'rect' items drawn on a Tk canvas, I would use a name like 'outline0or1'. I know right away when I see that '0or1' suffix that it is a variable associated with a checkbutton on the GUI.
** For array names I have started using a prefix of aR --- and for lists, I have recently started using a prefix of LIST.
** When I define a label widget, I prefix the name with .lab or .label --- examples: .labelINFO, .labCOLOR1, .labCOLOR2. Then I know right away, without looking through the code, that I am dealing with a label widget. Compare this to seeing a name like '.fred' or '.l' somewhere within many, many lines of code. Even the name '.l' is vague becuase that is often used by people for a 'listbox' widget. Similarly, I use at-least-3-character prefixes for widgets like buttons, radiobuttons, checkbuttons, scales, etc.
** I have recently started using the suffix px or Px or PX on variables that are meant to hold a number (non-negative integer) of pixels. Thus, when I see the variable name, I know right away, without scanning back and forth through the code, that the variable is intended to hold a number of pixels (and hence should be a non-negative integer).
I am still settling on naming conventions. If I think of a few more naming conventions that I have found helpful lately, I will add them here. And if I find that I start using some more naming conventions that seem quite useful, in coming months or years, then I may return to this page to add a few more of my 'best practices in names'.
In fact, the subject of Tcl-Tk naming could require lots of words and should probably go on a separate page --- one for every Tcler :-)