FLAT TOOLBAR WITH TOOLTIPS FOR THE OPEN SOURCE EDITOR
- upper intermediate level
- editor code is open source, public domain
- this article copyright 2002, Alyce Watson

Home

Liberty BASIC News
Safe Registry and Ini File Alternative
Deleting and Renaming Disk Files
Segments and Flushing
Flat Toolbar with Toolips
Translating 32-bit VB API Calls
Event-Driven Programming Concepts
Spotlight on the Community!
ODBC in Liberty BASIC
Hex Viewer
Listing Files Recursively
Registering Hot Keys
Preventing more than 1 instance
Multi-Coloured Text Input Boxes
Images on Buttons and Statictext
Two Demos by David Conner

Before we begin this tutorial, please know that creating a toolbar with tooltips is a fairly complex process. It is possible to do it with great ease with a DLL made especially for the purpose by Dennis McKinney. Get it at the Liberty Belle: http://libertybelle.0catch.com


We can add a real Windows toolbar to our open source Liberty BASIC Editor! This type of toolbar has flat buttons that appear to raise up when the mouse hovers over them. We can also add tooltips to the toolbar buttons. Toolbars and tooltips are part of comctrl32.dll. Here is how we do it, step-by-step:


STEP ONE - create hidden buttons
We need to create some genuine Liberty BASIC buttons so that we have access to the button event handlers, which are the branch labels. We'll hide these buttons by giving them a position offscreen and a width and height of zero. We will never see these buttons in the Open Source Editor, but they are essential to the operation of the toolbar.

 
    Button #1.hide0, "", [new], UL, -400, -400, 0, 0 
    Button #1.hide1, "", [open], UL, -400, -400, 0, 0 
    Button #1.hide2, "", [save], UL, -400, -400, 0, 0 
    Button #1.hide3, "", [saveas], UL, -400, -400, 0, 0 
    Button #1.hide4, "", [print], UL, -400, -400, 0, 0 
    Button #1.hide5, "", [run], UL, -400, -400, 0, 0 
    Button #1.hide6, "", [debug], UL, -400, -400, 0, 0 
    Button #1.hide7, "", [maketkn], UL, -400, -400, 0, 0 
    Button #1.hide8, "", [paint], UL, -400, -400, 0, 0 
    Button #1.hide9, "", [winfile], UL, -400, -400, 0, 0 
    Button #1.hide10, "", [notepad], UL, -400, -400, 0, 0 
    Button #1.hide11, "", [calculator], UL, -400, -400, 0, 0 
    Button #1.hide12, "", [help], UL, -400, -400, 0, 0 
    Button #1.hide13, "", [quit], UL, -400, -400, 0, 0

STEP TWO - bitmap for the toolbar
We need a bitmap for our toolbar and it must be created properly. This is not difficult. For best results, make this bitmap a 16-color bitmap. After creating the bitmap in your favorite Paint program, be sure to "save as" and choose the 16-color format. You may instead choose the 256-color format, but that is the maximum number of colors to use if the toolbar is to appear correctly.

Make the toolbar bitmap 16 pixels tall. IMPORTANT: fill the background with the traditional, default, Windows lightgray color! This color will appear transparent on the toolbar, so that the button images appear directly on the toolbar, rather than in rectangular boxes. This transparency is the reason we save the bitmap as a 16-color bitmap.

Each button image should be 16 pixels wide and 16 pixels high. Do not leave any space between the button images! If you have two buttons on the toolbar, your toolbar bitmap will be 16 pixels high and 32 pixels wide. If you have four buttons, then it will be 64 pixels wide, and so on.

'do not attempt to create toolbar if bmp is missing
if FileExist(DefaultDir$,"opensrc.bmp")=0 then wait

We'll need a handle to our toolbar bitmap, which we will pass as an argument to the function that creates the toolbar. We could load the bitmap with Liberty BASIC's LOADBMP command, but we will instead load it with an API call - LoadImageA. This function asks for a width and height to give the bitmap, and it will be loaded at those dimensions. If we use 0 for these arguments, the bitmap will be loaded at its own default dimensions. The function returns a handle to the loaded bitmap. We need to be careful when specifying the load flags. We must use the _LR_LOADFROMFILE flag to tell the function to look for a file on disk. It is also really important to use the _LR_LOADMAP3DCOLORS flag. When this flag is used, the function searches the color table for the image and replaces the following shades of gray with the corresponding 3D color:

Color Replaced with
Dk Gray, RGB(128,128,128) COLOR_3DSHADOW
Gray, RGB(192,192,192) COLOR_3DFACE
Lt Gray, RGB(223,223,223) COLOR_3DLIGHT

