Custom Controls in a Box (- lbjoseph lbjoseph)

Making your own custom control with a graphicbox in Liberty BASIC.


Throughout the ages, mankind has sought after customization and personalization. Now, with the power of Liberty BASIC 4, you can create your very own custom control inside of a simple graphicbox. The following article expects you to be familiar with the Liberty BASIC language, as well as with Liberty BASIC's native drawing commands.

In this article, we will discuss the techniques that I (- lbjoseph lbjoseph) think are necessary to creating a good custom control.


Why a custom control?

Because you can. But there's more to it than that - by creating your own custom control, it's look and feel will stay consistent throughout all the various versions of windows. Secondly, you can make them to where it is a million times easier to use than the windows API controls. If you are well rounded in LB, you can make your control to where it doesn't require globalized "constants" at the start of the code, and doesn't require a huge wrapper of subs and functions to deal with the windows API calls, callbacks, messages, and so on.

Part the First: The container

First of all, we need some sort of object that we can manipulate to turn into our own object. The Liberty BASIC graphicbox is just the thing. Not only does it let you draw on it with LB's native drawing commands, but it lets you set event handlers to branch labels or subs when it is clicked, etc. This allows you to make a click-able control that looks beautiful to the eyes (some sarcasm implied).

Part the Second: The foundation of what we want

In this article we'll walk through custom control creation by creating a "fake" hyperlink control. There's a couple of things we want specifically from this control:

  1. It needs to have a roll-over effect (becomes underlined upon mouse-over)
  2. We want to be able to create as many as we like

So, let's go ahead and create the basis of our program. We'll need a window with a graphicbox in place and a trapclose event setup so our program will actually close when someone hits the little red x.

If we wanted to have more than one hyperlink, we'd make a graphicbox for every link we want. More on that later.

'Make a custom control hyperlink with a Liberty BASIC graphicbox.
'-----------------------------------------------------------------'
 
    [SetupWindow]
        NoMainWin
        WindowWidth = 400
        WindowHeight = 200
        UpperLeftX = Int((DisplayWidth-WindowWidth)/2)
        UpperLeftY = Int((DisplayHeight-WindowHeight)/2)
 
        Link1.Width = 180
        Link1.Height = 28
 
        GraphicBox #Win.Link1, 4, 4, Link.Width+20, Link.Height+10
        Stylebits #Win.Link1, 0, _WS_BORDER, 0, 0
 
        Open "Hyperlink Custom Control" For Window As #Win
 
        #Win, "TrapClose [Quit.Win]"
 
    Wait
 
    [Quit.Win]
        Close #Win
    End
 
    [Link1.Click]
    Wait
 
If you run the code above, things don't look too promising. However, this is just the foundation of our program.

There's a couple of things you need to note about the above code:

First, notice in the "GraphicBox" command that we added 20 to the width, and added 10 to the height. I'll explain why we do this a little later - for now, you just need to notice that we did that.

Secondly, we issued a "Stylebits" Command - we used the flag _WS_BORDER in the removeBits section. This removes the border of the graphicbox. This command is necessary to remove clutter which could mess up the way our control looks (especially in a dialog window).

Now we have the foundation of a custom control - a graphicbox at the right size without a border.

Part the third - Data management

