Tcl/Tk GUI Programming
Lesson #8: putting it all together

Course content Enter chat room Send email
to mailing list
Check calendar

In memory of Linda.  Worldwide Cancer sites here.    Canadian Cancer donations here.
 

In this lesson we are going to wrap together all the things we have done in this course to produce our final ticker application.    Actually it is going to take the form of 2 separate applications.

ticker.tcl
messenger.tcl
We will discuss more about this and how the two programs are going to interact as we go forward in this lesson.

For those of you that succeeded with the exercise we left you with at the end of lesson#7 ... Congratulations.

For the rest of you ... not to worry ...  it was a very hard exercise and we'll go through the steps to a solution below.
 

Step 0: preparation for lesson#8

As with all the previous lessons let's start by creating a new folder inside our level1 folder called "lesson8".

Being the lazy programmers we are I want you to copy two files into this new folder.

From lesson7 I want you to copy lesson7.tcl as ticker.tcl

From lesson6 I want you to copy lesson6.tcl as messenger.tcl and mytest.dat as itself.

At this point in your lesson8 folder you should have 3 files:

ticker.tcl
messenger.tcl
mytest.dat
HINT:
You will probably want to open 2 text editor windows for this lesson.   One to edit "ticker.tcl" and the other to edit "messenger.tcl"

Using your text editor modify the main area of the ticker.tcl program as follows:
 
#========================================
# explicitly setup our main window
#========================================
wm geometry  .   400x75+10+10
wm title  .   "lesson 8: ticker"

Using your other text editor window,  modify the main area of the messenger.tcl program as follows:
 
#========================================
# explicitly setup our main window
#========================================
wm geometry  .   200x100+10+10
wm title  .   "lesson 8: messenger"

We  will now focus on making our next batch of changes to "ticker.tcl" so you can probably just save the "messenger.tcl" and then minimize the window to an icon to unclutter your screen.
 
 

Step 1: ticker.tcl: reenabling the Open menu

In your ticker.tcl file locate the TEMPORARY FOR TESTING sections that we modified in the main area of the program
 
#========================================
# add the Open menu item
#========================================
#$File add command -label Open -command openFile
#TEMPORARY FOR TESTING
$File add command -label Open -command openFile -state disabled

<some other statements>

#TEMPORARILY COMMENTED OUT FOR TESTING
#$Ticker add command -label Start -command toggleTicker  -state disabled

<some other statements>

#==========================
#   THIS IS TEMPORARY
#    TO TEST ticker PROCEDURE
#==========================
ticker $f.t 400 " +++ " 95

By removing the temporary statement lines as follows we will be wanting to reenable the Open menu item :
 
#========================================
# add the Open menu item
#========================================
$File add command -label Open -command openFile

and redisable the Start menu item :
 
$Ticker add command -label Start -command toggleTicker  -state disabled

You can go ahead and remove the entire lines below:
 
#==========================
#   THIS IS TEMPORARY
#    TO TEST ticker PROCEDURE
#==========================
ticker $f.t 400 " +++ " 95

Also in your ticker.tcl file locate the openFile procedure and change it to look something like.
 
#==========================
#  openFile - entry point
#==========================
proc openFile { } {
set fn "openFile"
global f

set myFile [tk_getOpenFile]

puts stdout [format "%s:myFile=<%s>" $fn $myFile]

if { [string len $myFile] > 0 } {
	loadTicker $myFile

	if { $tickerState == "stopped" } {
		$Ticker entryconfigure Start -state normal
	}  ;#end if tickerState

}  ;#end if string len

}  ;#end openFile 

Notice that these changes makes our little program stop running because it does not find the procedure called loadTicker.

Before we go into the explaination of everything that we have added here let's create a stub procedure for loadTicker so that our program will at least run.

At the top of  your ticker.tcl file add the following lines:
 
#==========================
#  loadTicker - entry point
#==========================
proc loadTicker { filename } {
set fn "loadTicker"

puts stdout [format "%s:filename=<%s>" $fn $filename]

}  ;#end loadTicker 

Now your ticker.tcl should run once again and the Open menu option should resume working.

In the previous lessons some of you may have tried to press the Cancel button on the Open file dialog and discovered that an ugly error resulted.     If you had then asked the mailing list for help you might recognize these lines we added to the openFile procedure:
 
