Tcl/Tk GUI Programming
Lesson #6: working with files

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 continue to work with the text widget.

In addition we are going to learn how to work with data files on the harddrive.     Specifically we are going to learn to open these files and read out the contents.   We are going to learn to write back information and save that to a file.

We are going to make use of the dialogs that we introduced at the end of the last lesson.

Before we begin the code for lesson #6, here is a possible solution to part of the exercise we left you with in lesson #5.

Notice that this is the complete code for lesson#5 with some enhancements to complete the exercise.
 
#==========================
#  saveFile - entry point
#==========================
proc saveFile { }  {
set fn "saveFile"

puts stdout [format "%s: ding"  $fn]
} ;# end saveFile


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

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

} ;# end openFile


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

#========================================
# explicitly setup our main window
#========================================
wm geometry  .   200x250+10+10
wm title  .   "lesson 6"

#========================================
# 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
#========================================
destroy .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 

Notice that the only thing we added was another "$File add command" to give us the  Save menu item.    We have added a procedure called saveFile which gets invoked when the Save menu item is selected.
 

Step 0: preparation for lesson#6

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

For those of you who took the time to complete the exercise in lesson#5 you should simply copy your work into this new folder and rename it "lesson6.tcl".

For those others who didn't have time to complete the exercise use your text editor and enter the program shown above and save it as "lesson6.tcl".
 
 

Step 1: the Save Dialog

The first "enhancement" we are going to make to our little program is to add the call to the save dialog which mirrors our call to the open dialog we introduced last time.

Using your editor modify your saveFile procedure as shown below.
 
proc saveFile { } {
set fn "saveFile"

set myFile [tk_getSaveFile]

puts stdout [format "%s:myFile=<%s>" $fn $myFile]
} ;#end saveFile 

When you run this program with "wish" and select the Save menu option you should see a dialog appear asking you to enter the file name you wish to save things as.     Try typing some imaginary file name and clicking the save button.

For those of you who were paying attention to what lines you modified above you would have expected that you should see a line of output appear on your wish console window.    That line should look something like:

saveFile:myFile=<c:/level1/lesson6/bob.tcl>
The string of words in the <> above represent what is known in programming circles as a full file name.       Notice that this full file name tells you exactly where to find your file.       Each operating system (Windows, Mac or Linux) will have slightly different full file names.      Let's examine the Windows one in more detail:

The "C:" refers to the Windows designation for the hard drive on your system.     The colon ":" always follows the hard drive designator.      The "/" characters separate folder names in this hard drive.    In English this full file path might read something like:

"The file bob.tcl is located in the lesson6 folder which is inside the level1 folder which is on the C drive in the computer."

As you can see the file system on your computer is really just organized in a typical hierarchy much like a business organization would be.       The president/CEO is the "C drive".    The senior VP of iCanProgram courses is "level1".    The manager of operations is "lesson6" and the floor boss is "bob.tcl".

You have used this hierarchy in your computer travels often without really knowing it exists.     The graphical representation of the file system as folders and files shields this from you.     But underneath that graphical veneer almost every computer file system behaves in much the same way.     You can almost always access a given file by simply specifying the full path to that file.

Let's see if we can use our new found knowledge to actually access a file on our hard drive from within our Tcl/Tk program.
 
 

Step 2: reading a text file

Before we begin let's use our editor to create a text file inside our lesson6 folder.     Let's be really imaginative and call this test file:

mytest.dat
The ".dat" extension is not strictly necessary.     In the non Windows operating systems (Mac or Linux) it carries no special meaning whatsoever.     On the Windows systems these file extensions are often used to "trigger" an application that is capable of reading them.     We don't really want Windows doing this for us so the ".dat" is probably a safe choice.

Into this little file let's enter some text.

I like my
iCanProgram
course
so far.
Now save this as a text file in exact the same manner that you would if you were editting your program (eg. lesson6.tcl) file.

Using your text editor once again and modify  your openFile procedure to look something like.
 
