Announcement

Collapse
No announcement yet.

Sending DDT Controls To Class

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Sending DDT Controls To Class

    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:
    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
    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:
    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
    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:
    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
    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:
    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
    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.
    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
    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.
    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
    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.
    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
    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:
    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
    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.

    Code:
            METHOD Move(wMsg AS DWORD, wParam AS LONG, lParam AS LONG)
                SendMessage hStatusBar, wMsg, wParam, lParam
                ME.ReCalculateWidths
            END METHOD
    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:
    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
    :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:
    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
    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.
    Furcadia, an interesting online MMORPG in which you can create and program your own content.

  • #2
    Attachment did not

    Oh ha, didn't notice the ZIP did not attach.
    Attached Files
    Furcadia, an interesting online MMORPG in which you can create and program your own content.

    Comment

    Working...
    X