Version 31 of Invoice-Demo

Updated 2015-05-22 19:56:54 by MiHa

if 0 {


Introduction

MiHa 2015-05-13: This is a demo for a tcl/tk-program with all the essential GUI-elements.
It acts as a simple invoice-program with menu, entry-form and output (planned: one printed page).

All the data is just strings of text, no validation, no calculation.

With ideas and code from the following pages:

}


Program 1

 # InvoiceDemo09.tcl - Mike Hase - 2015-05-13 / 2015-05-21
 # http://wiki.tcl.tk/29284
 # Simple "invoice"-themed GUI-demo, with menu, form for data-entry, output to screen and printer.
 # (output to printer is not done yet)

  package require Tk

  set Prog(Title)    "Invoice-Demo"
  set Prog(Version)  "v0.09"
  set Prog(Date)     "2015-05-21"
  set Prog(Author)   "Mike Hase"
  set Prog(Contact)  "[email protected]"
  set Prog(About)    "A GUI-demo (disguised as an invoice-program:)\nPlus a printer-test."

 #---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+

 global data maxFNr startF1 startF2

#set FieldNames1 {"Date                " Title Name Street City Phone }
 set FieldNames1 { Title Name Street City Phone }
#set FieldNames2 { art1Nr art1qty art1desc art1price price1 }
#set FieldNames2 { Article-Number Quantity Description Price Total }
 set FieldNames2 { Item Description Unit-Price Quantity Amount }
                  #---+----1----+----2----+

 set fNr   0
 set artNr 0
 set lenLb 16
 set lenEn 20

 
 #: Boilerplate-text / Company-Address:

 set data(Adr1) "We have no T-Shirts Inc."
 set data(Adr2) "Marketplace 9"
 set data(Adr3) "Bigcity"
 set data(Adr4) "Somewhere"
 set data(Fon)  "555-4321-0"
 set data(Fax)  "555-4321-9"
 set data(Mail) "[email protected]"
                #---+----1----+----2----+----3

 # Build data-entry form:
 ## Todo: put this form into its own dialog

 #: Frame for customer-data:
 frame .f1   ;# -background red

 set field "Date"
 incr fNr
 set data($fNr) $fNr        ;## Test
 set lb [label .f1.lb$field -text $field ]
 set en [entry .f1.en$field -width 10 -textvar data($fNr) ]
 grid $lb $en -padx 4 -pady 2
 grid $lb -sticky e
 grid $en -sticky w


 set   hdr [label .f1.lbHdr -text "Customer:"]
 grid $hdr -columnspan 2  -pady 8 

 foreach field $FieldNames1 {
        incr fNr
        set data($fNr) $fNr        ;## Test
        set lb [label .f1.lb$field -width $lenLb -justify right -text $field ]        
        set en [entry .f1.en$field -width $lenEn -justify left  -textvar data($fNr) ]
        grid $lb $en -padx 4 -pady 2
        grid $lb -sticky e
        grid $en -sticky w
        if { $fNr == 2 } { set startF1 $en }
 }

 #: Frame for article-data:
 # Same layout as above.  Todo: place all fields for an article in one line
 frame .f2   ;# -background green
 incr artNr                                ;# Todo: extend to more articles

 set   hdr [label .f2.hdr -text "Purchase:"]
 grid $hdr -columnspan 2  -pady 8 

 foreach field $FieldNames2 {
        incr fNr
        set data($fNr) $fNr        ;##
        set lb [label .f2.lb$field -width $lenLb -justify right -text $field ]
        set en [entry .f2.en$field -width $lenEn -justify right -textvar data($fNr) ]
        grid $lb $en -padx 4 -pady 2
        grid $lb -sticky e
        grid $en -sticky w
       #if { $startF2 == "" } { set startF2 $en }
        if { ![info exists startF2] } { set startF2 $en }
 }
 set maxFNr $fNr


