How can I make effective use of the BWidgets toolset

Purpose: to provide a community updatable web page demonstrating uses for the BWidget toolset, enhancing the docs, pointing to tutorials, etc.


Can anyone discuss the various cross platform issues relating directly to BWidgets? For instance, when I try the demo on a Mac using a Tclkit version of Tcl and Tk, I find a number of errors when trying to test the progress indicator, the -armcommand, the french and german language dialog boxes, the select color dialog box, etc.


What additional documentation is needed for BWidgets?

What new widgets have you written based on the BWidgets toolkit? Is the source available for re-use?


GWM How do I set the colours of the background of the MainFrame menu? We have access to the menu entries, to the frame but not to the top level menu items (eg the "File" menu exists but the background of the button which drops the menu isnot accessible). The background of the menu that drops is accessible. I have even written a recursive routine to find all widgets beneath the MainFrame, and changed their colour - the File, Edit, Options etc menu remains boringly grey.

I suppose I could add yet another toolbar, whose colour I can set, and add my ownmenus. But then what is the point of the -menu option if I cannot change its config?


BWidget Examples

The BWidget documentation could really benefit from a few examples, illustrating some of the details.

Paned Window

    package require BWidget

    set pw [PanedWindow .topframe -side bottom]
    for {set i 0} {$i<5} {incr i} {
        set p [$pw add -minsize 100]
        set t [text $p.text]
        pack $t
    }
    pack $pw
    pack .topframe

ScrollableFrame and ScrolledWindow

AM (24 june 2004) To create a scrollable frame, that is, a window that can be moved via scrollbars, use code like:

   #
   # Add a scrollable frame, so that we do not have to worry about the size
   #
   set sw  [ScrolledWindow $tabwin.sw -relief sunken -borderwidth 2]
   set sff [ScrollableFrame $tabwin.sw.f]
   $sw setwidget $sff

   set sf  [$sff getframe]
   #
   # Add the various elements
   #
   set lid [label  $sf.name -text "Name"] 
   set eid [entry $sf.editname -textvariable name]

   pack $sw -fill both -expand yes
   #  do not pack $sff
   pack $lid $eid

(So the trick is: first create a scrolled window, then create a scrollable frame inside it and have it managed by that scrolled window. This extra level is required, because a frame has no xscroll/yscroll subcommand.)

KJN (2004-06-26) added pack statements. Note that the scrollable frame itself should not be packed. The command "$sw setwidget $sff" tells the scrolled window to do geometry management of the scrollable frame. Packing the scrollable frame is unnecessary and will lead to an error.


2003-03-13, Svenn Are Bjerkem: Tried this example with ActiveTcL 8.4.0.1 and got following error:

 bad option "identify": must be cget or configure
    while executing
 "$w identify $x $y"
    (procedure "::tk::panedwindow::Motion" line 3)
    invoked from within
 "::tk::panedwindow::Motion . 102 2 "
    (command bound to event)

checked the documentation for tk builtins for tk and found that the widget panedwindow has the command identify. For some reason I want to use BWidget, but use the builtin. How come?

AK: AFAIK this could be a confusion in the bindings. I.e. both Tk's panedwindow and Bwidgets panedwindow using the same class name. This causes the Bwidgets panedwindow to get the bindings for Tk's panedwindow, which do not work. I thought we had a bug report about this in Tk or Bwidgets, but can't seem to find it. Or maybe it was something on c.l.t.


Recently, George Petasis answered the following question on comp.lang.tcl:

What can I do to make native Tk widgets and BWidgets look closer, when using them on Linux?

George answered:

 option add *highlightBackground [. cget -background]

(this will make your white borders the same colour as the rest of the window...)

If you want to remove completely these "frames", simply use:

 option add *highlightThickness 0

To use the icons provided with BWidget, you can either

 set im [image create photo -file [file join ::BWIDGET::LIBRARY images folder.gif]]

But it's much easier to use the undocumented (but used in BWidget Demo)

 set im [Bitmap::get folder]

Thanks to de for pointing this out!


Example of how to do Menus

Every one of my scripts that uses BWidgets starts out by defining the mainframe for the application via the MainFrame bwidget. This widget simplifies the construction of the top level for your application by providing a place to easily define pull down menus. It also permits a simple "indicator" area at the bottom which contains any text you want. I often put the program name and version number.

