Made you look. 
This idea bounced into my a while back, and my idea was, "Why not wrap your controls in an object, let the object class handle all the nitty gritty details, and you just use the Object to manage what you want to do with the control?", which will work with a lot of controls you care to use. And then you can do strange things which would normally require several lines of code in your application, that boils down to calling upon a single method, or using the properties, of the object to do the same thing. Example?
From:
CONTROL SET LOC CB.HNDL, %ID_CONTROL, x, y
CONTROL SET SIZE CB.HNDL, %ID_CONTROL, xx, yy
To:
MyControlClass.Move x, y, xx, yy
Oh say, that looks familiar. Sneaky.
Other stuff can be done inside the class method which you, as the programmer, don't see, don't have to worry about, and should no longer need to be concerned about with the class providing it is well thought out ahead of time and works the way you want it to. For example, included in that move the object could also be storing the position and size of said control, so you can retrieve just say the width later on without having to:
CONTROL GET SIZE CB.HNDL, %ID_CONTROL TO x, y
Just access a property:
myVariable = MyControlClass.nWidth
Properly written, the class becomes usable for a whole range of projects, very recyclable, and easy to maintain, especially if you make them into a common INC file. It might be possible to write a single class which can handle every possible control, but that would be overkill, would be simpler to write a single class for handling a specific control, or a limited selection of controls which behave in many ways like one another. For this example, I'm tackling the statusbar control, and specifically, to make the status bar flexible in the use of the panels.
This is an incomplete work in progress, does need some finer features done, but so far it works.
First is a few TYPE's and CONST's for the class:
Added the %SBT_NORMAL because it's more informative than setting a STYLE of 0. Reading of the SDK reveals that any single part of the statusbar can only contain up to 127 characters, so 128 works just fine. The sStyle part will use the exact same styles as listed. The three types of panels I'm going to be concerned with are fixed width, spring width where the panel assumes the width needed to display the text, and a "remaining" width, which is a panel that acquires all the remaining space available in the statusbar. The way the class is set up is when it resizes the panels, the first panel of the type %SBU_REMAIN is the sole reciepent of the excess space.
The class's instance variables:
One of the things I wanted to do when designing the class was to make it work with either SDK or DDT programs. One seemingly failure here is that the class does not take into account Dialog Units versus Pixels. Doesn't seem to matter, when you start tackling the innards of the statusbar, you seem to get pixels.
Everything above should be pretty evident, the GripWidth is how wide the grip is on the statusbar, it's calculated from comparing the inner area available to the statusbar versus the outside dimensions of the statusbar when you assign a statusbar to the class's care. Divider is the borders of a panel, and FillLast is used to achieve flush to the right edge with the last panel effects.
Creating the class:
And while I put in a destroy method, it's half habit and half not knowing if I will need it or not, ...
The create basically sets up the internal variables at this point to some default values.
:rant:
PB needs a CONSTRUCTOR for it's classes, so you can pass values to the class on creation and not have to write code into the class to take variables from the program after creation. More later.
Currently I have two private class methods used in the status bar, one's fairly simple, the other darn complex. Tackle the complex one first in chunks:
First we need the current width of the statusbar, so we get the statusbar's client area, then subtract off the Sizing Grip and the divider times the number of elements. The width of each element is subtracted from the total width, and if all the elements fit, then the assignment of each part of the statusbar can be done. If the total width of the elements does not fit, we fall back to allocating a percentage of available width to each part of the status bar. This lets us fail nicely.
And that handles the most important part, (pardon the pun), of the reason for making this statusbar class, creating a flexible and programmable statusbar manager.
Each time a string is set to any part of the statusbar which is set to be either %SBU_SPRING or %SBU_REMAIN, the size of the part needs to recalculated to the length of the string.
Something I found out when designing this class is that getting the DC of the statusbar itself gets you the wrong font, in my case, it was returning the system font. But the panels use another font, and the statusbar does not reveal it directly, instead, you use the %WM_GETFONT message to the statusbar to retrieve the font used by the panels of the status bar. Then use that font in a generic DC to get the correct length of the string. One thing that I'm not sure of is the fact that I'm returning the length of the string with twice the size of the Divider tacked on, if I didn't do this, the string would run into the right edge of the panel.
Next up is the interface part of the class, first is several property handling statements for manipulating various aspects of the statusbar.
It is probably incomplete, there might be more aspects of the statusbar which could be added to this, for example tinkering with the background color, font, icon's, whatever is needed to make the statusbar fit the look of your application.
Currently, I have only 4 methods in this class:
I have yet to build the RemovePanel and InsertPanel functions. AddPanel does not need any parameters to work, but you can pass them and take care of all the grunt work on creating a panel. The defaults would be:
Style = %SBT_NORMAL ' depressed panel
Type = %SBU_FIXED ' set width for the panel
Width = DefaultWidth ' whatever was designated as the default width for a panel
One of the property statements should be a means to set and get the default width of new panels, any existing panels either may or may not be set to this default width depending on how you code the property. Likewise, there could be property's for dealing with how a new panel is added into the system in terms of what style is used and what type of panel it becomes.
Oh hey, so the move method does not really move the status bar, but instead takes the usual WinProc parameters which are passed to the class and pass them on to the statusbar, I would of called this Size except that's a reserved word with PB, and I do try to avoid using reserved words when creating stuff. This method also recalculates the widths of the parts for the status bar as well.
Needed one method for assigning the control to the class:
:rant:
And this is why I would like to see the inclusion of a CONSTRUCTOR method for PB Class's, it would permit the creation of the statusbar in one easy stroke.
Ranting aside, this method stores the owner, the statusbar handle, and the id used for the status bar, then figures out the width of the grip. What if you are using a statubar in a non-resizable dialog or window? It still works, the size of the grip is merely 0. The divider is also figured out, and in probably 99% of the systems out there, it will probably be 2, but better to get the actual size over making it a fixed value.
And finally the most important part of this class:
Setting some text to the statusbar. I inverted the order of the Text and Index intentionally with this to permit the ease of setting just a simple mode statusbar, or just setting text to the first part of the statusbar easy, just omit the index. If the selected part is not of the type %SBU_FIXED, the method also calls on the class method to recalculate the widths for the status bar.
One of the interesting things I noted that if I put a "#DEBUG PRINT CALLSTK$(2)" right at the beginning of the method, it reveals the who called the SetText method of the class, which leads to the possible use of "owned" panels for the statusbar. That is, if function X sets text, it sets text to a specific part of the statusbar, and pIndex is ignored, unless the function owns more than one, in which case pIndex would indicate which one it's setting text to.
Attached ZIP contains a sample driver program (StatusBar.bas) along with the StatusBarClass.bas which is constructed so far. To show that the GripSize is properly set when using a un-resizeable window, there's a second DIALOG NEW in the ShowSTATUSBARTESTER function, just comment out the first and uncomment the second. The clock part of the statusbar is still set to the right edge of the dialog.
One minor thing I noticed, and is shown here, is that if the last panel is not set to be either %SBU_REMAIN, with one other in the array prior to the last panel set as a %SBU_REMAIN, or you don't use FillLast = %TRUE, is that there's a couple of pixels remaining to the end of the bar. You can see that by uncommenting out the:
CONTROL SEND hDlg, %IDSB_MAINSTATUS, %SB_SETBKCOLOR, 0, %RED
And running the program in both FillLast = %FALSE and FillLast = %TRUE modes, (line 92 in the program).
Have fun.