proc openFile { } {
set fn "openFile"

set myFile [tk_getOpenFile]

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

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

When you run your newest version of our little program now and select your "mytest.dat" from the Open menu, you should see a bunch of lines on the wish console window which display line by line the contents of your mytest.dat.
 
 
Congratulations !

You have just written your very first program which accesses files.

Let's go through the new statements we have introduced.    The first pair are:
 
set fileID [open $myFile r]
close $fileID

In the first of the pair we are creating a new variable called fileID.    The right hand side in [ ] indicates that we want this variable to point to the top of a file called $myFile which we have opened for "reading only"... that's what the "r" represents ... reading only.    Want to guess what the Tcl/Tk language designers chose for the case where you want to "write" to the file ... you got it a "w".     If you wished to read and write to a file ... right again "rw".

The inverse of "open" is "close".    The second statement in the pair above indicates that we wish to terminate our access to the file we had previously opened as "fileID".       In Tcl/Tk programs which involve file access these statements always come in pairs  ... open ... do some things ... close.

Let's take a closer look at the statements we introduced between the "open" and "close".    They would be:
 
set i 1
while { [gets $fileID line] >= 0 } {
	puts stdout [format "line(%d)=%s" $i $line]
	incr i
	} ;#end while

The first "set" statement above is very simple.    We are setting a new variable called "i" to the value of 1.

The next statement is our first introduction to a very powerful programming construct called a loop.     In previous lessons we introduced the concept of branching using if/else statements.    With branching our programs could be taught simple rules around how to make "decisions" at the time the program was running based on the value in certain variables.     This is the largest single fact which gives computer software the appearance of being "intelligent".

Loops on the other hand give computer programs the power to repeat things as often as necessary to accomplish a task.     Loops are what give computer programs their "work ethic".

Our little loop is what is known as a "while" loop ... hence the command name "while".        Before looking in detail at the "gets" statement inside the first set of { } let's examine the boundaries of our repeating block of code.     As with any blocks of code we have encountered in Tcl/Tk so far they are bounded by an opening { and terminated by a closing }.      In this case the while loop will repeat the statements

puts stdout [format "line(%d)=%s" $i $line]
incr i
because they are the ones inside the loop block.

Looping would not be very useful as a programming construct unless we had a way to "exit from the loop".     Kind of like the figure skater extending a leg to stop a spin.    The contents in the first { } pair after the while statement form what is known as the exit condition or simply the condition.

Let's see what this line is saying in plain English and then try to see how that is expressed in "tickleeez".    In English the loop condition reads:

"Continue looping, reading a line from the file on each pass, until we reach the end of the file ie. no more lines are found".

The "Tickleeez" statement which expresses the "reading until portion" of this logic is.
 
 [gets $fileID line] >= 0 

The "gets" statement combined with the fileID tell the program to begin reading the file line by line.     The "line" arg is where the results of that operation are placed.       The gets command will return either a number 0 or greater if it successfully read in a line or -1  if there was none to read because we were at the end of the file.

The ">=" condition for those of you who remember your algebra stands for "greater than or equal to".     So our Tcl/Tk statement might be translated as:

"get a line from the file.    Return a "true" result if the return code from this line fetch is greater than or equal to 0 other wise return a "false".

This "gets" statement is the condition on which the while decides whether or not to continue the looping.   ie. the while will continue repeating the loop block as long as the condition remains "true".      As soon as it becomes "false" the while will "exit" the loop and Tcl/Tk will continue the program execution from the statement after the end of the loop block.

This execution "jumping" is very much the same as occurs when an "if" statement jumps to the "else" branch when the condition being tested comes up "false".

Don't worry if you don't understand all the details of looping the first time through.     Nonetheless, looping is a very powerful programming tool indeed and we will be using it several times more in future lessons.
 
 

Step 3: but I thought we were writing graphic programs

Let's see if we can bring our friend the text widget back into play.

We already know how to add text to the end of the text widget.     Let's see if we can display the contents of file inside the text widget window.      We first have to make the openFile procedure know about the frame containing our Text widget.    In Tcl/Tk the way we do this is using:
 
proc openFile { } {
set fn "openFile"
global f

The statement

global f
Tells our openFile procedure that the frame we called "f" was defined outside the procedure somewhere else in the program.

Inside our loop we know we have access to each line in the file as it is read in because we used that to create the output on the wish console already.    If we couple it with our knowledge of how to add to the end of the text widget we can add the following inside the loop block just ahead of the incr statement:
 
$f.t insert end [format "%s\n" $line]

If you run this now the contents of your text file will get appended to the area in your text widget frame.

Have some fun changing your text file and rerunning your program to "prove to yourself" that you are actually programming with files.

Before we move on to the saveFile portion and complete this lesson, let's do some housekeeping and clear out the text widget before we start adding the contents of the file to it.

To do this you need to simply add a statement like
 
$f.t delete 1.0 end

just ahead of the while statement.
 

Step 4: make the Save menu do something

Try using your text editor to change the data file mytest.dat and rerun your program.     You should see the changes you make appear inside the text widget.

Now try changing the text inside the text widget area itself.    You should be able to do lots of editor like things to this text including typing new characters and lines of text.

What we want to do now is be able to save these changes back to a file using our Tcl/Tk program.

To start with lets modify the saveFile procedure as follows to display the contents of the text widget screen on the wish console.
 
proc saveFile { } {
set fn "saveFile"
global f

set myFile [tk_getSaveFile]

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

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]
	set line [$f.t get $i.0 $i.end]
	} ;#end for loop
} ;#end saveFile 