Here is an example directly from a recent application I built to track my hours on various projects. Note how easy it is to build the menu as a string and pass it to the widget construction command. Hot keys are defined by placing the ampersand before the critical hot key character. One of the fields (I've forgotten which one) can be a variable name. If the variable is zero then the entry is greyed out. Handy for doing things like turning off "Save" if nothing has changed yet. I also try to make the "-command" arguments a simple function call with no arguments if possible. That way the commands can be used by other parts of the application when necessary (particularly useful for save and open file operations.) Also built in is a little bit of "help" feed back. The text "New Hours Database" (in the "&New" menu item) will be displayed at the bottom of the Mainframe when the user mouses over the entry. Cheap but effective documentation which requires a tiny bit of effort to create.

 set menudesc {
    "&File" all file 0 {
        {command "&New"           {} "New Hours Database" {} -command file_new}
        {command "&Open"          {} "Open existing hours database" {} -command file_open}
        {command "&Save"          {} "Save to hours database" {} -command file_save}
        {command "Save &As"       {} "Save hours database to new file" {} -command file_save_as}
        {command "Show &Console"  {} "Show TCL console" {} -command {console show}}
        {command "&Hide Console"  {} "Hide TCL console" {} -command {console hide}}
        {command "&Quit"          {} "Quit hours.tcl" {} -command { file_quit}}
    }
    "&Edit" all edit 0 {
        {command "&Copy"  {} "Copy" {} -command {console show;puts "Copy not active"}}
        {command "C&ut"   {} "Cut" {} -command {console show;puts "Cut not active"}}
        {command "&Paste" {} "Paste" {} -command {console show;puts "Paste not active"}}
    }
    "&Interval" all event 0 {
        {command "&New" {} "Begin new interval" {} -command interval_new}
        {command "&Edit" {} "Edit selected interval" {} -command interval_edit}
    }
    "&Report" all report 0 { 
        {command "&Mon-Sun Timecard" {} "Prepare Monday through Sunday Timecard" {} -command report_pti_timecard}
        {command "&Hours"    {} "Prepare total hours report" {} -command report_hours }
        {command "&Full"     {} "Prepare Full report of hours plus journal entries" {} -command report_full}
        {command "&Weekly"   {} "Prepare weekly summary" {} -command report_weekly }
        {command "&Journal"  {} "Prepare Journal report in HTML format" {} -command report_journal}
    }
    "I&nvoice" all invoice 0 {
        {command "&Mark" {} "Mark selected intervals as invoiced" {} -command invoice_mark}
    }
    "&Help" all help 0 {
        {command "&Info" {} "Program Information" {} -command help_info}
        {command "&About" {} "About the Program" {} -command help_about}
    }
 }

 set state(mainframe) [MainFrame .mainframe -menu $menudesc -textvariable state(status)]
 pack $state(mainframe) -fill both -expand yes
 $state(mainframe) addindicator -text "Hours.tcl Rev $state(rev)"

 set mf [$state(mainframe) getframe]; # Place where application widgets get stuffed.

If you think this example sucks then by all means fix it, or trash the whole thing! EKB I wonder if this is necessary? Humility is good, but this seems harsh. Also, since anyone can fix it or trash it, there's no need to say it! :-) Personally, I found it useful.

EKB I added the following lines at the top of the script to make it run "out of the box":

 package require BWidget
 set state(rev) "0.1"

Nono I would like to know : how can you change the background color for example for the entire 'File' section in your menu ? I understand you can do it by adding the command -bg "blue" (for example) in all of the commands (New, Open, ...) but is there another way (global way) ?


Dialog Boxes

I used to be afraid of creating dialog boxes for my apps. They were cumbersome even though Welch's book gave some pretty good code for creating them. Now, with BWidget I use them a lot. The challenge is to draw the graphics for the dialog, give it control, then have your code that "draws" the dialog wait for the user to close out of it by some mechanism. In the example below, the dialog is built at the beginning when all of the widgets are created but it is only drawn (made visible) when necessary. The

 Dialog::enddialog .d 2 

command is used to terminate the dialog after the user clicks on the button "ENTER NOW". The "add" commands for the widget create a set of user defined buttons at the (top/bottom) of the Dialog box. These can be anything you want but typically include at least a <CANCEL> button and an <OK> or <ENTER> button. The buttons are numbered starting at 0. The widget is dumb and needs to be told which button is the cancel button using the "configure" option. After the button is clicked the dialog ends automatically for the <cancel> button or the <ENTER AS-IS> button. However, the <ENTER NOW> button uses the -command switch so the command must explicitly call the "Dialog::enddialog .d 2" to close down the dialog and return the value of "2" to indicate the button that was pressed.

Like most BWidgets you must use the "getframe" option to retrieve the path where you can begin stuffing your widgets.

 set state(editDialog) [Dialog .d -side bottom -title "Interval Edit" -transient no]
 .d add -text CANCEL
 .d add -text "ENTER AS-IS"
 .d add -text "ENTER NOW" -command "set_stop_now; Dialog::enddialog .d 2"
 .d configure -cancel 0
 set f [.d getframe]; # User frame
 #
 # Pack the user frame with all the widgets you want to have appear when
 # it is "drawn". Remember that any textvariables you associate with entry
 # boxes must be global variables!
 #

Here is the code that is called when you want to "pop" up the dialog. Note, that in this case I wanted the dialog box to keep poping up if there was an error in the data that the user entered. Hence the while loop. This isn't strictly necessary but is a common enough thing that I left it in the example.

 proc dialog_show {} {
   global state
   set result "error"
   while {[string compare $result "error"] == 0} {
      # Draw the dialog box. The global variable state holds the widget
      # path for this dialog. The "Dialog" widget has a command called
      # "draw" that makes the dialog (and all the widgets in side of it)
      # visible. The buttons that you "add"ed when the dialog was created
      # have numbers associated with them that are returned when the
      # dialog is terminated. That is how you tell what button was pressed
      # to close it.
      set dialog_result [$state(editDialog) draw];
      if {$dialog_result == 1 || $dialog_result == 2} {
         # user hit button <ENTER AS-IS> or <ENTER NOW> to accept the data
         set result "ok"
         # 
         # USER COMPUTATION FOR "ENTER"ing data. If the data
         # is bad then set result = "error" else set it to "ok"
         #
      } else {
         # User decided to hit CANCEL button
         set result "cancel"
         logMessage "dialog canceled \n" note
      }
   }
   return $result
 }

The widgets inside the dialog will keep their values between invocations of the dialog because you must use global variables for the textvariable item when the widget is built. Consequently, it is a good idea to reset them before you draw the widget.

If you think this example sucks then by all means fix it, or trash the whole thing!


See Enhancing BWidget for more BWidget help.

See also A vertical-tab notebook - Windows Registry Browser - Multi-column display in text widget - Beveled lines - Creating a BWidget Widget - Adding 'history' to BWidget ComboBox widgets.