if { [string len $myFile] > 0 } {

... other lines of code are here ... }  ;#end if string len

Basically we are recognizing that when the tk_getOpenFile statement returns after a "Cancel" has been pressed the contents of the resulting file name string is completely empty.    We can then test for this empty string condition by checking on the length of this resulting string.    If the string has at least 1 character in it (ie. length > 0)  then we can be sure that the "Cancel" was not selected.

The first thing we do in the case where the string length is greater than zero (or the case where we assume that the user has in fact selected a valid file name) is to:

loadTicker $myFile
We will be doing lots more within this loadTicker procedure shortly.   Meanwhile we have simply arranged to have the procedure called here.

The remaining part within the if block is:
 
if { $tickerState == "stopped" } {
	$Ticker entryconfigure Start -state normal
}  ;#end if tickerState

Once again you may recognize this as the enabler code for the Start menu option in the Ticker menu.     As this program starts up this Start menu item is left disabled.    It will now remain disabled until you select a filename from the Open menu.    This is exactly the new behavior we want to impart to our little program.

We now need to get the contents of our data file into our ticker area.
 

Step 1: ticker.tcl: making loadTicker actually do something

We are still working on ticker.tcl at this point in the lesson.

Locate your loadTicker procedure and then modify it as follows:
 
#==========================
#  loadTicker - entry point
#==========================
proc loadTicker  { filename }  {
set fn "loadTicker"
global text_messages
global maxMessages

puts stdout [format "%s:filename=<%s>" $fn $filename]

set fileID [open $filename r]
set i 0
while { [gets $fileID line] >= 0 } {
	puts stdout [format "line(%d)=%s" $i $line]
	set text_messages($i) $line 
	incr i
}  ;#end while
close $fileID

set maxMessages $i

}  ;#end loadTicker 

Why doesn't this change anything !!!!

The clue lies in the following lines inside the "ticker" procedure (not to be confused with the ticker.tcl file ... although you will find the ticker procedure inside that file):
 
set text_messages(0) Zero
set text_messages(1) One
set text_messages(2) Two
set text_messages(3) Three

set maxMessages 4

Simply comment these 5 lines out inside the ticker procedure.

In addition near the rest of the "global" statements inside the same ticker procedure add the following line:
 
 global maxMessages

Now when you run your program  you should see the contents of your data file appear inside your ticker tape.
 
 
You have now completed the exercise from the previous lesson.     We are going to start tying things together now.

 

Step 2: introducing a very powerful concept

Programs are a lot like people.    They are next to useless if they can't interact with others (programs).

Programmers have a fancy name for the communications that they build between programs.   They are a very imaginative bunch and they call it ... are you ready for it ...

Interprocess Communications (or IPC for short)
Many of you will have used this without knowing it lots of times already.     A form of IPC is involved anytime you do anything at all with the Internet.   Others of you will have used spreadsheets which automatically update themselves from other spreadsheets ... a form of IPC.    Still others may have used a word processor document which contained "live" table updates ... IPC again.

Just to prove to you that IPC is not a difficult concept at all we are going to do some right now ... in a beginner programming course to boot !!!

We are going to use one of the oldest and one of the simplest IPC "tricks in the book".     We are going to use our data files that we have so carefully been working on opening and saving as the carrier of our IPC messages.    We are going to use the fact that all computers store time stamps in the filesystem alongside each and every file.     These timestamps can tell us if a file has been altered or not.

With these two very simple ideas we are now going to connect our ticker.tcl program to our messenger.tcl program.

Are you ready !!!
 
 

Step 3: ticker.tcl adding timestamp triggers

Once again in your loadTicker procedure add the following lines.    First in the global section:
 
global filemodtime

and secondly just after the "set maxMessages $i" line:
 
set filemodtime [file mtime $filename]

What we are doing here is storing what is known as the "file modification timestamp" into a global variable called filemodtime.    We don't need to be concerned about how this time is actually stored except to note that it is a number that always increases well into the future (until we hit another Y2K boundary at least).     Knowing that much we can always detect if a file is "newer" than our  previously stored timestamp by just comparing the values.    If the file timestamp is greater than our stored value ... the file is newer.