When you run your program now by using Open to select the

mytest.dat
file and then Save to save it as
mytest2.dat.
You should see the contents of your text widget appear on the wish console window.

Before moving on to the actual saving as a file let's discuss the new statements we have introduced here.   The first of these occur with slight variations in 2 separate lines.
 
set line [$f.t get 1.0 1.end]

set line [$f.t get $i.0 $i.end]

This is the statement that we are using to transfer a line from the text widget screen into our variable called "line".    In English this line says:

"get the line 1 (or $i) from the text widget $f.t from the 0th character to the end of the line and place the result into a variable called line".

Tcl/Tk is not unique as a computer language in its use of a counting system that starts from 0 and not from 1.    This is very common amongst computer programming languages.     It simply makes the alignment to the language of the microprocessor itself a little simpler to achieve.

In our case we simply have to remember that the text widget counts characters on a line starting with 0 as the first one.

The more important new statement that we have introduced here is the other main type of programming loop in Tcl/Tk.
 
for {set i 2} {[string length $line] > 0 } {incr i } {

	... do stuff in here ... 

	} ;#end for loop

This is what is known as the "for loop" for obvious reasons.    The "for loop" is more complex than the "while loop" we introduced earlier.

To understand "for loops" we need to look at the 3 sets of conditions that occur inside the { } right after the for command on the line above.

The first of these is known as the "initialization statement".     Inside here you can place a statement that you want executed when the loop is first entered.      In our example we are initializing a "loop counter" called "i" to a value of 2.

The second of the { } statements is known as the "exit condition".     This statement gets checked for "true" or "false" each time through the loop including the first time in.     If the condition comes up "true" then the loop is allowed to continue.    If it comes up false the program moves to the statement immediately following the loop block.      This behavior is very similar to the behavior of the condition inside the "while loop" we discussed previously.

The third of the { } statements is known as the "each loop pass statement".     It gets executed after each loop pass.    It is commonly used to increment a counter such as we have done in this example but it is not restricted to this function.

So if we return to the English description of our "for loop" statement it might go something like:

"starting with a counter called i set to 2, continue the loop block as long as the variable called line has some characters in it (ie. the length of the string called line is larger than 0).   After each loop pass increment the counter by 1"

For those of you still with this lesson you might say.

"Without anything in the loop block this loop will never exit."

You'd be absolutely correct.     This is a common beginner "bug" with programmers.    Putting together a "for loop" that never exits ... resulting in a program that is said to have a "run away loop".

Fortunately, we did include something in our loop block which allows our loop to always reach an exit point.

The statement which does this for us is the second of the "fetch a line from the text widget" statements
 
set line [$f.t get $i.0 $i.end]

Each time through the loop the value of $i is incremented by 1 from a starting value of 2.    ie. the sequence of statements that gets executed are:

set line [$f.t get 2.0 2.end]
set line [$f.t get 3.0 3.end]
 etc.
This will continue until the resulting "line" variable is empty.

The only other line in our loop block is a variation of the "puts" statement that we have encountered many times before in this course already and it should be well understood by now.
 

Step 5: let's put it all together

To make the Save actually write the contents of our text widget into a file on our hard drive all we need to do is arrange to open the file for "writing", write each line into it as part of our loop block and close the file when we exit the loop.

Our final save procedure might look something like this:
 
proc saveFile { } {
set fn "saveFile"
global f

set myFile [tk_getSaveFile]

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

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 saveFile 

Play with your program now.    You have almost built a program that can do much of the same things as your text editor (like Wordpad) can to.
 
WOW.   This is REALLY NOW starting to look like a "real" program.

Without much effort you could enhance your little Tcl/Tk program and use it to write short Tcl/Tk programs !!!
 
 

Step 6: final listing

The full listing of the source code for lesson #6 is below.
 
#==========================
#  saveFile - entry point
#==========================
proc saveFile { } {
set fn "saveFile"
global f

set myFile [tk_getSaveFile]

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

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 saveFile 


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

set myFile [tk_getOpenFile]

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

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 openFile


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

#========================================
# explicitly setup our main window
#========================================
wm geometry  .   200x250+10+10
wm title  .   "lesson 6"

#========================================
# 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 

For those running PCWindows the result of running this program should be something like:


 

Summary

You have been introduced to a bunch of new Tcl/Tk statements and constructs in this lesson.   Let's review:

Exercises

We are going depart from this program in our next lesson to introduce the concepts surrounding our ticker animation so you will be given a break.    No exercises for this week.

End of Lesson 6.


Copyright of iCanProgram Inc. 2002