Application Base Projectwritten 15 Nov 1999 being revised May 2006 |
Each student works alone to produce his/her own application base program. The source of this program can then be used as a basis for other more complex programs.
This project can be done as a single student as a personal project, or by a class.
Most of the project tuition is taken up in explaining what Object Oriented Methodology is. Describing concepts such as object, method, encapsulation, module, property and inheritance. The project shows how these ideas are incorporated into an object written in the programming language Tcl and which can be reused for other projects.
The process of cannibalisation is an important one for generating general solutions derived from the solution of specific problems in the past for reuse. This ability to extract from past solutions, the components that help us solve new problems in a general manner is called "generalisation" in Mathematics. And the abiltiy to create clear cut general modules from a specific program solution is a comparable skill in computer programming which should be promoted and refined. This is how the building blocks of very complex program projects are built up.
The general idea is that the product of this exercise should be a program that can easily be used as the basis for any new application without any great modification.
Tcl provides several facilites that make code reuse easier. The source command allows new segments of program to be loaded while the program is running. It also allows for procedures to be redefined during the programs execution and this is the key to allowing reuse of existing code without changing it. This process can be abused of course and good coding practice should be promoted, but it is useful for initailly sketching out a program, to be replaced with specific code later.
The first step is to draw a line through the text editor program at the point where the code changes from being general to the point where it is specific to a text editor. In particular, any part of the program which refers to the text widget is peculiar to the text editor. This include the newFile, saveFile and loadFile procedures and the contents of the Help text widget. This gives us the following code:
#Application Base - Canniblised Text Editor
#Procedures
set initialdir "/tcl"
proc fileDialog {w operation} { global initialdir
# Type names Extension(s) Mac File Type(s)
#
#---------------------------------------------------------
set types {
{"All files" *}
{"Tcl Scripts" {.tcl .tk} }
{"HTML files" {.htm .html .shtm .shtml} }
{"Text files" {.txt .doc} }
{"C Source Files" {.c .h} TEXT }
{"All Source Files" {.tcl .tk .c .h} TEXT }
}
if {$operation == "open"} {
set file [tk_getOpenFile -filetypes $types -parent $w -initialdir $initialdir]
} else {
set file [tk_getSaveFile -filetypes $types -parent $w -initialdir $initialdir \
-initialfile Untitled -defaultextension .tk]
}
if {$file != ""} {set initialdir [file dirname $file]}
return $file
}
set w {}
set title "My Text Editor 7"
frame $w.menu
pack $w.menu -side top -fill x
button $w.menu.new -text New -command {newFile}
button $w.menu.open -text Open -command {loadFile [fileDialog . open]}
button $w.menu.save -text Save -command {saveFile $filename}
button $w.menu.saveas -text "Save As" -command {saveFile [fileDialog . save]}
pack $w.menu.new $w.menu.open $w.menu.save $w.menu.saveas -side left
button $w.menu.help -text "Help" -command help
pack $w.menu.help -side right
text .text -relief raised -bd 2 -height 20 -yscrollcommand ".scroll set"
scrollbar .scroll -command ".text yview"
pack .text -side left -fill both -expand 1
pack .scroll -side left -fill y
# Load initial file
if {$argc==1} {loadFile [lindex $argv 0]} else {newFile}
proc help {} { global title
if { ![winfo exists .help] } {
toplevel .help
wm title .help "$title Help"
wm transient .help .
text .help.text -relief raised -bd 2 -height 20 -yscrollcommand ".help.scroll set"
scrollbar .help.scroll -command ".help.text yview"
pack .help.text -side left -fill both -expand 1
pack .help.scroll -side right -fill y
.help.text tag configure H1 -font {Helvetica 18 bold}
.help.text tag configure H2 -font {Helvetica 15 bold}
.help.text tag configure H3 -font {Helvetica 12 bold}
.help.text tag configure H4 -font {Helvetica 12 normal}
.help.text tag configure H5 -font {Helvetica 10 bold}
.help.text tag configure H6 -font {Helvetica 8 bold}
}
} |
Object Encapsulation is the process whereby the boundary of the Object is made firm so that the way variables are used is made clear to the programmer.
Tcl leads the way by using its own paradigm. You will notice looking at the Tcl Help Manual on-line that there are many procedure called Tk_.... and tcl_... and I will promote continuing to use the same paradigm. The Tcl paradigm is to use a first letter capital prefix for C routines and a lowercase first letter for Tcl procedures, and we will honour this method also. So the first step is to prefix all the variables used in the Application Base by the prefix appBase_. This still leaves us with the problem of deciding which variable are to be considered to be "owned" by the object. To start with rename everything in the code above in Lesson 1, but for the Text Editor to still work variable names will have to be changed there as well. Make changes to the Text Editor and verify that it still runs OK. In this way we test that our design paradign works in practice.
Many program languages provide special features for managing object namespaces, but they create a layer of complexity that is not usually worth the effort except for very large projects. Tcl has such a system called the namespace subsystem, but it also is too complex to be useful for the programs that we are working with here.
If a variable is being used and changed both inside and outside the object, and cannot be encapsulated into the object completely, then the object boundaries need to be reconsidered.
This can be seen in the Tk widgets in which an initial call is made to a procedure like button to create a button widget with the desired properties and behaviours. The Tk widgets use commands that have keyword references. This form of command is more difficult to program than a simple procedure call where specific parameters are used. We will use the simple approach here. Basically the code that would be executed when app_base is sourced is put into a create procedure. When we attempt to do this we come up against a problem. The code inside the object which attempts to read an initial file causes problems. With appBase_create call at the head of the program the text widget into which the file is to be loaded doesnt exist yet. If we move the call further down the program the menu bar gets displaced. Attempting to load an initial file in the create pahse makes assumptions about what has already happened in the main program. This function has to be taken out of the create procedure and moved to the application where it more properly belongs since it uses $argc and $argv variables.
At the same time the application procedure names for new save and load Files can be passed across at initialisation, making for even more encapsulation.
The create procedure now looks rather cumbersome with a lot of global variables and initial assignments. This can be addressed by creating an AppBase array which contains all the variables which define the Application Base objects attributes. This array can then be queried to see what attirbutes exist before attempting to access them outside the object if the need arises.
Now that the object behaviour has been disentangled from the application we can see how to condense the saveFile and loadFile procedures back into the fileDialogue procedure. The code now looks something like this:
#Application Base - Encapsulated
#Procedures
proc appBase_fileDialog {w operation} { global appBase_initialdir
# Type names Extension(s) Mac File Type(s)
#
#---------------------------------------------------------
set types {
{"All files" *}
{"Tcl Scripts" {.tcl .tk} }
{"HTML files" {.htm .html .shtm .shtml} }
{"Text files" {.txt .doc} }
{"C Source Files" {.c .h} TEXT }
{"All Source Files" {.tcl .tk .c .h} TEXT }
}
if {$operation == "open"} {
set file [tk_getOpenFile -filetypes $types -parent $w -initialdir $appBase_initialdir]
} else {
set file [tk_getSaveFile -filetypes $types -parent $w -initialdir $appBase_initialdir \
-initialfile Untitled -defaultextension .tk]
}
if {$file != ""} {
set appBase_initialdir [file dirname $file]
wm title . "$appBase(title) - $file"
set appBase(filename) $file
$appBase($operation) $file
}
return $file
}
proc appBase_create { title initialdir new save open} {
global appBase
foreach i { title initialdir newFile saveFile loadFile} {
set appBase($i) [set $i ]
}
set appBase(filename) ""
wm title . $title
frame .menu
pack .menu -side top -fill x
button .menu.new -text New -command " set appBase(filename) {}; wm title . $title; $new "
button .menu.open -text Open -command {appBase_fileDialog . open}
button .menu.save -text Save -command {$appBase(save) $appBase(filename)}
button .menu.saveas -text "Save As" -command {appBase_fileDialog . save}
pack .menu.new .menu.open .menu.save .menu.saveas -side left
button .menu.help -text "Help" -command help
pack .menu.help -side right
}
proc help {} { global appBase_title
if { ![winfo exists .help] } {
toplevel .help
wm title .help "$appBase_title Help"
wm transient .help .
text .help.text -relief raised -bd 2 -height 20 -yscrollcommand ".help.scroll set"
scrollbar .help.scroll -command ".help.text yview"
pack .help.text -side left -fill both -expand 1
pack .help.scroll -side right -fill y
.help.text tag configure H1 -font {Helvetica 18 bold}
.help.text tag configure H2 -font {Helvetica 15 bold}
.help.text tag configure H3 -font {Helvetica 12 bold}
.help.text tag configure H4 -font {Helvetica 12 normal}
.help.text tag configure H5 -font {Helvetica 10 bold}
.help.text tag configure H6 -font {Helvetica 8 bold}
}
}
|