Here's where the fun begins. We know we need several parameters to create a hyperlink:

  1. We need the handle to the "parent" graphicbox container. We'll call this variable "gbHndl$". In our case, the programmer would pass the value "#Win.Link1" into the function.
  2. We need the text for the hyperlink - we'll call this "text$"
  3. We need to know what font to use - we'll call this "font$"
  4. We need to know the background color of the window the link is on (so it won't look ugly) - we'll call this "backcolor$"
  5. We need to know what color to make the hyperlink -we'll call this "linkcolor$"
  6. We need the name of the sub or branch label that contains the user's code to execute upon clicking the link. We'll call this "eventClick$". In our case, the programmer would pass the value "[Link1.Click]" into the function.
  7. We'll need the name of the sub or branch label that contains the user's code to execute when the mouse moves over the link. We'll call this eventMove$

We also know that each hyperlink needs to store the above parameters so it can be rendered. The hyperlink also needs to know what state it is in - whether or not is "active". We'll call this hlinkActive. We'll keep it set to 0 when the mouse isn't over the hyperlink, and we'll set it to 1 when the mouse is over the hyperlink. That way, we'll only need to re-render the hyperlink when the value of hlinkActive changes. This reduces flickering in the graphicbox.

Part the fourth - The guts of our hyperlink program

Here's the updated program (heavily commented) with the sub and functions required for the hyperlink:

    [SetupWindow]
        NoMainWin
        WindowWidth = 400
        WindowHeight = 200
        UpperLeftX = Int((DisplayWidth-WindowWidth)/2)
        UpperLeftY = Int((DisplayHeight-WindowHeight)/2)
 
        Link1.Width = 180
        Link1.Height = 28
 
        GraphicBox #Win.Link1, 4, 4, Link1.Width+20, Link1.Height+10
        Stylebits #Win.Link1, 0, _WS_BORDER, 0, 0
 
        Open "Hyperlink Custom Control" For Window As #Win
 
        #Win, "TrapClose [Quit.Win]"
 
        Link1$ = cCc.Hyperlink$("#Win.Link1", "Link 1 Text", "Arial 10", "buttonface", "blue", "[Link1.Click]", "[Link1.Move]")
    Wait
 
    [Quit.Win]
        Close #Win
    End
 
    [Link1.Click]
        Notice "You clicked Link 1."
    Wait
 
    [Link1.Move]
        Call cC.RollHyperlink Link1$, MouseX, MouseY
    Wait
 
 
    Function cCc.Hyperlink$(gbHndl$, text$, font$, backcolor$, linkcolor$, eventClick$, eventMove$)
 
        #gbHndl$, "CLS; Down; Fill ";backcolor$ 'Clear the graphicbox and fill it with the specified backcolor.
 
        'We need the height of the current font in pixels. Here's an easy way:
            #gbHndl$, "Place -100 -100 " 'Place pen offscreen
            #gbHndl$, "| " 'Print a blank line.
            #gbHndl$, "PosXY penX penY" 'Get the coordinates of the pen. After drawing text, the pen moves down the proper
                'height to make room for another line. Just subtract the end position from the start position to get the height
                'of the font:
            fontHeight = penY-(-100) 'And it works, if you print the results out!
        '--------------------------------------------------------------------'
 
        #gbHndl$, "CLS; Down; Fill ";backcolor$;"; Flush" 'Redo the intial thing just in case and this time flush it
            'so the graphics will stick.
        #gbHndl$, "Font ";font$;" ; Color ";linkcolor$;" ; BackColor ";backcolor$ 'Set the font, foreground and background color.
 
        #gbHndl$, "Place 1 "; fontHeight 'Set the pen at the proper location so the text will draw correctly in the graphicbox.
            'Notice the text will start at the far left of the gbox, just like statictext would.
 
        #gbHndl$, "|"; text$ 'Draw the text!
 
        #gbHndl$, "Flush DefaultText" 'Make this drawing stick. And, give this drawing (segment) the name DefaultText .
 
        #gbHndl$, "when leftButtonUp "; eventClick$ 'Set the graphicbox to jump to the branch label/sub that the user specified
            'when the link/graphicbox is clicked.
        #gbHndl$, "when mouseMove "; eventMove$
 
        'Now, return the handle to a hyperlink so the user can pass it to the hyperlink functions:
 
        hlinkActive = 0 'The hyperlink isn't active just yet.
 
        'Return all the info as one big string of data seperated by spaces. We'll parse this using word$() in another
        'function. This allows the user to have more or less a "handle" to a link.
 
        font$ = Sys.ReplaceChar$(font$," ","ø") 'Replace any spaces in the font text with a weird symbol, so we can parse the
            'handle with word$() in another function.
 
        text$ = Sys.ReplaceChar$(text$," ","ø") 'Do the same with the text of the hyperlink. We'll reverse this in the other
            'function using Sys.ReplaceChar$(text$,"_"," ") which will replace the underscores with spaces - so we'll be back
            'to normal. Same with the font$.
 
        cCc.Hyperlink$=gbHndl$;" ";hlinkActive;" ";font$;" ";backcolor$;" ";linkcolor$;" ";eventClick$;" ";eventMove$;" ";text$;" ";fontHeight
 
        'Word 1 = handle to the graphicbox.
        'Word 2 = 0/1 - whether or not the link is active (mouse over)
        'Word 3 = the font used
        'Word 4 = the backcolor
        'Word 5 = the link color
        'Word 6 = the event in which to trigger when the link is clicked.
        'Word 7 = the event in which to trigger when the mouse is moved over the gbox.
        'Word 8 = the text for the hyperlink
        'Word 9 = the height of the font
 
    End Function
 
    Sub cC.RollHyperlink byref Link$, X, Y
        'Extract information from the hyperlink's handle:
        gbHndl$ = Word$(Link$,1) : hlinkActive = Val(Word$(Link$,2)) : font$ = Word$(Link$,3) : backcolor$ = Word$(Link$,4) : linkcolor$ = Word$(Link$,5) : eventClick$ = Word$(Link$,6) : eventMove$ = Word$(Link$,7) : text$ = Word$(Link$,8) : fontHeight = Val(Word$(Link$,9))
 
        #gbHndl$, "Font ";font$;" ; Color ";linkcolor$;" ; BackColor ";backcolor$ 'Make sure the colors and fonts are set right...
 
        'Now, replace the weird symbol in the font$ and text$ with spaces:
        font$ = Sys.ReplaceChar$(font$,"ø"," ")
        text$ = Sys.ReplaceChar$(text$,"ø"," ")
 
        'Check to see if the mouse coordinates (X, Y) are over the link's text - if so, we'll make sure the hyperlink becomes underlined:
 
        'First, let's get the width of the hyperlink's text using the stringwidth? command:
        #gbHndl$, "StringWidth? text$ textWidth"
        textWidth = textWidth + 1
            'Now, we know both the width and height of the text. they are stored in textWidth and fontHeight . Perfect!
 
        'Now, check to see if the mouse is over the actual area the text is drawn in:
            If X<=(textWidth+1) And Y<=fontHeight Then 'If the mouse is in the proper area:
                If Not(hlinkActive) Then
                    'If the link has not been drawn in it's mouseOver (active) state, then let's do so now. If hlinkActive was true (set to 1),
                    'it means we had already drawn it in it's active state, so we wouldn't need to redraw the same thing. That just causes flickering.
                    #gbHndl$, "DELSEGMENT DefaultText" 'Delete the inactive (normal) drawing of the text.
                    #gbHndl$, "REDRAW" 'Update the graphicbox to reflect our deletion.
                    #gbHndl$, "Place 1 ";fontHeight 'Position the pen to redraw the text.
                    #gbHndl$, "Font ";font$;" underscore" 'Set the font to be underlined! Of course, if the user already has an underlined font, this is a bummer. :(
                    #gbHndl$, "|";text$ 'Draw the new, underlined text!
                    #gbHndl$, "Flush ActiveText" 'Make this drawing stick, and call it ActiveText - we can remove this drawing and redraw the old one when the mouse isn't
                        'over the graphicbox text area. Cool, huh?
                    hlinkActive = 1 'The hyperlink is now in it's active state!
                End If
            Else 'If the mouse is NOT over the actual text area, then let's make sure that the hyperlink is drawn in it's inactive state.
                If hlinkActive Then 'If the hyperlink is being drawn in it's active state, then let's unactivate it!
                    #gbHndl$, "DELSEGMENT ActiveText" 'Remove the drawing of the active text.
                    'Draw the text in it's normal state:
                    #gbHndl$, "REDRAW"
                    #gbHndl$, "Font ";font$
                    #gbHndl$, "Place 1 ";fontHeight
                    #gbHndl$, "|";text$
                    #gbHndl$, "Flush DefaultText" 'Make it stick and call this drawing the DefaultText drawing.
                    hlinkActive = 0 'hlinkActive is now inactive.
                End If
            End If
        '----------------------------------------------------------------------------'
 
        'Replace spaces with underscores...
        font$ = Sys.ReplaceChar$(font$," ","ø")
        text$ = Sys.ReplaceChar$(text$," ","ø")
 
        'Change the properties of the hyperlink's handle to match the updated properties.
        'This will actually affect the user's handle, because we passed Link$ by reference. (byref in the help file)
        Link$=gbHndl$;" ";hlinkActive;" ";font$;" ";backcolor$;" ";linkcolor$;" ";eventClick$;" ";eventMove$;" ";text$;" ";fontHeight
 
    End Sub
 
    'This is a helper function for the hyperlink functions.
    Function Sys.ReplaceChar$(String$, FindChar$, ReplaceChar$) 'Find the character FindChar$ and replace it with ReplaceChar$
        For i = 1 To Len(String$)
            char$=Mid$(String$,i,1)
            If char$=FindChar$ Then char$=ReplaceChar$
            Sys.ReplaceChar$=Sys.ReplaceChar$;char$
        Next i
    End Function
 

Part the fifth - What does it all mean?

If you would like this to make sense, you should probably look over the code and read the comments. It's designed to be easy to understand. That way, you'll be able to keep up with this section.

The function cCc.Hyperlink$ (the cCc stands for create custom control) is called after the window has been setup like this:

Link1$ = cCc.Hyperlink$("#Win.Link1", "Link 1 Text", "Arial 10", "buttonface", "blue", "[Link1.Click]", "[Link1.Move]")

The arguments passed into this function correspond with the paremeters (representational variables) received by the function cCc.Hyperlink$():

Link1$ = cCc.Hyperlink$("#Win.Link1", "Link 1 Text", "Arial 10", "buttonface", "blue",  "[Link1.Click]", "[Link1.Move]")
Function cCc.Hyperlink$(  gbHndl$,        text$,       font$,     backcolor$, linkcolor$, eventClick$,    eventMove$)

The function returns a string of values put together. Well, when we called this function, we assigned the variable Link1$ to be that string.

Earlier, we mentioned that we need an event for when the mouse moves over the link. If you compare the two above, you'll notice that we specified the branch label [Link1.Move] to be the block of code that Liberty jumps to when the mouse moves over the graphicbox #Win.Link1. Let's take a look at that block:

    [Link1.Move]
        Call cC.RollHyperlink Link1$, MouseX, MouseY
    Wait
 
Notice that we called the sub in our program. This sub, cC.RollHyperlink (cc stands for Custom Control), checks to see if the mouse is directly over the rendered text (using the graphics commands, and a little bit of math and conditional checking). The sub then draws the hyperlink either in it's active (underlined) form or inactive (not underlined) form depending on if the mouse is over it or not.