HOW TO Use the Toolbar and
Toolbutton Classes In dBASE

Last Modified: January 31, 2001
Ken Mayer, Senior SQA Engineer
dBASE, Inc.

Example files available in tooluse.zip


NOTE: This document was originally written for Visual dBASE 7.0/7.01, it has been updated for dB2K (release 1) and later releases of dBASE to include information about new properties, events, etc., and any new controls since the document was first written. If a control is new it will be noted. In updating this document, the images were left "as is" unless it was felt to be absolutely necessary to change them ...

In addition, this document refers to dB2K a lot, but unless it is about a dB2K specific aspect, the text can be used for Visual dBASE 7.0 through Visual dBASE 7.5.


The Toolbar and Toolbutton classes that are built in to dB2K are a bit of an enigma to most developers, due to the way that they function. However, many developers wish to use these in their applications to make their applications have the same look and feel of other Windows applications.

This HOW TO document is designed to help a developer learn the basics of their use, and how to hook them up to their forms. It will cover the differences in use between MDI and SDI forms, and so on.

In order to do this, there are some sample toolbars that ship with the samples, and we will use these as a basis for designing our own. For this document, we will create a single toolbar that has a lot of the functionality needed for most forms. We will then see how to use this toolbar with different types of forms ...


What Is a Toolbar?

I suppose the first thing we need to define is what the toolbar is:

A Toolbar is a container with special properties.

An example of a toolbar is something you see all the time when you work with dB2K -- at the top of the dB2K IDE screen, under the titlebar (the one that says "dB2K") and under the menus, are a series of pushbuttons -- this is a toolbar.

As you work in different parts of the IDE, the content of the toolbar changes, and some of the buttons are enabled or disabled based on what you are currently doing (example, if you are in the source editor, and you have not highlighted some text, the clipboard buttons are disabled).

The Toolbar class in dB2K has the following properties:


How Do You Design a ToolBar?

Unfortunately the developers of Visual dBASE 7 did not give us a design surface for the toolbar class, and the R&D team at dBASE, Inc. have not had time to create one yet. This would make things much easier. However, given the nature of toolbars and the fact that their display is almost entirely automatic, this is not a serious drawback. If a toolbar is docked (the default), the only control you have on the display is the bitmaps on the buttons, the order the buttons appear and whether or not each button is enabled. If the toolbar is floating (not docked) you can also assign the title bar text and the toolbar's position.

At this time, the only way to design a toolbar is through code, and a bit of trial and error.

To create a toolbar, you must define it as a subclass of the pre-defined toolbar class built in to dB2K (or your own subclass of the stock dB2K toolbar class):

   class myToolbar of toolbar
      // properties go here

      // definition of toolbuttons go here

      // code (toolbutton onClick, toolbar onUpdate ...)
      // goes here
   endclass

In the dB2K samples directory is toolbutton program (note that this is not in a .CC file, although you could create them in custom class files), this file is called: CLIPBAR.PRG.

We could use those exactly "as is", but the code gets a bit complicated, so what we will do is simplify them a bit. The Samples group at Borland (now Inprise) tried to make these into "generic" toolbar classes which means that they can be used with a variety of applications. There are some drawbacks to that approach when trying to learn how they work, as a lot of extra code has been added to cover various contingencies that may not be necessary for your toolbars. So we will examine simplified versions of these toolbars.


Putting Toolbuttons in the Toolbar

We need to take a look at the toolbutton class itself, and how it works. Toolbuttons are the actual buttons that get placed in the toolbar container. These are related to the pushbutton class in dB2K, but are not true subclasses of the pushbutton. The only place you can use a toolbutton is on a toolbar -- in otherwords, you cannot place a toolbutton object directly onto a form.

Toolbuttons have the following properties and events:

A simple toolbutton might be one that navigates in a table, perhaps a "top" button that always moves to the first row. For our sample, create MYTOOLBAR.CC (MODI COMM MYTOOLBAR.CC at the command window), and enter the following:

   class myToolbar of toolbar
      // properties go here
      this.flat = true
      this.floating = false

      // definition of toolbuttons go here
      this.FirstToolButton = new ToolButton( this )
      with ( this.FirstToolButton )
           bitmap   := "RESOURCE TS_FIRST"
           speedTip := "First Row"
           onClick  := class::First_onClick
      endwith

      // you need to have code here for your onclick events:
      function First_onClick
      return ( this.parent.form.rowset.first() )

   endclass