This means that the bitmap is loaded to match the system default colors. If a user has an alternate color scheme than the standard Windows desktop colors, and the _LR_LOADMAP3DCOLORS flag is not used, then the images for the buttons will probably appear within their lightgray background boxes. If the flag is used, then the images for the buttons will match the colors in force on the system, giving a very professional look. See issue #100 for more information on bitmap color tables.

The flag for _LR_LOADTRANSPARENT is also used. This flag causes the LoadImageA function to retrieve the color value of the first pixel in the image and replace the corresponding entry in the color table with the default window color (COLOR_WINDOW). All pixels in the image that use that entry become the default window color. This value applies only to images that have corresponding color tables. If the flag includes both the LR_LOADTRANSPARENT and LR_LOADMAP3DCOLORS values, LR_LOADTRANSPARENT takes precedence. However, the color table entry is replaced with COLOR_3DFACE rather than COLOR_WINDOW.

Here is the LoadImageA function:

 flags=_LR_LOADFROMFILE or _LR_LOADMAP3DCOLORS _
    or _LR_LOADTRANSPARENT

CallDLL #user32, "LoadImageA",_ 0 As long,_ 'instance handle, can be null bmp$ As ptr,_ 'file name of bitmap _IMAGE_BITMAP As ulong,_ 'type of image to load 0 As long,_ 'desired width, 0=default 0 As long,_ 'desired height, 0=default flags As long,_ 'load flags hbmpTools As ulong 'handle to loaded bitmap

STEP THREE - define constants and structs
Liberty BASIC recognizes many Windows constants and will subsitute their values when they are typed with an underscore. The Windows constant for SRCCOPY is recognized by Liberty BASIC when it is written like this: _SRCCOPY . There are some Windows constants that are not recognized by Liberty BASIC, and trying to access them will cause the compiler to halt with an "undefined Windows constant" error. If this happens, then we must use the actual value of the constant. Here are some values we must define in order to use a toolbar:

 TB.ADDBUTTONS = 1044 
    TB.SETTOOLTIPS = 1060 
    TBSTYLE.BUTN = 0 
    TBSTYLE.FLAT = 2048 
    TBSTYLE.TOOLTIPS = 256

We will also need a struct that will hold information about the toolbar buttons:

 struct TBBUTTON,_ 
    bmpID As long,_ 'index of bitmap 
    cID As long,_ 'command ID 
    State As long,_ 'button state 
    Style As long,_ 'button style 
    dwData As long,_'not used 
    Str As long 'not used