The next modification we need in ticker.tcl is in the ticker procedure as follows:
 
#==========================
#  ticker - entry point
#==========================
proc ticker { t delay filler minlen}  {
set fn "ticker"
global text_messages
global maxMessages
global index
global tickerState
global filemodtime
global myFile

puts stdout [format "%s:ding" $fn ]

#set text_messages(0) Zero
#set text_messages(1) One
#set text_messages(2) Two
#set text_messages(3) Three

#set maxMessages 4

set newmodtime [file mtime $myFile]
if { $newmodtime > $filemodtime} {
	puts stdout [format "file mtimes=%d %d" $newmodtime $filemodtime]
	loadTicker $myFile
}  ;#end if newmodtime

Notice what we have done here.    We have built into our ticker procedure a "file modification time" trigger.    If the file is changed by any means then we will cause our loadTicker procedure to be run.     The result of running loadTicker is amongst other things the reseting of our global filemodtime variable to the new file modification time.

We now have our ticker.tcl process in a form we want it.

Let's move to our other process in our IPC partnership ... namely messenger.tcl.
 
 

Step 4: messenger.tcl let's polish things up a little

You are going to make the following changes in messenger.tcl    NOT IN ticker.tcl.

Let's protect our saveFile procedure from the "Cancel" bug we noted above.
 
#==========================
#  saveFile - entry point
#==========================
proc saveFile {  }  {
set fn "saveFile"
global f

set myFile [tk_getSaveFile]

puts stdout [format "%s:myFile=<%s>" $fn $myFile]

if { [string len $myFile] > 0 } {
	set fileID [open $myFile w]

	set line [$f.t get 1.0 1.end]
	for { set i 2 } { [string length $line] > 0 } { incr i } {
		puts stdout [format "line=<%s>" $line]
		puts $fileID $line
		set line [$f.t get $i.0 $i.end]
		}  ;#end for loop

	close $fileID
	}  ;#end if string len

}  ;#end saveFile 

We need to add the same protection to our openFile procedure from the "Cancel" bug we noted above.
 
#==========================
#  openFile - entry point
#==========================
proc openFile {  }  {
set fn "openFile"
global f

set myFile [tk_getOpenFile]

puts stdout [format "%s:myFile=<%s>" $fn $myFile]

if { [string len $myFile] > 0 } {
	set fileID [open $myFile r]
	set i 1
	$f.t delete 1.0 end
	while { [gets $fileID line] >= 0 } {
		puts stdout [format "line(%d)=%s" $i $line]
		$f.t insert end [format "%s\n" $line]
		incr i
		}  ;#end while

	close $fileID
	}  ;#end if string len

}  ;#end openFile

Run your messenger.tcl and it should work much like it did in lesson6.
 
 

Step 5: Final lap folks !!!

Here is how you can run your complete ticker programs.

Because we have two separate programs in our IPC family, you will need to open two separate wish sessions.

In one wish session startup ticker.tcl

In the other startup messenger.tcl

In the ticker window select mytest.dat as the file from the Open menu and then Start the ticker.    All should behave as before.

Now from the messenger window Open the same mytest.dat file.    The contents should display in the text area.    Now change something in the text area ... perhaps to read.
 

I still really really
like my
iCanProgram
course so far.
Now Save these changes and observe the ticker tape.      You may have to wait until a few sections scroll by ... be patient.
 
 
NOW THAT IS IMPRESSIVE !!!

Your changes to the mytest.dat file done from within the messenger.tcl program have magically appeared in the ticker.tcl program without you having to do anything at all in that window.


 

For those running PCWindows the result of running these programs should be something like:


 

Now try this experiment which is some ways is even more impressive.

Shut down the messenger.tcl program but leave the ticker.tcl program running.

Using your text editor that you have been using all along to write your .tcl files (eg. Wordpad for the Windows users) and open up the data file  -  mytest.dat.

Make some changes to the text and save it as you always have as a text file.

Observe what happens in the ticker tape.

Now you didn't write the code for your text editor.    Someone else did that.     What you have done is made use of a very simple file based IPC scheme to connect that program to your ticker tape program.

THINK ABOUT WHAT YOU HAVE JUST ACCOMPLISHED.    THINK ABOUT THE POSSIBILITIES.
 
 

Step 6: Final program listings

Here  is the complete listing for the ticker.tcl program.
 
#==============================================
#
#     FILE: ticker.tcl
#
#     DESCRIPTION:
#      Final program for iCanProgram.com Level 1 Course
#      I had a lot of fun writing these programs for the course.
#      I hope you had as much fun learning about how they tick.
#
#     AUTHOR:
#      Bob Findlay
#
#      REVISIONS:
#
#=============================================

#==========================
#  loadTicker - entry point
#==========================
proc loadTicker { filename }  {
set fn "loadTicker"
global text_messages
global maxMessages
global filemodtime

puts stdout [format "%s:filename=<%s>" $fn $filename]

set fileID [open $filename r]
set i 0
while { [gets $fileID line] >= 0 } {
	puts stdout [format "line(%d)=%s" $i $line]
	set text_messages($i) $line 
	incr i
	}  ;#end while
close $fileID

set maxMessages $i
set filemodtime [file mtime $filename]

}  ;#end loadTicker 


#==========================
#  ticker - entry point
#==========================
proc ticker  { t delay filler minlen}  {
set fn "ticker"
global text_messages
global maxMessages
global index
global tickerState
global filemodtime
global myFile

#puts stdout [format "%s:ding" $fn ]

set newmodtime [file mtime $myFile]
if {$newmodtime > $filemodtime} {
	puts stdout [format "file mtimes=%d %d" $newmodtime $filemodtime]
	loadTicker $myFile
	}  ;#end if newmodtime

scan [$t index 1.end] %d.%d line len

# add to ticker
if { $len < $minlen } {
	if { $index >= $maxMessages } { set index 0 }
	set j $index

	puts stdout [format array(%d)=<%s> $j $text_messages($j)] 
	$t configure -state normal
	$t insert end $text_messages($j) tag$j
	$t insert end $filler fill
	$t configure -state disabled

	incr index
	}  ;#end for

# move ticker one character to the left
$t configure -state normal
$t delete 1.0
$t configure -state disabled

if { $tickerState == "running" } {
	after $delay [list ticker $t $delay $filler $minlen]
	}  ;#end if tickerState

#puts stdout [format "%s:done" $fn ]

}  ;#end ticker


#==================================
#   toggleTicker - entry point
#==================================
proc toggleTicker { }  {
set fn "toggleTicker"
global tickerState
global Ticker

#puts stdout [format "%s:ding  tickerState=<%s>" $fn $tickerState]

if { $tickerState == "running" } {
	set tickerState "stopped"
	$Ticker entryconfigure Stop -label Start
	puts stdout [format "ticker is now stopped"]
} else {
	set tickerState "running"
	$Ticker entryconfigure Start -label Stop
	ticker $f.t  300  " +++ "  95
	puts stdout [format "ticker is now running"]
	}  ;# end if

#puts stdout [format "%s:done  tickerState=<%s>" $fn $tickerState]

}  ;#end toggleTicker



#==========================
#  openFile - entry point
#==========================
proc openFile {  }  {
set fn "openFile"
global f
global myfile
global Ticker
global tickerState

set myFile [tk_getOpenFile]

puts stdout [format "%s:myFile=<%s>" $fn $myFile]

if { [string len $myFile] > 0 } {
	loadTicker $myFile

	if { $tickerState == "stopped" } {
		$Ticker entryconfigure Start -state normal
		}  ;#end if tickerState

	}  ;#end if string len

}  ;#end openFile 


#========================================
#  main - entry point
#========================================

#========================================
# explicitly setup our main window
#========================================
wm geometry  .   400x75+10+10
wm title  .   "lesson 8:ticker"

#========================================
# setup the frame stuff
#========================================
destroy .myArea
set f [frame .myArea -borderwidth 5 -background blue]

#========================================
# add the ticker area as a text widget
#========================================
text $f.t  \
	-bd 2 \
	-bg white \
	-height 1 \
	-relief ridge \
	-wrap none \
	-state disabled

pack $f.t  -side bottom
pack $f -side top -expand true -fill both 

#========================================
# create a menubar
#========================================
menu .menubar
. config -menu .menubar

#========================================
#  create a pull down menu with a label 
#========================================
set File [menu .menubar.mFile]
.menubar add cascade -label File  -menu  .menubar.mFile

#========================================
# add the Open menu item
#========================================
$File add command -label Open -command openFile

#========================================
# add the menu item
#========================================
$File add command -label Quit -command exit 

#========================================
#  create a Ticker pull down menu 
#========================================
set Ticker [menu .menubar.mTicker]
.menubar add cascade -label Ticker  -menu  .menubar.mTicker

#========================================
# add the  Start menu item
#========================================
$Ticker add command -label Start -command toggleTicker  -state disabled

#=====================================
#   initialize some global variables
#=====================================
set tickerState "stopped"
set index 0

The final listing for messenger.tcl
 
#==============================================
#
#     FILE: messenger.tcl
#
#     DESCRIPTION:
#      Final program for iCanProgram.com Level 1 Course
#      I had a lot of fun writing these programs for the course.
#      I hope you had as much fun learning about how they tick.
#
#     AUTHOR:
#      Bob Findlay
#
#      REVISIONS:
#
#=============================================

#==========================
#  saveFile - entry point
#==========================
proc saveFile {  }  {
set fn "saveFile"
global f

set myFile [tk_getSaveFile]

puts stdout [format "%s:myFile=<%s>" $fn $myFile]

if { [string len $myFile] > 0 } {
	set fileID [open $myFile w]

	set line [$f.t get 1.0 1.end]
	for { set i 2 } { [string length $line] > 0 } { incr i } {
		puts stdout [format "line=<%s>" $line]
		puts $fileID $line
		set line [$f.t get $i.0 $i.end]
		}  ;#end for loop

	close $fileID
	}  ;#end if string len

}  ;#end saveFile 


#==========================
#  openFile - entry point
#==========================
proc openFile { }  {
set fn "openFile"
global f

set myFile [tk_getOpenFile]

puts stdout [format "%s:myFile=<%s>" $fn $myFile]

if { [string len $myFile] > 0 } {
	set fileID [open $myFile r]
	set i 1
	$f.t delete 1.0 end
	while { [gets $fileID line] >= 0 } {
		puts stdout [format "line(%d)=%s" $i $line]
		$f.t insert end [format "%s\n" $line]
		incr i
		}  ;#end while

	close $fileID
	}  ;#end if string len

}  ;#end openFile


#========================================
#  main - entry point
#========================================

#========================================
# explicitly setup our main window
#========================================
wm geometry  .   200x100+10+10
wm title  .   "lesson 8:messenger"

#========================================
# setup the frame stuff
#========================================
destroy .myArea
set f [frame .myArea -borderwidth 5 -background blue]

#========================================
# add the text widget
#========================================
text $f.t -bd 2 -bg white -height 5
pack $f.t

pack $f -side top -expand true -fill both 

#==========================
# add a line of text
#==========================
$f.t insert end "abc\n" tag0
$f.t insert end "def" tag1

#========================================
# create a menubar
#========================================
menu .menubar
. config -menu .menubar

#========================================
#  create a pull down menu with a label 
#========================================
set File [menu .menubar.mFile]
.menubar add cascade -label File  -menu  .menubar.mFile

#========================================
# add the Open menu item
#========================================
$File add command -label Open -command openFile

#========================================
# add the  Save menu item
#========================================
$File add command -label Save -command saveFile

#========================================
# add the menu item
#========================================
$File add command -label Quit -command exit 

 

Summary

You have been introduced to a wide range of programming concepts over the past 8 weeks.

As with any discipline, programming requires lots of practice to become proficient.

Returning to writing analogy.     We have taught you the alphabet, how to use a pencil and paper and how to use a dictionary for words.    Together we have written a very short story.     In essence you have been given all the tools that would be required to write a novel.     With a little creative imagination and lots of practice in the craft you could do just that.

Derivations of this very simple program that we wrote together in this course might even find some use in your day to day computing.  But that was not really the objective.

The objective was to stimulate your mind by exposing you to a new aspect of using your computer.

The objective was to teach you a little programming so that you could understand and appreciate some of the software you currently use on your computer a little more.

The objective was to have fun.

I know I did.   I hope you did too.

END OF LEVEL 1 COURSE


Copyright of iCanProgram Inc. 2002