One of the goals of the Fx toolkit is to allow the programmer to customize as much of the interface as possible without losing the common look and feel. Any user familiar with one Fx application should be able to muddle through almost all Fx applications. Of course, there may be exceptions to this rule (for example, custom secondary screens for invoice entry).
This section describes some of the common custom features that you can implement with Fx. We cannot, of course, describe all the possible things you can do. The manual pages describe all the options available to the Fx commands.
When you write custom Fx applications, you need to realize that Fx uses
the global Tcl name space to hold many variables and procedures. For this
reason, you should take care not to conflict with any of Fx's predefined
names. You should avoid prefixing your names with fx
in any case or
case mixture.
Consider these names reserved for future use by the toolkit even if the name you
want isn't in use at the moment.
Fx allows you to augment existing menus or create new ones. To augment an existing menu, you must explicitly add a menu item to the appropriate menu. Menus are named according to their label in all lowercase. For example, to add a menu item to kill the last search results, you might do this:
.mb.view.menu add command -underline 0 -label "Kill last search" \ -command {menubar KillLastSearch}
The -underline
option specifies the character to be underlined
in the menu item's label and used as an accelerator.
Fx generates a reasonably nice Search Results listbox for displaying
the result of a query. Since you might want to add menus for special
predefined searches, Fx provides a mechanism for you to change the
contents of the Search Results. There are several Fx_Menubar
methods available to aid this sort of manipulation:
The SetLastSearch
method takes a list argument that must be the
result of a call to qddb_rows select -query off
; it implicitly
calls KillLastSearch
. GetLastSearch
returns such a list
or {}
if there are no previous search results.
DisplayLastSearch
displays the contents of the last search
results in the standard Search Results listbox.
We can add a menu item to the View menu to generate a search that matches all tuples:
Fx_Menubar menubar -w .mb -schema $schema .mb.view.menu add separator .mb.view.menu add command -label "All records" -command MyApp_ViewAll proc MyApp_ViewAll {} { set s [menubar info public schema -value] set k [qddb_search $s regexp .*] set k [qddb_keylist process nullop -deldup_sameentry on $k] set r [qddb_rows select $k] qddb_keylist delete $k menubar SetLastSearch $r menubar DisplayLastSearch }
You can calculate some fields in your relation calculated from other fields. For example, suppose you have a Students relation containing:
Name SS verbosename "Social Security Number" separators "" Course verbosename "Course Number" Grades ( Description Weight type real Score type real format "%.2f" )* Total type real format "%.2f" FinalGrade verbosename "Final Grade"
You want to recalculate the Total
every time one of the
scores (or weights) changes. You might do something like this (in the
global context, of course):
#...stuff deleted... Fx_Entry Grades.Weight -w .grades.weight -attr Grades.Weight \ -read_only 1 -userconfig 0 -width 5 Fx_Entry Grades.Score -w .grades.score -attr Grades.Score \ -read_only 1 -userconfig 0 -width 5 proc MyApp_Recalc {} { global weight score gv_attr if {[Fx_Entry :: TupleChanged] == 0} {return} ;# nothing changed set t [menubar info public tuple -value] set v [qddb_view define $t { {Grades.Weight weight} {Grades.Score score} }] set max [qddb_instance maxnum $v Grades] set tot 0.00 for {set i 1} {$i <= $max} {incr i} { qddb_instance switch $v Grades $i set tot [expr $tot + ($weight * $score)] } # setting gv_attr(Total) automatically sets tuple as changed set gv_attr(Total) [format "%.2f" $tot] update idletasks qddb_view delete $v } bind [Grades.Weight GetEntry] <FocusOut> MyApp_Recalc bind [Grades.Score GetEntry] <FocusOut> MyApp_Recalc
Now whenever the user presses the <Tab>
key or selects
a menu button, the Total
will be recalculated if the
cursor was in either the Grades.Weight
or Grades.Score
field and the tuple has been modified.
Suppose you have a client/invoice database containing two major components: client information and invoices. You could describe this database with a single Qddb relation:
Client ( Name (First Last) Address (Street City State Zip) Phones (Description Area Number) ) Invoices ( Number type integer verbosename "Invoice Number" Date type date format "%I:%M %p, %B %d, %Y" Items ( Qty type integer Number verbosename "Item number" separators "" Description Price type real format "%.2f" Total type real format "%.2f" )* Total type real format "%.2f" )*
The Invoices.Number
field should be a unique integer and must be
generated whenever an invoice is created. One common practice is to
create a Setup
relation containing some of the standard information
you commonly need: business name/address, next invoice number, etc.
A Setup
Schema might look like:
Name verbosename "Business name" Address verbosename "Business address" ( Street City State Zip verbosename "Zip Code" ) NextInvoice verbosename "Next invoice number" type integer
Using this relation, we can define a new invoice number whenever a new invoice for a particular client is created. For example, the following code fragment explains what must be done to generate a new invoice number and to prevent the user from changing it:
Fx_Frame Invoices -w .invoices -attr Invoices -afteradd AddInvoiceProc proc AddInvoiceProc {} { global gv_attr next {fx:status_variable} set s [qddb_schema open Setup] set k [qddb_search $s -prunebyattr Name regexp .*] set k [qddb_keylist process nullop -deldup_sameentry on $k] set t {} foreach i [qddb_keylist get $k] { set t [qddb_tuple read $s $i] if {[string compare $t {}] == 0} {continue} } qddb_keylist delete $k if {[string compare $t {}] == 0} { set {fx:status_variable} {Error! Must set up Setup relation!} return } while {[qddb_tuple lock $t] == 0} { set {fx:status_variable} "Waiting for Setup screen to close." exec sleep 1 ; set {fx:status_variable} {} ; exec sleep 1 } set v [qddb_view define $t { {NextInvoice next} }] set gv_attr(Invoices.Number) $next incr next qddb_tuple write $t qddb_schema delete $s ;# deletes/unlocks tuple, view and schema. set gv_attr(Invoices.Date) [exec date {+%I:%M %p, %B %d, %Y}] } Fx_Entry Invoices.Number -w .invoices.number -attr Invoices.Number \ -read_only 1 -userconfig 0 -date_search 0 -regexp_search 0 Fx_Entry Invoices.Date -w .invoices.date -attr Invoices.Date \ -read_only 1 -userconfig 0 -regexp_search 0
Since the procedure AddInvoiceProc
is called after
creating and switching to the new instance of Invoices
, we just need
to set the invoice number and date.
The last few lines disable regular-expression and date searching on the
invoice number field.
The typical Setup
application manipulates a relation containing
a single tuple. When you run your Setup
application, you want that
tuple to come up immediately and you never want to search for tuples.
Suppose we have a Setup
relation with the following Schema:
Business ( Name Address ( Street City State Zip ) ) NextInvoice verbosename "Next Invoice Number #" type integerAfter we define the
Fx_Menubar
, Fx_Frame
s and Fx_Entry
s, we
want to go directly into Change Mode if the record has been created, and go into
Add Mode otherwise. The full Setup application might look like:
#!/usr/local/qddb/bin/qwish -f lappend auto_path $qddb_library/fx if {[info exists blt_library]} { lappend auto_path $blt_library } wm title . "Setup" set s [qddb_schema open Setup] wm withdraw . ;# withdraw so the user doesn't watch the drawing. Fx:Init $s Fx_Menubar menubar -w .mb -schema $s -array gv_attr set search_entry [menubar SearchForEntry] Fx_Frame Business -w .biz -attr Business -setschema $s \ -side top -anchor nw -relief sunken -bd 2 Fx_Entry Business.Name -w .biz.name -attr Business.Name \ -searchfor_entry $search_entry -side top -anchor e -side left Fx_Frame Business.Address -w .biz.addr -attr Business.Address \ -side top -anchor nw -relief sunken -bd 2 Fx_Entry Business.Address.Street -w .biz.addr.str \ -attr Business.Address.Street -width 40 -side left .biz.addr.str.f_0.l configure -width 20 Fx_Entry Business.Address.City -w .biz.addr.city \ -attr Business.Address.City -width 40 -side left .biz.addr.city.f_0.l configure -width 20 Fx_Entry Business.Address.State -w .biz.addr.state \ -attr Business.Address.State -width 40 -side left .biz.addr.state.f_0.l configure -width 20 Fx_Entry Business.Address.Zip -w .biz.addr.zip \ -attr Business.Address.Zip -width 40 -side left .biz.addr.zip.f_0.l configure -width 20 Fx_Entry NextInvoice -w .nextinv -attr NextInvoice -side top pack .nextinv -side top -fill x menubar configure -instances [Fx_Entry :: GetInstances] \ -frames [Fx_Frame :: GetInstances] menubar configure -afterpost_modes { .mb.modes.menu entryconfigure 0 -state disabled .mb.modes.menu entryconfigure 1 -state disabled } menubar configure -afterpost_edit { .mb.edit.menu entryconfigure 7 -state disabled } set k [Fx_QddbSearchParser :: MultiSearch $s {.*} on Business.Name] set mykey [qddb_keylist get $k] if {[llength $mykey] == 0} { menubar configure -aftersave { set k [Fx_QddbSearchParser :: MultiSearch $schema {.*} on Business.Name] set mykey [qddb_keylist get $k] menubar SetLastSearch [qddb_rows select -attrs Business.Name \ -print Business.Name $k] menubar ChangeModeProc 0 } menubar AddModeProc } else { menubar SetLastSearch [qddb_rows select -attrs Business.Name \ -print Business.Name $k] menubar ChangeModeProc 0 } qddb_keylist delete $k wm deiconify .
First, we set up the Fx_Menubar
and all the Fx_Frame
and Fx_Entry
instances. Next, we link the frames and entries
with the menubar, then disable some of the standard Fx features
we aren't interested in. Finally, we search with '.*
'
to provide the single record, then depending on whether a record
exists, go to Change Mode or Add Mode. After saving the record
in Add Mode, we switch to Change Mode to prevent entry of further
records.