if 0 {        ;# these buttons can be used as an alternative to the m+menu
 #: Frame for buttons:  
 frame  .f8 -background blue
 button .f8.b1 -text New     -command { data0 }
 button .f8.b2 -text Test1   -command { data1 }
 button .f8.b3 -text Test2   -command { data2 }

 button .f8.b4 -text Show    -command { showData2 }
 button .f8.b5 -text Print   -command { printData }

 button .f8.b6 -text Exit    -command { exit }

 grid .f8.b1  .f8.b2  .f8.b3  .f8.b4  .f8.b5  .f8.b6
 pack .f1 .f2 .f8 -side top
}
 pack .f1 .f2  -side top

 #---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+

 # Menu:

  proc m+ {head name {cmd ""}} {
  #: Menu-creator "m+" by R.Suchenwirth and DKF, 2006-08-24, see http://wiki.tcl.tk/16327
    # Uses the undocumented and unsupported feature ::tk::UnderlineAmpersand
    if {![winfo exists .m.m$head]} {
       foreach {l u} [::tk::UnderlineAmpersand $head] break
       .m add cascade -label $l -underline $u -menu [menu .m.m$head -tearoff 0]
    }
    if {[regexp ^-+$ $name]} {
       .m.m$head add separator
    } else {
       foreach {l u} [::tk::UnderlineAmpersand $name] break
       .m.m$head add command -label $l -underline $u -comm $cmd
    }
  }


 # GUI: define menu, bindings

  . configure -menu [menu .m]
  m+ &File &Open      { Dummy "Open" }
  m+ &File &Save      { Dummy "Save" }
  m+ &File -----
  m+ &File &Exit        exit

  m+ &Edit &Clear       data0 
  m+ &Edit Testdata&1   data1 
  m+ &Edit Testdata&2   data2 

  m+ &Output &List      showData2
  m+ &Output &Show      showData3
  m+ &Output &Print     printData 

  m+ &Help &About       About
  m+ &Help &Help        Help
  m+ &Help -----
  m+ &Help &Debug     { console show }


  bind all <Key-F1>     Help
  bind all <Key-F2>   { console show }
  bind all <Key-F3>     bell
  bind all <Key-F4>     exit

  bind all <Key-F5>     data0 
  bind all <Key-F6>     data1 
  bind all <Key-F7>     data2 
  bind all <Key-F8>     showData2

  bind all <Key-F9>     showData3
  bind all <Key-F10>    printData

 #---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+

  proc Dummy {txt} {
  #: Alert: "Function X not implemented yet"
    bell
    tk_messageBox -icon warning  -title "Demo" \
                  -message "Function '$txt' not implemented yet."
  }

  proc About {} {
  #: Short info about the program
    global Prog
    set txt    "$Prog(Title) $Prog(Version) - "
    append txt "$Prog(Date)\nby $Prog(Author) - $Prog(Contact)\n\n$Prog(About)"
    tk_messageBox -icon info -message $txt -title "About $Prog(Title) $Prog(Version)"
  }

  proc _Help {} { Dummy Help }                ;# needs work

  proc Help {} {
    global Prog
   
    set w .help
    if { [winfo exists $w] } {
        wm deiconify $w
        raise $w
        focus $w
        return;
    }
    toplevel $w
    wm title $w "$Prog(Title) - Help"
    frame $w.f
    text $w.f.t -wrap word
    scrollbar $w.f.sb
    $w.f.t  configure -yscrollcommand "$w.f.sb set"
    $w.f.sb configure -command "$w.f.t yview"

    $w.f.t tag configure h1 -font {{} 18 bold}   -justify center -foreground red
    $w.f.t tag configure h2 -font {{} 14 italic} -justify center
    $w.f.t tag configure h3 -font {{} 10 bold } 

    $w.f.t insert end "$Prog(Title)\n" h1
    $w.f.t insert end "Version $Prog(Version)\n\n" h2

    $w.f.t insert end "$Prog(Title) is a GUI-demo that acts as a simple invoice-program.\nThere is a form for data-enty, and output of that data to screen and printer.\n\n"

    $w.f.t insert end "HOTKEYS:\n" h3
    $w.f.t insert end "* F1 - Show this help.\n* F2 - Activate Debug/Console.\n* F4 - Exit program.\n"
    $w.f.t insert end "* F5 - Edit: Clear entry-fields.\n* F6, F7 - Edit: Fill entry-fields with testdata.\n"
    $w.f.t insert end "* F9 - Output data to screen.\n\n"

    $w.f.t insert end "DEBUG:\n" h3
    $w.f.t insert end "  The console offers access to the program's internals and variables.\n\n"
    $w.f.t insert end "Commands:\n  ?: show console-help, e: Exit program.\n"
    $w.f.t insert end "  d0: Clear entry-fields. d1, d2: Fill-in testdata.\n"
    $w.f.t insert end "  s0, s1, s2, s3: Show data in different formats.\n\n"
    $w.f.t insert end "You can enter any tcl-command in the console, for example:\n"
    $w.f.t insert end "% info proc\n"
    $w.f.t insert end "% info var\n"
    $w.f.t insert end "% puts \$maxFNr\n"
    $w.f.t insert end "% parray data\n"
    $w.f.t insert end "% set data(3) \"Johnson\"\n"
    $w.f.t insert end "\n"

    $w.f.t insert end "CREDITS:\n" h3
    $w.f.t insert end "Written by $Prog(Author) ($Prog(Contact)), source at: $Prog(HomePage).\n"
   
    $w.f.t configure -state disabled
    pack $w.f    -side top   -expand 1 -fill both
    pack $w.f.t  -side left  -expand 1 -fill both
    pack $w.f.sb -side right -fill y
    frame  $w.f2
    pack   $w.f2 -side top -pady 8
    button $w.f2.b -text "Close" -command "wm withdraw $w" -width 9
    pack   $w.f2.b
    wm protocol $w WM_DELETE_WINDOW "wm withdraw $w"
    focus $w.f.t
  };# Help


 #---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+

 # Routines to edit/fill in data:

 proc data0 {} {
 #: Clear data in entry-fields
   for {set f 1} {$f <= $::maxFNr} {incr f} {
      set ::data($f) ""
   }
  #set ::data(1) "2015-05-20"
   set cs [clock seconds]
   set td [clock format $cs  -format "%Y-%m-%d"]
   set ::data(1) "$td"
   focus .f1.enDate
 }

 proc data1 {} {
 #: Fill entry-fields with testdata
   set ::data(1) "2015-05-13"
   set ::data(2) "Mr."
   set ::data(3) "Smith"
   set ::data(4) "Mainstreet 123"
   set ::data(5) "Newtown"
   set ::data(6) "555-6789"

   set ::data(7) "0815"
   set ::data(8) "T-Shirt ultrablack, size XL"
   set ::data(9) " 9.99"
   set ::data(10) "2"
   set ::data(11) "19.98"
   focus $::startF1
 }

 proc data2 {} {
 #: Fill entry-fields with testdata
   set fNr   0
   set values {
      "2015-05-14"
      "Mrs." "Miller" "Highstreet 99" "Oldsville" "555-9876"
      "0816" "T-Shirt XL" "12.34" "1" "12.34"
   }
   foreach v $values {
        incr fNr
        set ::data($fNr) $v
   }
   focus $::startF2
 }

 #---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+

 # Output-Routines:

  proc showData0 {} { 
    puts "INVOICE-data:"
    parray ::data
  }

  proc showData1 {} { 
  #: Show the data on the console
    global data maxFNr

    puts "INVOICE\n=="
    for {set f 1} {$f <= $maxFNr} {incr f} {
      puts "Field#$f : $data($f)"
    }
    puts "--\nTHANKS !"
  }

  proc showData2  {} { 
  #: Show the data in a simple messagebox
    global data maxFNr

    set txt    "INVOICE\n==\n"
    for {set f 1} {$f <= $maxFNr} {incr f} {
      append txt "Field#$f : $data($f)\n"
    }
    append txt "--\nTHANKS !"

    tk_messageBox -icon info -message $txt -title "ShowData2"
  }

  proc showData3 {} {                                ;# from: TkOverload Help
  #: Show the data formatted like an invoice-letter
    global data
    catch {destroy .dia} 
    toplevel .dia
    wm title .dia "Invoice"
    wm geom .dia "+[expr {[winfo x .] + [winfo width .] + 10}]+[winfo y .]"

    text .dia.t -relief raised -wrap word -width 70 -height 23 \
        -padx 10 -pady 10 -cursor {} -yscrollcommand {.dia.sb set}
    scrollbar .dia.sb -orient vertical -command {.dia.t yview}
    button .dia.dismiss -text Dismiss -command {destroy .dia}
    pack .dia.dismiss -side bottom -pady 10
    pack .dia.sb      -side right  -fill y
    pack .dia.t       -side top    -expand 1 -fill both

    set bold   "[font actual [.dia.t cget -font]] -weight bold"
    set italic "[font actual [.dia.t cget -font]] -slant  italic"
    .dia.t tag config    h1    -justify center -foregr blue -font "Times 20 bold"
    .dia.t tag configure h2    -justify left   -font "Times 12 bold"
    .dia.t tag configure bold  -font $bold
    .dia.t tag configure ital  -font $italic
    .dia.t tag configure n     -lmargin1 15 -lmargin2 15

    .dia.t insert end "Invoice\n" h1

    # Todo: place picture with logo here

    set m "--- --- --- --- --- ---\n\n"

    .dia.t insert end "From:\n" h2
    .dia.t insert end "$data(Adr1)\n$data(Adr2)\n$data(Adr3)\n$data(Adr4)\n" bold $m .\n\n 

    .dia.t insert end "To:\n" h2
    .dia.t insert end "$data(2) $data(3)\n$data(4)\n$data(5)\n" bold $m .\n\n 

    # Todo: tweak formatting

   #.dia.t insert end "Item         Description         Unit Price         Quantity         Amount\n" bold $m n 

    foreach field $::FieldNames2 {
        .dia.t insert end "$field    " bold
    }
    .dia.t insert end "\n" 

   #set m "$data(8) x $data(7) $data(9) -- $ $data(10) ea. = $ $data(11)\n"
    set m "$data(7)  $data(8)   $data(9) * $data(10) = $ $data(11)\n"
    .dia.t insert end $m n 

    set m "$ $data(11)\n\n"
    .dia.t insert end "Total:\n" bold $m n

    set m ""
    append m "This product is meant for educational purposes only. "
    append m "Keep away from fire or flames. "
    append m "For recreational use only. "
    append m "Some assembly required. "
    append m "Do not use while operating a motor vehicle or heavy equipment. "
    append m "Any resemblance to real persons, living or dead is purely coincidental. "
    append m "Avoid contact with skin. "
    append m "No other warranty expressed or implied. "
    append m "One size fits all. "
    append m "Do not disturb. " 
    append m "Action figures sold separately. "
    append m "Batteries not included. "
    append m "Price does not include taxes. "
    append m "Use only as directed. "
    append m "Keep away from sunlight. "
    append m "All rights reserved. "
    append m "Keep cool. \n"
    .dia.t insert end "Standard Disclaimer\n" bold $m n

    .dia.t config -state disabled
    focus .dia.t
  }

 # Todo:
  proc printData {} { Dummy printData }

 #---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+

 #: Test+Debug:

  proc ?    {} { help }
  proc help {} {
    puts "\n$::Prog(Title) $::Prog(Version)"
    puts "--"
    puts "argv0 : $::argv0"
    puts "argv  : $::argv"
    puts "tcl/tk: $::tcl_patchLevel / $::tk_patchLevel \n"
    puts "d0,d1,d2, s0..s3:data0,data1,data2, showData0..3  ?:help  e:exit \n"
  }

  proc d0 {} { data0 }
  proc d1 {} { data1 }
  proc d2 {} { data2 }

  proc s0 {} { showData0 }
  proc s1 {} { showData1 }
  proc s2 {} { showData2 }
  proc s3 {} { showData3 }

  proc e  {} { exit }

 #---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+

 #: Main:

  wm title . "$Prog(Title) $Prog(Version)"
  help

 # Todo: make and include an icon here
  if {[file exists  demo.ico]} {
    wm iconbitmap . demo.ico
  }

 focus -force .

 ### EOF ###