(What I did was extract code from a Visual dBASE 7.x example "VCRBAR.PRG" and simplify it a bit ...)

The code in the function "First_onClick" simply navigates in the rowset that is attached to the form. Note the syntax for this:

  this.parent.form.rowset.first()

"this" refers to the toolbutton that called the code.
"parent" refers to the toolbar which is the container of the toolbutton.
"form" is the form that the toolbar is "attached" to (we'll see how this works later).

There are some possible problems here. The first is if you have this toolbar running and attached to a form that does not have a rowset assigned to the rowset property of the form. The second is that you don't really need to call this if you are already on the first row of the table. Using the toolbar's event "onUpdate" we can actually deal with this fairly easily.

We need to add a new method to the toolBar class (this is a hook into the class's event which will fire whether or not we assign any code to the event), which can go pretty much anywhere, but for simplicity's sake we will put it below the definitions of the buttons and above the onClick events for the buttons:

   function onUpdate
      // check to see if there is a rowset on the form:
      if type( "this.form.rowset" ) # "O"
         this.FirstToolbutton.enabled = false
      else
         // check to see if we are already at the first
         // row:
         this.FirstToolbutton.enabled := ;
              NOT this.form.rowset.atFirst() 
      endif
   return

The onUpdate event will fire fairly constantly as long as there is no interaction with the toolbar. If the mouse is over the toolbar, or the user is clicking buttons, then the onUpdate event is not firing -- otherwise it will execute the code shown above quite often.


Making The Toolbar More Useful

Our sample toolbar is not very useful at the moment, because it only has a single toolbutton on it. We can build it up, which we will do a bit at a time.

To continue where we are now, let's finish the VCR part of the toolbar -- the part used to navigate through the rows of a table on a form.

First, we need three more toolbuttons. These are going to do the "Previous", "Next", and "Last" settings that are common to most of this sort of toolbar. Note that the buttons will appear on the toolbar in the order they are defined in your source code.


bitmaps The bitmaps being used in these examples have a resource name of "TS_xxxx" -- the first two letters stand for "Toolbar Small", the underscore is a separator, and the "xxxx" part is some descriptive name for the actual image.

These were created by the Samples and Art groups at Inprise specifically for toolbars. There are also "TL_xxxx" which are the exact same images, but they are a touch larger, hence the "L" instead of "S" in the resource name.

To see what is in the standard RESOURCE.DLL file that can be used for your images for toolButtons, the simplest way is to go to the command window, and do the following:

       t = new ToolBar()
       t1 = new ToolButton( t )
       inspect( t1 )
In the inspector, click on the bitmap item, click the "tool" button. A dialog will appear. In the combobox, select "Resource" (it will default to "filename"), and on the right side of the dialog, click on the "tool" button again. You will see a new dialog and you can scroll through it until you find the image you want. Note it -- some images use numbers, some use text. If the image you want to use has text, then the string for your bitmap property will be:

      "RESOURCE bitmapname"

If the image you want uses a number, then the string will be:

      "RESOURCE #number"

The "#" sign is vital. Finally, if what you see has two images (the first set of images that are named "PS_something", for example) you have a SPLIT bitmap, and you need to modify the string so that the word "RESOURCE" is followed immediately by ":2", i.e.,

      "RESOURCE:2 PS_FIRST"

In addition, you can use button images if you have bitmap files that are the righ size. Visual dBASE 7.x shipped with a set of button images in the ..\Visual dBASE\ARTWORK\BUTTON directory, but these artwork folders were removed for dB2K. These are all split images. To view them is a bit more difficult, unless you create a form (or use the one in the dUFLP library) to do so. To use one, the bitmap property should read:

      "FILENAME:2 path\filename.bmp"

Disabled Bitmaps
One difference between standard pushbuttons and toolbuttons is that toolbuttons only have one bitmap property called "bitmap". Toolbuttons don't have a separate upBitmap, focusBitmap, downBitmap, and disabledBitmap. There are reasons for this. First, toolbuttons never get focus, so they don't need a focusBitmap. Second, VdB automatically handles dimming the bitmap of disabled toolbuttons, so you don't need a disabledBitmap. The separate downBitmap is just not included because (I suppose) it's passé.

Now that we've brought up the subject of dimming a toolbutton's bitmap, let's consider what happens to the bitmap when you disable a toolbutton. VdB automatically interprets the colors of your bitmap to determine a dimmed representation of it. In doing so, one (and only one) color is seen to be transparent. With a 16 color bitmap, this color is rgb(128,0,128) which looks like a dark purple. Even using GIF files with a transparent color will not get around this limitation. GIF transparent colors appear to be ignored. If your bitmap does not contain the transparent color as a background, the background color will be interpreted as either light or dark and rendered accordingly. This may, or may not provide the desired results. In any case, the user has control of the Windows color scheme and can change it at their whim. This means that your bitmap may not display correctly if the user changes colors. The bitmap will either display the background when not dimmed, or will not dim properly, or both. So, for best results, it is recommended that you use a 16 color bitmap with the background set to dark purple (Red=128, Green=0, Blue=128) for your toolbuttons.


Add the following into MyToolbar:

   this.PreviousToolButton = new ToolButton( this )
   with ( this.PreviousToolButton )
        bitmap   := "RESOURCE TS_PREV"
        speedTip := "Previous Row"
        onClick  := class::Previous_onClick
   endwith

   this.NextToolButton = new ToolButton( this )
   with ( this.NextToolButton )
        bitmap   := "RESOURCE TS_NEXT"
        speedTip := "Next Row"
        onClick  := class::Next_onClick
   endwith

   this.LastToolButton = new ToolButton( this )
   with ( this.LastToolButton )
        bitmap   := "RESOURCE TS_LAST"
        speedTip := "Last Row"
        onClick  := class::Last_onClick
   endwith

And add the following onClick events:

   function Previous_onClick
     local bNext
     // navigate one row "back"
     bNext = this.parent.form.rowset.next(-1)
     // if that didn't work, we're at the endOfSet
     // and need to navigate back to where we were
     if ( not bNext )
         this.parent.form.rowset.next()
      endif
   return

   function Next_onClick
      local bNext
      bNext = this.parent.form.rowset.next()
      if ( not bNext )
         this.parent.form.rowset.next(-1)
      endif
   return ( bNext )

   function Last_onClick
   return ( this.parent.form.rowset.last() )

And finally, we need to modify the onUpdate for the toolbar to include all the buttons (we don't want them enabled if there's no rowset, and we don't need the last button enabled if we're on the last row ...). We're going to make this a touch more complex. It's possible that during the processing of a form it may start with no rowset attached to the form's rowset property, but this may change. This adds a layer of complexity to the code below, but it makes it more flexible:

   function onUpdate
      // check to see if there is a rowset on the form:
      local bRowset, bAtFirst, bAtLast
      bRowset = type( "this.form.rowset" ) == "O"
      if bRowset 
         bAtFirst = this.form.rowset.atFirst()
         bAtLast  = this.form.rowset.atLast()
         this.FirstToolbutton.enabled    = NOT bAtFirst
         this.PreviousToolbutton.enabled = NOT bAtFirst
         this.NextToolbutton.enabled     = NOT bAtLast
         this.LastToolbutton.enabled     = NOT bAtLast
      else // No rowset ...
         this.FirstToolbutton.enabled    = false
         this.PreviousToolbutton.enabled = false
         this.NextToolbutton.enabled     = false
         this.LastToolbutton.enabled     = false
      endif
   return

Wow. Now that we have all that, I guess we need to see if we can get it to work, eh?

Save your code, and exit the source editor.


Testing Our Toolbar

To test this, first, we need a form that has a rowset on it. You can use an existing form, or create a quick form using the form wizard for this. There are two ways a form may be opened (open() and readmodal()) and we will see how this works in both cases. Do the following in the command window, substituting "formname" for the name of your test form. The first test is with a "modeless" form:

   set procedure to formname.wfm additive 
   set procedure to MyToolbar.cc additive
   f = new formnameform()
   t = new MyToolbar()
   t.attach( f )
   f.open()

Note that when the form opens, the toolbar appears at the top of the screen (above the 'normal' toolbar -- this is actually as designed, as we'll see later in this document), and that if you are at the top of the rowset that the first two toolbuttons are not enabled. However, if you click on the buttons, you will see the form's rowset navigate as it should.

Once you are done experimenting, close the form, and notice that the new toolbar is gone.

Now, let's try opening the same form as sdi, and we will see some differences:

   t.detach( f )     // Detach the toolbar from the form
   t=null            // "Null" the object pointer out
   f.mdi = false
   t=new mytoolbar() // Re-instantiate the toolbar
   t.attach( f )     // Re-attach the toolbar ...
   f.open()

Notice that the toolbar is now on the form, and that the form's objects have all shifted down to make room for it. (We'll talk about this ...)

What happens if you want to do this on a completely modal form? (Close the form and do the following)

   f.readModal()

The onUpdate event does not appear to be firing on a modal form. This may be as designed (the toolbar is waiting for the form to stop executing so it can do it's thing ...), and it may be a bug (it has been submitted to dBASE, Inc. by the author as a bug, we'll see what they have to say about it). It is possible to work around this problem, but this document will not get into it ...


NOTE:
There are some interesting behavioral problems in assigning specific values in specific sequences.

Gary White spent a bit of time tinkering with this, and has come up with the following when when changing the MDI property of forms:

      f=new tbartestform()
      f.mdi = true
      f.t = new mytoolbar()
      f.t.attach(f)
      f.open()
   

This works fine. Notice that the mdi property is being set before attaching the toolbar. In this case, the toolbar should appear on the _app.framewin (Application Frame).

      f=new tbartestform()
      f.mdi=true // for test purposes
      f.t = new mytoolbar()
      f.t.attach(f)
      f.mdi = false // <-- Problem here
      f.open()
   

In the above example, if you set the MDI property to false after attaching the toolbar, the toolbar appears attached to _app.framewin, rather than to the form, as you would expect for an SDI form. Now, before you go thinking "Cool! I can use that!" take a look at the last example. You'll see that this misdirection can be a source of instability problems and may cause you a great deal of grief later on.

      f=new tbartestform()
      f.mdi=false
      f.t = new mytoolbar()
      f.t.attach(f)
      f.open()

This works as expected, because the MDI property is being set to false before attaching the toolbar -- in this case, the toolbar will be attached to the form as it should be.

Finally, if the form is instantiated with MDI set to false, then you attach a toolbar, and switch the MDI property to true, you will get a GPF. (This is a particularly nasty one ...)

      f=new tbartestform()
      f.mdi=false // for testing
      f.t = new mytoolbar()
      f.t.attach(f)
      f.mdi=true  // this causes the gpf, but it won't
                  // appear until you open the form
      f.open()
   

Obviously it is quite important to get this sequence of statements just right. The important thing to remember is to set the mdi property before you attach the toolbar. If you need to change the mdi property after attaching a toolbar, you'll need to detach the toolbar and stub out the reference to it, then re-create the toolbar in order to avoid problems. Assuming the previous example where f is the form, f.t is the toolbar, and the form.mdi=false:

      f.t.detach(f)       // detach the toolbar
      f.t=null            // stub out the toolbar reference
      f.mdi=true          // now change to mdi
      f.t=new mytoolbar() // re-create toolbar
      f.t.attach(f)       // and re-attach
   



Adding Functionality To the ToolBar

We can add functionality in two ways -- we can add more toolbars to the form, OR we can put a bunch of toolbuttons onto one toolbar.

The disadvantage to adding multiple toolbars, is that they simply stack up, one under the other. This may not be desireable (it's not for any application I develop, but ...).

The advantage is that each toolbar then can be pulled off the form or application as a floating palette by the user, or left where it is. This means that they can be independant of each other. Another advantage is easier reuse of code. All your toolbars won't need all the buttons. With them grouped into logical, smaller toolbars, you can simply add the functionality with an existing toolbar.

For our purposes, we'll simply be adding to the toolbar.

Of course, one thing that we might want to consider is putting a separator between the sections of the toolbar. It helps break up the purpose of the different parts of a toolbar if we do this. So, the first thing we should do is add a new toolbutton to our toolbar class:

   this.Separator1ToolButton = new ToolButton( this )
   with ( this.Separator1ToolButton )
        separator = true
   endwith

Note that the only property we need is the separator property.

The next thing we might want to add are the standard clipboard buttons used to cut, copy and paste text into/out of a control. The visual controls in dB2K that have the ability to use the Windows clipboard have methods built-in to handle these abilities, so we do not have to write any code to access the Windows clipboard.

What we do need to do is create the buttons, and attach code in the onClick events and the onUpdate event for the toolbar.

Once again, we will be simplifying the code in the samples that ship with dB2K.

First we'll add the following toolbuttons (after the separator we added above):

   this.CutToolButton = new ToolButton( this )
   with ( this.CutToolButton )
        bitmap   := "RESOURCE TS_CUT"
        speedTip := "Cut"
        onClick  := class::Cut_onClick
   endwith

   this.CopyToolButton = new ToolButton( this )
   with ( this.CopyToolButton )
        bitmap   := "RESOURCE TS_COPY"
        speedTip := "Copy"
        onClick  := class::Copy_onClick
   endwith

   this.PasteToolButton = new ToolButton( this )
   with ( this.PasteToolButton )
        bitmap   := "RESOURCE TS_PASTE"
        speedTip := "Paste"
        onClick  := class::Paste_onClick
   endwith

We need the onClick event code for these new toolbuttons:

   function Cut_onClick
   return ( this.parent.form.activeControl.cut() )

   function Copy_onClick
   return ( this.parent.form.activeControl.copy() )

   function Paste_onClick
   return ( this.parent.form.activeControl.paste() )

And we need to modify the onUpdate to determine if these should be enabled or not ... the way we do that is to see if the copy method is a "Function Pointer" for the form's active control (the current control). What should happen when the form is running is -- as you (or your user) move through the controls on the form (tab, mouse, whatever), these toolbuttons will be enabled or disabled based on whether or not the control has a copy() method (which is built-in to the control).

      this.LastToolbutton.enabled     = NOT bAtLast
      // New Code:
      bClip = ( TYPE("this.form.activeControl.copy") == "FP" )
      this.CutToolButton.enabled      = bClip
      this.CopyToolButton.enabled     = bClip
      this.PasteToolButton.enabled    = bClip

And you can also add the code if check if there's no rowset on the form to disable these buttons ... Unfortunately, VdB does not surface a way to tell if any text is currently selected to provide more intelligent enabling/disabling the cut and copy functions. If you find a way to get that using the API, please post a message in the VdB news groups.

Another set of buttons which we'll add to our toolbar are ones that handle rowset editing operations -- allowing us to edit the current row, add a new row, delete a row, save changes, cancel changes ...

While I am using the EDITBAR program as a basis for the following, the one button that is missing from that program is an EDIT toolbutton. This is useful if you have your rowset's autoEdit property set to false -- this is how all of my applications work (otherwise there is no way for the user to actually modify a row!).

   this.Separator2ToolButton = new ToolButton( this )
   with ( this.Separator2ToolButton )
        separator = true
   endwith

   this.EditToolbutton = new ToolButton( this )
   with ( this.EditToolButton )
        bitmap   := "RESOURCE TS_EDIT"
        speedTip := "Edit Row"
        onClick  := class::Edit_onClick
   endwith

   this.AppendToolbutton = new ToolButton( this )
   with ( this.AppendToolButton )
        bitmap   := "RESOURCE TS_APPEND"
        speedTip := "Add Row"
        onClick  := class::Append_onClick
   endwith

   this.DeleteToolButton = new ToolButton( this )
   with ( this.DeleteToolButton )
        bitmap   := "RESOURCE TS_DELETE"
        speedTip := "Delete Row"
        onClick  := class::Delete_onClick
   endwith
   
   this.SaveToolButton = new ToolButton( this )
   with ( this.SaveToolButton )
        bitmap   := "RESOURCE TS_SAVE"
        speedTip := "Save Row"
        onClick  := class::Save_onClick
   endwith

   this.AbandonToolButton = new ToolButton( this )
   with ( this.AbandonToolButton )
        bitmap   := "RESOURCE TS_ABANDON"
        speedTip := "Abandon Row"
        onClick  := class::Abandon_onClick
   endwith

And the following onClick event code:

   function Edit_onClick
   return ( this.parent.form.rowset.beginEdit() )

   function Append_onClick
   return ( this.parent.form.rowset.beginAppend() )

   function Delete_onClick
      local bDelete, bFirst
      bDelete = false
      bFirst  = false
      if ( not this.parent.form.rowset.endOfSet )
         if ( MSGBOX("You are about to delete the current row." ;
                   + CHR(13) ;
                   + "Click Yes to delete the current row.", ;
                     "Alert", ;
                     4) == 6 )
            bFirst  := this.parent.form.rowset.atFirst()
            bDelete := this.parent.form.rowset.delete()       
            if ( not bFirst ) 
               this.parent.form.rowset.next(-1)
            endif
            if ( this.parent.form.rowset.endOfSet )
               this.parent.form.rowset.next()
            endif
         endif
      endif
   return ( bDelete )

   function Save_onClick
   return ( this.parent.form.rowset.save() )

   function Abandon_onClick
   return ( this.parent.form.rowset.abandon() )

And this is how the toolbar's onUpdate should look (this is the whole thing). The additional code in the first block checks to see if we'are at the endOfSet, which would mean we can't edit or delete a row (therefore, we would disable those buttons), and the save and abandon buttons would only be enabled if a modification occurred to the row ...

   function MyToolBar_onUpdate
      // check to see if there is a rowset on the form:
      local bRowset, bAtFirst, bAtLast
      bRowset = type( "this.form.rowset" ) == "O" 
      if bRowset 
         bAtFirst = this.form.rowset.atFirst()
         bAtLast  = this.form.rowset.atLast()
         this.FirstToolbutton.enabled    := NOT bAtFirst
         this.PreviousToolbutton.enabled := NOT bAtFirst
         this.NextToolbutton.enabled     := NOT bAtLast
         this.LastToolbutton.enabled     := NOT bAtLast
         bClip = ( TYPE("this.form.activeControl.copy") == "FP" )
         this.CutToolButton.enabled      := bClip
         this.CopyToolButton.enabled     := bClip
         this.PasteToolButton.enabled    := bClip
         bEoF = this.form.rowset.endOfSet
         this.EditToolButton.enabled     := NOT bEof
         this.AppendToolButton.enabled   := true
         this.DeleteToolButton.enabled   := NOT bEoF
         bModified = this.form.rowset.modified
         this.SaveToolButton.enabled     := bModified
         this.AbandonToolButton.enabled  := bModified

      else // No rowset ...
         this.FirstToolbutton.enabled    := false
         this.PreviousToolbutton.enabled := false
         this.NextToolbutton.enabled     := false
         this.LastToolbutton.enabled     := false
         this.CutToolbutton.enabled      := false
         this.CopyToolbutton.enabled     := false
         this.PasteToolbutton.enabled    := false
         this.EditToolButton.enabled     := false
         this.AppendToolButton.enabled   := false
         this.DeleteToolButton.enabled   := false
         this.SaveToolButton.enabled     := false
         this.AbandonToolButton.enabled  := false
      endif
   return

Note that there is a copy of MyToolbar.CC that comes with this HOW TO document, and can be used "as is" in your applications.


Things to Note

dB2K Toolbar
If you have never done this before, and you are working with an MDI application, you may be a bit annoyed to note that your toolbar appears above the one that is normally in dB2K. For your application this would be ugly.

Don't despair, there is a simple solution -- the application is an object in dB2K, with a name of "_app", and it has properties. One of those properties is: "speedbar", which has a logical value of true or false. It defaults to true. To turn the VdBASE speedbar off, the command is:

   _app.speedbar := false

Don't forget to turn it back on when your application is done.

Detaching Toolbars
If, when you close your form, you do not detach your toolbar, you may get some interesting results. The toolbar will be replicated (you will have two or more of them ...). The simple solution is, when you close your form, in the canClose or onClose event, call the toolbar's detach method:

   t.detach( this )

The problem with this particular assumption, however, is that the reference "t" is available to you at this time. You should consider perhaps creating a reference to the toolbar in your form, when you instantiate the form:

   f = new form()
   f.toolbar = new MyToolBar()
   f.toolbar.attach( f )
   // etc.

This will allow you to detach the toolbar easily enough, and even better, if you release the form, the toolbar reference is gone completely.

In addition, it gives you an easy method of accessing the toolbar itself during the form's execution, in case, for whatever reason, you wished to modify the toolbar, the reference to the toolbar is there, and you can modify properties of individual toolButtons by just adding their reference to "f.toolbar".

Sharing ToolBars in an MDI Application
Another thing you may wish to consider is to have multiple forms (in an MDI application) sharing the same toolbar.

This turns out to be fairly simple. Rather than instantiating the toolbar as an object/property of a specific form, you can attach it to the application framework:

   _app.ToolBar = new MyToolBar()

And when you want to attach it to a form that you are about to open:

   fMyForm1 = new MyFormForm()
   _app.ToolBar.attach( fMyForm1 )
   fMyForm1.open()

If you want to share this toolbar with another form, when you open that form:

   fMyForm2 = new MyFormForm2() // whatever ...
   _app.ToolBar.attach( fMyForm2 )
   fMyForm1.Open()

One thing to remember is when the form gets closed, you should detach the toolbar, or you may run into problems (as noted elsewhere in this document, with the same toolbar appearing multiple times ...).

   _app.ToolBar.detach( fMyForm2 )

The user who suggested this (Todd Kreuter, on the dBASE newsgroups) also suggested if you want to use multiple toolbars, that you create an array to hold the object references for the toolbars, and then attach the toolbars you wish to the individual forms:

   _app.Toolbars = new Array()
   _app.ToolBars[1] = new ToolBarClip()
   _app.ToolBars[2] = new ToolBarEdit()
   // etc.
   fMyForm1 = new MyFormForm()
   _app.ToolBars[1].attach( fMyForm1 )
   _app.ToolBars[2].attach( fMyForm1 )
   // etc.
   // Do whatever you need to, and when the
   // form closes:
   _app.ToolBars[1].detach( fMyForm1 )
   _app.ToolBars[2].detach( fMyForm1 )
   // etc.

If you always want the toolbar to appear on the application frame window, you could attach it to framewin, rather than to a form:

   _app.ToolBars[1].attach( _app.FrameWin )

The problem with doing this, is that your code will need to be more complex. After some experimentation, you cannot do the obvious -- attach a toolbar to the framewin and a form (if you try it, you will get a dB2K error informing you that you cannot do this).

As noted, your code in a case like the above would need to be modified. The code attached to the toolbar and the toolbuttons will not have any idea what form to use. What this would mean is you would have to modify the code to look at an object reference for a "current" form, using a custom property attached to the toolbar, such as "currentForm":

   _app.ToolBars[1].CurrentForm = fMyForm1

This means that you would need to be setting the currentForm property each time you open and close a form, using the form's onGotFocus and when the form loses focus (including when it gets closed) you would need to set the property to NULL.

Don't forget when done to detach the toolbar(s) and so on ...

As you can see, this does make the issue more complex ... but it is doable. (There are examples of this in the two attached files: MYTOOLBAR2.CC (which I wrote and tested) and APP.PRG (which was written by Gary White to show a quick'n' dirty version of this in action).

SDI Forms
As noted elsewhere in this document, your toolbar will appear at the top of the form, shifting everything on the form down to make room.

While it is handy that this is done automatically for you, the one problem that happens is that while the controls are moved down, the form is not resized to make room for the toolbar -- which means that if you have controls at the bottom of the form, they are not necessarily visible anymore.

One solution is to always leave room at the bottom of the form for this to happen -- the question is how to make sure you leave enough room. You don't see the toolbar on the form until you run it, so it's difficult to know exactly how much room to reserve. This becomes a bit of trial and error ... I suggest you create a pushbutton which has the visible property set to false -- it's only purpose is to sit there and reserve room. Place it at the bottom of the form. Size it so that it takes close to the same amount of room as the toolbar, and then tinker a bit. Run the form with the toolbar attached, and see if you are close ... set the text property of the pushbutton to something that will remind you what it's there for ...

One other problem I've noted is that toolbars do not have a bottom line when placed onto a form. This looks a bit unfinished to me. The simple solution is to use a rectangle (since lines do not allow you to set the appearance like rectangles) and set the properties as follows:

      left = 0
      width = form.width
      top = 0
      height = 0.1
      text = ""

The default border style is "etched-in" which will match the line at the top of the toolbar. The rectangle will be moved down with all the other controls when the toolbar is attached and will appear at the bottom of the toolbar. Of course, if you allow the form to be resized, you'll need to write the code to resize the width of the rectangle in the form's onSize event.

Modal Forms
If your SDI application (Single Document Interface) uses Modal Forms (this is how VESPER at the author's website was done), there is one major drawback as noted above -- the onUpdate event does not fire until the form closes, which is not exactly useful.

One solution, which is a lot of extra work, is to create your own methods attached to the methods of the toolbar, that enable/disable the buttons as needed, based on the rowset's state, or based on the results of atFirst(), and so on. When the form opens, you could set some code that checks to see if you are at the first row, and disable the appropriate buttons, etc. Seems like a lot of work, and the author is going to leave that to the reader to figure out, if the reader is so inclined.

Another solution is to create your own toolbar using the speedbutton property of the pushbutton control, and maybe a container. This allows you to do the same sort of thing, but you don't have to rely on the toolbar's onUpdate. (The only problem with using the pushbuttons is that they do not give the option to use the "flat" toolbar appearance that you get with the toolbar and toolbutton classes ... maybe in a future release of dB2K?)

Finally, the last solution is to use the toolbar custom controls in the dUFLP library in the Knowledgebase, which is what the author did for Vesper and continues to use in other applications.


Floating Palettes versus Attached to Form

One user on the newsgroup noticed that even with the floating property of the toolbar set to false, the toolbar was able to be removed from the form and turned into a floating palette.

This is actually "as designed" -- I believe it is a Windows specification, although I can't guarantee it ...

If you really want your toolbar to stay attached to a form, you can force it by adding the following code to the onUpdate event of the toolbar:

   if this.floating
      this.floating := false
   endif

Which really just states that if the floating property has been changed (which it will do if the user attempts to drag it off the form), reset the floating property to false, which will snap the toolbar back to the form. (Code provided by Bowen Moursund in the dBASE newsgroups.)


Summary

The toolbar class is kind of spiffy. It takes some work to use properly, but can add a nice bit of functionality and appearance to your applications ...

Thanks go to Gary White for helping edit this and make it more clear to the reader, as well as helping find where some of the difficulties lie (especially the ones I stumbled across while testing code, but wasn't sure how to reproduce).


DISCLAIMER: the author is an employee of dBASE, Inc., but has written this on his own time. If you have questions regarding this .HOW document, or about dB2K you can communicate directly with the author and dBVIPS in the appropriate newsgroups on the internet.

.HOW files are created as a free service by members of dBVIPS and dBASE, Inc. employees to help users learn to use dB2K more effectively. They are edited by both dBVIPS members and dBASE, Inc. Technical Support (to ensure quality). This .HOW file MAY NOT BE POSTED ELSEWHERE without the explicit permission of the author, who retains all rights to the document.

Copyright 2001, Kenneth J. Mayer. All rights reserved.

Information about dBASE, Inc. can be found at:

       http://www.dbase.com
    

EoHT: TOOLBAR.HTM -- January 31, 2001 -- KJM