STEP FOUR - get IDs for the hidden buttons
Remember those hidden Liberty BASIC buttons? We use them now. We make a call to GetWindowLongA with the flag _GWL_ID to get the Windows ID for each of these buttons. Here are the first two. The GetWindowLongA function returns the ID of the button. We'll store each ID in a separate variable for use later.

 hbutton=hwnd(#1.hide0) 
    CallDLL #user32, "GetWindowLongA",_ 
    hbutton As long,_ 
    type As long,_ 
    hID0 As long

hbutton=hwnd(#1.hide1) CallDLL #user32, "GetWindowLongA",_ hbutton As long,_ type As long,_ hID1 As long

STEP FIVE - create the toolbar control
The first thing we must do is fill the TBBUTTON struct. The members are filled as indicated below. The first toolbar button is initialized when the toolbar is created. All other buttons are added later:

 TBBUTTON.bmpID.struct = 0 'index of first bitmap 
    TBBUTTON.cID.struct = hID0 'ID of first button 
    TBBUTTON.State.struct = 4 'enabled 
    TBBUTTON.Style.struct = TBSTYLE.BUTN 'style 
    TBBUTTON.dwData.struct = 0 'not used 
    TBBUTTON.Str.struct = 0 'not used

Notice the TBBUTTON.cID.struct is given the ID we retrieved for our first hidden Liberty BASIC button. Now, when a user clicks the first toolbar button, our Liberty BASIC branch label event handler for that button will be activated!

We'll set up the style for the toolbar. It will be a child window of our LB window, and it will be visible. We also want the FLAT style of buttons, rather than the raised style, and we'd like our toolbar to have a border. Notice that we are using the TBS.TOOLTIPS style. This allows us to add tooltips to our buttons, for a really professional appearance. Here is the CreateToolbarEx function. Each argument is explained. The function returns the handle to the toolbar.

 style=_WS_CHILD Or _WS_VISIBLE Or TBSTYLE.WRAPABLE Or TBSTYLE.FLAT _ 
    Or _WS_BORDER or TBSTYLE.TOOLTIPS 
    uStructSize = Len(TBBUTTON.struct) 
    CallDLL #comctl32, "CreateToolbarEx",_ 
    hMain As long,_ 'parent handle 
    style As long,_ 'window style flags 
    0 As long,_ 'ID 
    14 As long,_ 'number of Bitmaps 
    0 As long,_ 'hBMInst-not used 
    hbmpTools As long,_ 'bitmap handle 
    TBBUTTON As struct,_'toolbar button struct 
    1 As long,_ 'number of buttons to start 
    16 As long,_ 'width buttons 
    16 As long,_ 'height buttons 
    16 As long,_ 'width bitmaps for buttons 
    16 As long,_ 'height bitmaps for buttons 
    uStructSize As long,_ 
    hTB As long 'handle to toolbar


STEP SIX - create the toolip control
If we want the toolbar buttons to display tooltips, then we must create the tooltip control before adding any buttons to the toolbar. We explained tooltips pretty thoroughly when we first added them to the Open Source Editor in issue #60, so please refer to that issue for more details. First, we set up some values that will be needed in creating the tooltip control:

 
    TTF.IDISHWND = 1 
    TTF.CENTERTIP = 2 
    TTF.SUBCLASS = 16 
    TTM.ADDTOOL = 1028 
    TTM.DELTOOL = 1029 
    TTS.ALWAYSTIP = 1 
    TTS.NOPREFIX = 2 
    style = _WS_POPUP or TTS.NOPREFIX _ 
    or TTS.ALWAYSTIP

Next, we retrieve the instance handle of the parent window:

 calldll #user32, "GetWindowLongA", _ 
    h as long, _GWL_HINSTANCE as long,_ 
    hInstance as long

And now we create the tooltip control. It is not a visible control, like a button or textbox, but rather a control that manages the tooltips for us in the background. It knows when the mouse is hovering over a button or other control, and it displays the tooltip for a few seconds, then hides it again. The CreateWindowExA must create a window os "TOOLTIPS_CLASS32" and it returns the handle to the tooltip control.

 calldll #user32, "CreateWindowExA",_ 
    _WS_EX_TOPMOST as long,_ 
    "TOOLTIPS_CLASS32" as ptr,_ 
    "" as ptr, style as long,_ 
    _CW_USEDEFAULT as long,_CW_USEDEFAULT as long,_ 
    _CW_USEDEFAULT as long,_CW_USEDEFAULT as long,_ 
    h as long, 0 as long, hInstance as long,_ 
    0 as long,hwndTT as long

STEP SEVEN - add tooltips to the tooltip control
We need a struct to hold the tooltip information. Here it is:

 struct toolinfo, cbSize as long, uFlags as long,_ 
    hwnd as long, uId as long, left as long, top as long,_ 
    right as long, bottom as long, _ 
    hInst as long, lpstrText$ as ptr

We need to fill members of the struct before we add each tooltip. Some things remain the same for each tooltip, so we fill those struct members only once. A change form the previous method of doing tooltips for the Open Source Editor - instead of using the window handle, we fill that struct member with the toolbar handle. We also remove the flag that indicated that the ID for the tooltip is the handle of the control to which it is assigned. Instead of a control ID, we'll use the pixel coordinates of each button to tell the tooltip where it belongs. We need to fill the "top" and "bottom" coordinate members only once, since all buttons are in a horizontal line and share these values:

 'THESE STRUCT MEMBERS ONLY NEED TO BE FILLED ONCE: 
    toolinfo.cbSize.struct = len(toolinfo.struct) 
    toolinfo.uFlags.struct = TTF.SUBCLASS 
    toolinfo.hwnd.struct = hTB 
    toolinfo.top.struct=0 'top location on toolbar 
    toolinfo.bottom.struct=22 'bottom location on toolbar

We need to fill some members of the struct differently for each tooltip. Each will have a different caption, and a different left and right location on the toolbar. Each left location is 23 pixels greater than the one preceding it, since that is the "finished" width of the buttons. The right location is 22 pixels greater than the left location for each tooltip/toolbar button. Once we've filled the struct as desired, we send a message to the toolbar with SendMessageA that adds the tooltip.

 'THESE STRUCT MEMBERS NEED TO BE FILLED ANEW 
    'FOR EACH TOOTLIP ADDED: 
    toolinfo.lpstrText$.struct = "New File" 
    toolinfo.left.struct=0 'initial left location 
    toolinfo.right.struct=22 'add 22 to left for right 
    calldll #user32, "SendMessageA", hwndTT as long,_ 
    TTM.ADDTOOL as long, 0 as long,_ 
    toolinfo as struct, re as long

toolinfo.lpstrText$.struct = "Open File" toolinfo.left.struct=23 'add 23 to previous left location toolinfo.right.struct=45 'add 22 to left for right calldll #user32, "SendMessageA", hwndTT as long,_ TTM.ADDTOOL as long, 0 as long,_ toolinfo as struct, re as long (And so on for all tooltips desired.)

STEP EIGHT - add the tooltip control to the toolbar
Remember, we must add the tooltip control before adding buttons to toolbar. After the tooltip control is created, we added by sending the toolbar a message.

 CallDLL #user32, "SendMessageA",_ 
    hTB As long,_ 'toolbar handle 
    TB.SETTOOLTIPS As long,_ 'message to set tooltips 
    hwndTT As long,_ 'handle of our tooltip control 
    0 As long,_ 'not used 
    result As long

STEP NINE - add buttons to the toolbar
We're almost done! Just as we had to fill the tooltip struct before adding each tooltip to the control, so must we also fill the toolbar button struct before adding each toolbar button. It requires the image index for each button. This is a zero-based index. Our first bitmap image was at index 0, and that was set when we created the toolbar. The next image is at index 1, and so on. We also need to fill the struct member that contains the ID of the hidden Liberty BASIC button that is associated with each toolbar button.

 'fill toolbar button struct with new info to add buttons: 
    TBBUTTON.bmpID.struct = 1 'bitmap image index 
    TBBUTTON.cID.struct = hID1 'ID of button

CallDLL #user32, "SendMessageA",_ hTB As long,_ 'toolbar handle TB.ADDBUTTONS As long,_ 'message to add button 1 As long,_ 'number of buttons to add TBBUTTON As struct,_ 'toolbar button struct r As long

TBBUTTON.bmpID.struct = 2 'bitmap image index TBBUTTON.cID.struct = hID2 'ID of button

CallDLL #user32, "SendMessageA",_ hTB As long,_ 'toolbar handle TB.ADDBUTTONS As long,_ 'message to add button 1 As long,_ 'number of buttons to add TBBUTTON As struct,_ 'toolbar button struct r As long

(And so on for all of the desired buttons.)

Please see the Open Source Editor version 32_04 that is included in the zip archive attached to this newsletter.

WRAPABLE TOOLBARS
The toolbar we created for our editor will not wrap when the window is made narrower than the toolbar. A wrapable toolbar will add rows and move some buttons down so that all buttons are visible when the window width becomes too narrow to display all buttons in a single row. To make a wrapable toolbar, add the following style bit to the style flag used to create the toolbar:

TBSTYLE.WRAPABLE = 512

For the bar to wrap, we have to sent it a _TB_AUTOSIZE message in our resizehandler

[resize] 
    TB.AUTOSIZE = 1057

CallDLL #user32, "SendMessageA",_ hTB As word,_ 'toolbar handle TB.AUTOSIZE As word,_ 'message to resize itself 0 As long,_ '0 0 As long,_ '0 r As long

We haven't made our Open Source Editor toolbar wrapable because it would require a lot more code in the resizehandler, and the method described in this article is already fairly complex! The x, y coordinates for many of the buttons would change when the toolbar wrapped, and we'd need to determine new coordinates and make changes to our tooltips so that they would still display with their associated buttons. This isn't as important for a toolbar with a relatively small number of buttons, but it should be taken into account when creating a toolbar with many buttons.

EASIER TOOLBARS
LB Workshop contains a Toolbar Maker that allows you to select from many standard button images, or from images of your own. After selecting an image, you may also specify a branch label for the button and the tooltip text. The Toolbar Maker automatically saves your toolbar bitmap in 256-color format and creates the code for the toolbar with tooltips in a sample window, complete with stubs for all button branches.

http://iquizme.0catch.com/lb/lbw3/lbw3.html

Dennis McKinney has created an extremely versatile toolbar DLL. Here is the way it is described at the Liberty Belle:

-The most versatile toolbar dll available.
-Rewritten to take advantage of LB3 callbacks.
-No extra hidden buttons needed. Cleaner, more efficient LB code.
-Create almost any type of toolbar you need, horizontal, vertical, or wrapable.
-Can be used with floating toolwindows.
-Group different styles of buttons.
-Built-in tooltips. Use one string to create the tips for the whole toolbar.

Grab your copy here:

http://libertybelle.0catch.com/lb3code.htm


Home

Liberty BASIC News
Safe Registry and Ini File Alternative
Deleting and Renaming Disk Files
Segments and Flushing
Flat Toolbar with Toolips
Translating 32-bit VB API Calls
Event-Driven Programming Concepts
Spotlight on the Community!
ODBC in Liberty BASIC
Hex Viewer
Listing Files Recursively
Registering Hot Keys
Preventing more than 1 instance
Multi-Coloured Text Input Boxes
Images on Buttons and Statictext
Two Demos by David Conner