Example icon: InvoiceDemo_icon

It looks like the upload-procedure of the wiki doesn't like .ico - files.
So, just copy any icon you like to the location of the program, and rename it to demo.ico


Output

Dump of the contents of the entry-fields, filled with testdata:

INVOICE
==
Field#1 : 2015-05-13
Field#2 : Mr.
Field#3 : Smith
Field#4 : Mainstreet 123
Field#5 : Newtown
Field#6 : 555-6789
Field#7 : 0815
Field#8 : T-Shirt ultrablack, size XL
Field#9 :  9.99
Field#10 : 2
Field#11 : 19.98

Remarks

MiHa: This is a nice small demo of a simple program using only basic, essential GUI-features:

  • Menu
  • Buttons (to see them, change that "if 0"-section to "if 1")
  • Hotkeys (F1..F8, etc.)
  • Labels
  • Entry-fields
  • Messagebox
  • Title at top of window
  • Icon at top of window (currently, this needs an external file)
  • frames, pack and grid to manage the layout

There is lots of more advanced GUI-stuff not covered so far, e.g.:

  • Checkboxes
  • Radiobuttons
  • Fancy textformating, fonts (i.e. for a nice help-window)
  • Listbox / Multicolumn listbox / Tables
  • Scrollbars
  • dialog-windows separate from the main-window (i.e. for the data-entry form)

But if any of that gets included, and the program gets more complex,
it might be better to put that into a separate program.

The data is just simple strings of text, all of the same length, no validation, no calculation, no special logic, etc.
This definitely needs work for any real-world application.
BTW, I haven't found yet any good, nice, short routine for data-entry (such as m+ for menus).

Also, there is no file-I/O for data or configuration.

I haven't looked into the printing stuff yet, so if anybody thinks he is up to the task, you are welcome to fill in that part.


See also

  • GUI Primer - essentially my notes researching GUI-stuff on tcl/tk

All in all, it looks like printing from Tcl/tk in a "native, portable way" is kind of a hard problem, and still mostly unsolved.