This idea bounced into my a while back, and my idea was, "Why not wrap your controls in an object, let the object class handle all the nitty gritty details, and you just use the Object to manage what you want to do with the control?", which will work with a lot of controls you care to use. And then you can do strange things which would normally require several lines of code in your application, that boils down to calling upon a single method, or using the properties, of the object to do the same thing. Example?
From:
CONTROL SET LOC CB.HNDL, %ID_CONTROL, x, y
CONTROL SET SIZE CB.HNDL, %ID_CONTROL, xx, yy
To:
MyControlClass.Move x, y, xx, yy
Oh say, that looks familiar. Sneaky.
Other stuff can be done inside the class method which you, as the programmer, don't see, don't have to worry about, and should no longer need to be concerned about with the class providing it is well thought out ahead of time and works the way you want it to. For example, included in that move the object could also be storing the position and size of said control, so you can retrieve just say the width later on without having to:
CONTROL GET SIZE CB.HNDL, %ID_CONTROL TO x, y
Just access a property:
myVariable = MyControlClass.nWidth
Properly written, the class becomes usable for a whole range of projects, very recyclable, and easy to maintain, especially if you make them into a common INC file. It might be possible to write a single class which can handle every possible control, but that would be overkill, would be simpler to write a single class for handling a specific control, or a limited selection of controls which behave in many ways like one another. For this example, I'm tackling the statusbar control, and specifically, to make the status bar flexible in the use of the panels.
This is an incomplete work in progress, does need some finer features done, but so far it works.
First is a few TYPE's and CONST's for the class:
Code:
TYPE StatusItems sText AS ASCIIZ * 128 sWidth AS LONG sType AS LONG sStyle AS LONG END TYPE %SBU_FIXED = 0 %SBU_SPRING = 1 %SBU_REMAIN = 2 %SBU_MAXPANELS = 25 %SBT_NORMAL = 0
The class's instance variables:
Code:
CLASS STATUSBARCLASS INSTANCE hStatusBar AS DWORD INSTANCE idStatusBar AS DWORD INSTANCE hParent AS DWORD INSTANCE pCount AS LONG INSTANCE sPanels() AS StatusItems INSTANCE GripWidth AS LONG INSTANCE Divider AS LONG INSTANCE DefaultWidth AS LONG INSTANCE FillLast AS LONG
Everything above should be pretty evident, the GripWidth is how wide the grip is on the statusbar, it's calculated from comparing the inner area available to the statusbar versus the outside dimensions of the statusbar when you assign a statusbar to the class's care. Divider is the borders of a panel, and FillLast is used to achieve flush to the right edge with the last panel effects.
Creating the class:
Code:
CLASS METHOD CREATE() pCount = 0 REDIM sPanels(pCount) sPanels(pCount).sText = "StatusBar" sPanels(pCount).sWidth = ME.SpringWidth(0) sPanels(pCount).sType = %SBU_SPRING sPanels(pCount).sStyle = %SBT_NORMAL DefaultWidth = 50 FillLast = %TRUE END METHOD CLASS METHOD DESTROY() END METHOD
The create basically sets up the internal variables at this point to some default values.
:rant:
PB needs a CONSTRUCTOR for it's classes, so you can pass values to the class on creation and not have to write code into the class to take variables from the program after creation. More later.
Currently I have two private class methods used in the status bar, one's fairly simple, the other darn complex. Tackle the complex one first in chunks:
Code:
CLASS METHOD ReCalculateWidths() LOCAL pIndex AS LONG LOCAL rIndex AS LONG LOCAL sbWidth AS LONG LOCAL sRect AS RECT DIM pParts(pCount) AS LONG GetClientRect hStatusBar, sRect sRect.nRight = sRect.nRight - GripWidth - (Divider * pCount) sbWidth = sRect.nRight
Code:
FOR pIndex = 0 TO pCount sbWidth = sbWidth - sPanels(pIndex).sWidth IF sbWidth < 0 THEN EXIT FOR NEXT pIndex IF sbWidth < 0 THEN ' change of tatics ' convert everything into a percentage of width and apply sbWidth = 0 rIndex = 0 FOR pIndex = 0 TO pCount rIndex = rIndex + sPanels(pIndex).sWidth NEXT pIndex FOR pIndex = 0 TO pCount sbWidth = sbWidth + _ (sRect.nRight * (sPanels(pIndex).sWidth / rIndex)) pParts(pIndex) = sbWidth NEXT pIndex ELSE ' default sizes fit into space, find the Remaining then allocate sbWidth = 0 FOR pIndex = 0 TO pCount IF (sPanels(pIndex).sType = %SBU_REMAIN) THEN EXIT FOR sbWidth = sbWidth + sPanels(pIndex).sWidth + Divider pParts(pIndex) = sbWidth NEXT pIndex ' note we bust out on finding the first %SBU_REMAIN, if there's none ' then pIndex will be greater than pCount. ' if pIndex happens to equal pCount, that just means that the last ' element is a %SBU_REMAIN, acts like FillLast. IF pIndex < pCount THEN ' need to calculate the remaining, if any ' then subtract it from the width sbWidth = sRect.nRight FOR rIndex = pIndex + 1 TO pCount sbWidth = sbWidth - (sPanels(rIndex).sWidth) NEXT rIndex pParts(pIndex) = sbWidth ' calculate the remaining parts FOR rIndex = pIndex + 1 TO pCount sbWidth = sbWidth + sPanels(rIndex).sWidth + Divider pParts(rIndex) = sbWidth NEXT rIndex END IF IF (ISTRUE FillLast) OR (pIndex = pCount) THEN ' we execute a fake if last panel is %SBU_REMAIN pParts(pCount) = -1 END IF END IF SendMessage hStatusBar, %SB_SETPARTS, pCount + 1, VARPTR(pParts(0)) END METHOD
Each time a string is set to any part of the statusbar which is set to be either %SBU_SPRING or %SBU_REMAIN, the size of the part needs to recalculated to the length of the string.
Code:
CLASS METHOD SpringWidth(vPart AS LONG) AS LONG LOCAL tSize AS SIZEL LOCAL hDC, hFont, oFont AS DWORD hFont = SendMessage(hStatusBar, %WM_GETFONT, 0, 0) hDC = CreateCompatibleDC(0) oFont = SelectObject(hDC, hFont) GetTextExtentPoint32 hDC, sPanels(vPart).sText, LEN(sPanels(vPart).sText), tSize SelectObject hDC, oFont DeleteDC hDC METHOD = tSize.cx + (Divider * 2) END METHOD
Next up is the interface part of the class, first is several property handling statements for manipulating various aspects of the statusbar.
Code:
INTERFACE STATUSBARINTERFACE INHERIT IUNKNOWN PROPERTY GET FillMode() AS LONG PROPERTY = FillLast END PROPERTY PROPERTY SET FillMode(BYVAL nFill AS LONG) FillLast = nFill END PROPERTY PROPERTY GET PanelCount() AS LONG PROPERTY = pCount END PROPERTY PROPERTY GET SetStyle(BYVAL pIndex AS LONG) AS LONG IF pIndex <= pCount THEN _ PROPERTY = sPanels(pIndex).sStyle END PROPERTY PROPERTY SET SetStyle(BYVAL pIndex AS LONG, BYVAL nStyle AS LONG) IF pIndex <= pCount THEN _ sPanels(pIndex).sStyle = nStyle END PROPERTY PROPERTY GET SetType(BYVAL pIndex AS LONG) AS LONG IF pIndex <= pCount THEN _ PROPERTY = sPanels(pIndex).sType END PROPERTY PROPERTY SET SetType(BYVAL pIndex AS LONG, BYVAL nType AS LONG) IF pIndex <= pCount THEN _ sPanels(pIndex).sType = nType END PROPERTY
Currently, I have only 4 methods in this class:
Code:
METHOD AddPanel(OPTIONAL BYREF vText AS STRING, BYREF vStyle AS LONG, _ BYREF vType AS LONG, BYREF vWidth AS LONG) AS LONG LOCAL pError AS LONG IF pCount < %SBU_MAXPANELS THEN INCR pCount REDIM PRESERVE sPanels(pCount) IF ISMISSING(vStyle) THEN sPanels(pCount).sStyle = vStyle IF NOT ISMISSING(vType) THEN sPanels(pCount).sType = vType IF NOT ISMISSING(vText) THEN sPanels(pCount).sText = vText IF sPanels(pCount).sType > 0 THEN sPanels(pCount).sWidth = ME.SpringWidth(pCount) ELSE IF ISMISSING(vWidth) THEN sPanels(pCount).sWidth = DefaultWidth ELSE sPanels(pCount).sWidth = vWidth END IF END IF ' recalculate sizes and set the text ME.ReCalculateWidths SendMessage hStatusBar, %SB_SETTEXT, _ sPanels(pCount).sStyle OR pCount, VARPTR(sPanels(pCount).sText) METHOD = pCount ELSE METHOD = -1 END IF END METHOD
Style = %SBT_NORMAL ' depressed panel
Type = %SBU_FIXED ' set width for the panel
Width = DefaultWidth ' whatever was designated as the default width for a panel
One of the property statements should be a means to set and get the default width of new panels, any existing panels either may or may not be set to this default width depending on how you code the property. Likewise, there could be property's for dealing with how a new panel is added into the system in terms of what style is used and what type of panel it becomes.
Code:
METHOD Move(wMsg AS DWORD, wParam AS LONG, lParam AS LONG) SendMessage hStatusBar, wMsg, wParam, lParam ME.ReCalculateWidths END METHOD
Needed one method for assigning the control to the class:
Code:
METHOD SetControl(hOwner AS DWORD, hControl AS DWORD, idControl AS DWORD) DIM sBorders(2) AS LONG LOCAL sRect AS RECT idStatusBar = idControl hParent = hOwner hStatusBar = hControl SendMessage hStatusBar, %SB_SETTEXT, _ sPanels(0).sStyle OR 0, VARPTR(sPanels(0).sText) SendMessage hStatusBar, %SB_GETBORDERS, 0, VARPTR(sBorders(0)) Divider = sBorders(2) GetClientRect hStatusBar, sRect GripWidth = sRect.nRight SendMessage hStatusBar, %SB_GETRECT, 0, VARPTR(sRect) GripWidth = GripWidth - sRect.nRight END METHOD
And this is why I would like to see the inclusion of a CONSTRUCTOR method for PB Class's, it would permit the creation of the statusbar in one easy stroke.
Ranting aside, this method stores the owner, the statusbar handle, and the id used for the status bar, then figures out the width of the grip. What if you are using a statubar in a non-resizable dialog or window? It still works, the size of the grip is merely 0. The divider is also figured out, and in probably 99% of the systems out there, it will probably be 2, but better to get the actual size over making it a fixed value.
And finally the most important part of this class:
Code:
METHOD SetText(BYREF vText AS STRING, OPTIONAL BYVAL pIndex AS LONG) IF pIndex <= pCount THEN sPanels(pIndex).sText = vText SendMessage hStatusBar, %SB_SETTEXT, _ sPanels(pIndex).sStyle OR pIndex, VARPTR(sPanels(pIndex).sText) ' recalculate size if the element is a spring IF sPanels(pIndex).sType > 0 THEN sPanels(pIndex).sWidth = ME.SpringWidth(pIndex) ME.ReCalculateWidths END IF END IF END METHOD END INTERFACE END CLASS
One of the interesting things I noted that if I put a "#DEBUG PRINT CALLSTK$(2)" right at the beginning of the method, it reveals the who called the SetText method of the class, which leads to the possible use of "owned" panels for the statusbar. That is, if function X sets text, it sets text to a specific part of the statusbar, and pIndex is ignored, unless the function owns more than one, in which case pIndex would indicate which one it's setting text to.
Attached ZIP contains a sample driver program (StatusBar.bas) along with the StatusBarClass.bas which is constructed so far. To show that the GripSize is properly set when using a un-resizeable window, there's a second DIALOG NEW in the ShowSTATUSBARTESTER function, just comment out the first and uncomment the second. The clock part of the statusbar is still set to the right edge of the dialog.
One minor thing I noticed, and is shown here, is that if the last panel is not set to be either %SBU_REMAIN, with one other in the array prior to the last panel set as a %SBU_REMAIN, or you don't use FillLast = %TRUE, is that there's a couple of pixels remaining to the end of the bar. You can see that by uncommenting out the:
CONTROL SEND hDlg, %IDSB_MAINSTATUS, %SB_SETBKCOLOR, 0, %RED
And running the program in both FillLast = %FALSE and FillLast = %TRUE modes, (line 92 in the program).
Have fun.
Comment