Announcement

Collapse
No announcement yet.

Graphic Window and Subclassing - there are limits...

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

  • Graphic Window and Subclassing - there are limits...

    Over the past few months there have been a few posts - I think that Guy Dombrowski started it - about using subclassing in conjunction with Graphic Windows. This evidently worked when the communication with the main program is through globals, and was restricted to the cursor position, mouse clicks, and keys pressed. However, when the concept was pushed a little further, problems arose - when I started to put graphic statements into the subclass code, I found that the GW needed to be re-attached - although the main program knew about the "graphic attachment" - the subclass routine did not, and had to issue its own GRAPHIC ATTACH.

    Since then I have managed to break the principal feature of GWs - their persistence. The GW is drawn OK, but superimpose another window, or move the client area off-screen, and - goodbye, drawing!

    If I can't find a work-around for this, my ambition of devising applications using window-like boxes on a GW will be thwarted - oh well, no great loss.

    The moral of the story may well be - don't mix DDT techniques with SDK techniques. In particular,
    from PowerBasic Support: PowerBASIC cannot be responsible for handling the graphics if you are subclassing the graphic window.
    But then again, by not using GWs, instead using standard API functions and writing WM_PAINT and WM_ERASEBKGND handlers, the same results can be achieved by drawing on a STATIC window. The only graphic drawing commands I use are Print, Box and Clear. These can be replaced with API calls.

    Big thanks to PowerBasic Inc and Jeff Daniels for patient assistance with my struggle. A couple of documentation features were exposed so I hope it was not too one-sided.

    If anyone would care to try the code, here it is. There are draw, drag and size functions which work in a fairly intuitive way, and a right-click menu for deletion and z-ordering. If the screen needs to be refreshed because of the problem noted above, just click it.

    Code:
    '
    ' Drawing, dragging and resizing boxes on a Graphic Window (revisited)
    ' investigation into using objects. Needs PBCC V5.xx
    '
    ' version 1.1
    '
    ' Chris Holbrook Jan 2009
    '
    #compile exe
    #dim all
    #break on
    #include "win32api.inc"
    global hGW as dword
    global gOriginalProc as dword
    global gBX() as ICtlBox
    '--------------------------------------------------------------------------------
    ' colours
    %DEFAULT_COLOR       = %white
    %DRAW_COLOR          = &H00e8e8FF
    %DRAG_COLOR          = &H00e8ffe8
    %SIZE_COLOR          = &H00ffe8e8
    %RCLICKSUBJECT_COLOR = &H0080e8FF
    '
    ' drawmodes
    %RCLICKSUBJECT  =  4
    %SIZING         =  3
    %DRAWING        =  2
    %DRAGGING       =  1
    %IDLING         =  0
    '
    ' dimensions
    %BOXLINEWIDTH     =  1
    %SIZEBORDERWIDTH  = 10
    %MINVIABLEBOXSIDE = 25
    '
    class CtlboxClass
        instance X              as long
        instance Y              as long
        instance W              as long
        instance H              as long
        instance boxtype        as long
        instance title          as string
        instance lx             as long
        instance z              as long
        instance bgcolor        as long ' background color, different for default/drag/draw
        instance DDOX           as long ' Draw Drag Origin X, last point clicked
        instance DDOY           as long ' Draw Drag Origin Y, last point clicked
        instance drawmode       as long ' current drawing mode
        '-------------------------------------------
        class method create ()
            bgcolor = %DEFAULT_COLOR
        end method
        '-------------------------------------------
        class method SetDDO (  iX as long,  iY as long)
            DDOX = iX
            DDOY = iY
        end method
        '-------------------------------------------
        class method GetDDO  as long
            method = mak(dword,DDOX, DDOY)
        end method
        '-------------------------------------------
        interface ICtlBox
            inherit iunknown
            '-------------------------------------------
            method SetLoc ( iX as long, iY as long )
                X = iX
                Y = iY
            end method
            '-------------------------------------------
            method GetLoc as dword
                method = mak(dword, X, Y)
            end method
            '-------------------------------------------
            method SetSize ( lW as long, lH as long)
                W = lW
                H = lH
            end method
            '-------------------------------------------
            method GetSize as dword
                method = mak(dword, W, H)
            end method
            '-------------------------------------------
            method SetType ( ltype as long)
                boxtype = ltype
            end method
            '-------------------------------------------
            method GetType  as long
                method = boxtype
            end method
            '-------------------------------------------
            method SetTitle ( s as string)
                title = s
            end method
            '-------------------------------------------
            method SetIndex (lindex as long)
                lx = lindex
            end method
            '-------------------------------------------
            method GetIndex as dword
                method = lx
            end method
            '-------------------------------------------
            method SetZPos ( lz as long)
                z = lz
            end method
            '-------------------------------------------
            method GetZPos  as long
                method = z
            end method
            '-------------------------------------------
            method SetDrawMode ( lmode as long)
                drawmode = lmode
                select case drawmode
                    case %RCLICKSUBJECT
                        bgcolor = %RCLICKSUBJECT_COLOR
                    case %SIZING
                        bgcolor = %SIZE_COLOR
                    case %DRAWING
                        bgcolor = %DRAW_COLOR
                    case %DRAGGING
                        bgcolor = %DRAG_COLOR
                    case %IDLING
                        bgcolor = %DEFAULT_COLOR
                end select
            end method
            '-------------------------------------------
            method startdrag( iX as long, iY as long)
                drawmode = %DRAGGING
                bgcolor = %DRAG_COLOR
                me.SetDDO(iX, iY)
            end method
            '-------------------------------------------
            method enddrag( iX as long, iY as long)
                bgcolor = %DEFAULT_COLOR
                graphic attach hGW, 0, redraw
                graphic color %black, %white
                me.erasebox
                bgcolor = %DEFAULT_COLOR
                drawmode = %IDLING
                me.drawbox
                graphic redraw
            end method
            '-------------------------------------------
            method dragto( iX as long, iY as long)
                graphic attach hGW, 0, redraw
                graphic color %black, %white
                me.erasebox
                X = X + (iX - DDOX)
                Y = Y + (iY - DDOY)
                me.drawbox
                graphic redraw
                DDOX = iX
                DDOY = iY
            end method
            '-------------------------------------------
            method startdraw(  iX as long,  iY as long)
                drawmode = %DRAWING
                bgcolor = %DRAW_COLOR
                me.SetDDO(iX, iY)
                me.SetLoc(iX, iY)
            end method
            '-------------------------------------------
            method startsize ( iX as long, iY as long)
                drawmode = %SIZING
                bgcolor = %SIZE_COLOR
                me.SetDDO(iX, iY)
            end method
            '-------------------------------------------
            method endsize ( iX as long, iY as long)
                graphic attach hGW, 0, redraw
                graphic color %black, %white
                me.erasebox
                bgcolor = %DEFAULT_COLOR
                drawmode = %IDLING
                me.drawbox
                graphic redraw
            end method
            '-------------------------------------------
            method enddraw ( iX as long, iY as long)
               ' possible that X, Y define a point other than the NW corner of the rect
               X = min(X, X + W) : Y = min(Y, Y + H)
                W = abs(W): H = abs(H)
                graphic attach hGW, 0, redraw
                graphic color %black, %white
                me.erasebox
                bgcolor = %DEFAULT_COLOR
                drawmode = %IDLING
                me.drawbox
                graphic redraw
            end method
            '-------------------------------------------
            method drawto( iX as long, iY as long)
                graphic attach hGW, 0, redraw
                graphic color %black, %white
                me.erasebox
                W = iX - DDOX
                H = iY - DDOY
                me.drawbox
                graphic redraw
            end method
            '-------------------------------------------
            method sizeto ( iX as long, iY as long)
                graphic attach hGW, 0, redraw
                graphic color %black, %white
                me.erasebox
                W =  W + (iX - DDOX)
                H =  H + (iY - DDOY)
                me.SetDDO(iX, iY)
                me.drawbox
                graphic redraw
            end method
    
            '-------------------------------------------
            method EraseBox
                graphic box (X - %boxlinewidth, Y - %boxlinewidth) - (X + W + %BOXLINEWIDTH, Y + H + %BOXLINEWIDTH), 0, %white, %white, 0
            end method
            '-------------------------------------------
            method DrawBox
                local s as string
    
                select case as long drawmode
                    case %DRAWING
                        s = "DRAWING "
                    case %DRAGGING
                        s = "DRAGGING "
                    case %SIZING
                        s = "SIZING   "
                    case %IDLING
                        s = "IDLING   "
                    case %RCLICKSUBJECT
                        s = "RCLICK   "
                end select
                s = s + "(" + format$(X) + "," + format$(Y) + ")-(" + format$(W) + "," + format$(H) + ")"
                sendmessage hGW, %WM_SETTEXT, 0, strptr(s)
    
                'graphic box (100, 100) - (200, 200), 0, %white, %red, 0
                graphic box (X, Y) - (X + W, Y + H), 0, %black, bgcolor, 0
                graphic set pos (X + 5, y + 5)
                graphic print str$(lx) + " z=" + format$(z) + " " + title
            end method
            '-----------------------------------------------------------------
            ' returns zero if point not in box, 1 if point in size border, 2 if point inside size border
            method pointenclosed ( iX as long, iY as long ) as long
                local r as rect
    
                setrect r, X, Y, X + W, Y + H
                if ptinrect ( r, iX, iY) then
                    inflaterect r, - %SIZEBORDERWIDTH - 1, - %SIZEBORDERWIDTH -1
                    if ptinrect ( r, iX, iY) then
                        method = 2: exit method
                    end if
                    method = 1: exit method
                end if
            end method
    
        end interface
    end class
    '--------------------------------------------------------------------------------
    ' make a list of ctlbox indexes in ascending z order and draw boxes in Z order
    sub DrawInZOrder
        local i, j, k, n as long
        local zs() as long
    
        graphic clear %white, 0
        n = ubound(gBX)
        if n >= 0 then
            dim zs(0 to n) as local long
            for i = 0 to n
                if not isobject (gbx(i)) then
                    ? "notobject", i
                    exit sub
                else
                    j = gBX(i).GetZPos
                end if
                zs(i) = mak(dword, i, j)
            next
            array sort zs() ' NB z order is Most Significant word so this sorts in Z order
            for i = 0 to n
                j = lo(word,zs(i))
                gBX(j).Drawbox
            next
        end if
        graphic redraw
    end sub
    '--------------------------------------------------------------------------------
    ' move the box specified by lx up(direction = 0) or down (direction <> 0) the Z order
    ' by adjusting the Z orders of boxes
    sub reZorder ( lx as long, direction as long )
        local i, j, n as long
        local zs() as long
    
        n = ubound(gBX)
        for i = 0 to n
            j = gBX(i).getZpos
            gBX(i).SetZPos(j*10)
        next
    
        if direction = 0 then
            j = gBX(lx).GetZpos + 11
            gBX(lx).SetZPos(j)
        else
            j = gBX(lx).GetZpos - 11
            gBX(lx).SetZPos(j)
        end if
    
        n = ubound(gBX)
        dim zs(0 to n) as local long
        for i = 0 to n
            zs(i) = mak(dword, i, gBX(i).GetZPos)
        next
        array sort zs() ' NB z order is Most Significant word so this sorts in Z order
        for i = 0 to n
            j = lo(word,zs(i))
            gBX(j).SetZPos ( i )
        next
        DrawinZorder
    end sub
    '--------------------------------------------------------------------------------
    ' return two words in a dword.
    ' loword is 1 if the point is in the sizing border, 2 if elsewhere in the box, 0 if not in the box
    ' if loword > 0 then hiword = index of the CtlBox enclosing the cursor position given by the parameters
    ' or -1 if no such box is defined
    ' NB the boxes are inspected in Z order, if two boxes are overlapping then the one with the
    ' highest Z order is picked first
    function inabox ( X as long, Y as long ) as dword
        local i, j, k, n as long
    
        local zs() as long
    
        graphic clear %white, 0
        n = ubound(gBX)
        if n < 0 then
            function = -1
            exit function
        end if
        dim zs(0 to n) as local long
        for i = 0 to n
            zs(i) = mak(dword, i, gBX(i).GetZPos)
        next
        array sort zs(), descend ' NB z order is Most Significant word so this sorts in Z order
        for i = 0 to n
            k = lo(word,zs(i))
            j = gBX(k).pointenclosed( X, Y )
            if j then
                function = mak(dword,j,k)
                exit function
            end if
        next
        function = -1
    end function
    '--------------------------------------------------------------------------------
    ' delete the control and shuffle the array down, redim to remove topmost member
    sub DeleteControl (lx as long)
        local i, n as long
    
        n = ubound(gBX)
        gBX(lx) = nothing
        for i = lx to n -1
            gBX(i) = gBX(i + 1)
        next
        gBX(n) = nothing
        if n > 0 then
            redim preserve gBX(0 to n-1) as global ICtlBox
            DrawInZOrder
        else
            erase gBX()
            graphic clear %white
            graphic redraw
        end if
        ? "done del"
    end sub
    '--------------------------------------------------------------------------------
    ' right click menu. Parameter is the CtlBox index
    sub RClickMenu ( lx as long, X as long, Y as long)
        local hmenu, menuchoice as long
        local pt as POINTAPI
        local hWnd as dword
    
        hWnd = GetWindow(hGW, %GW_CHILD)
        pt.X = X : pt.Y = Y
        Clienttoscreen(hWnd, pt)
        hmenu = CreatePopupMenu
        InsertMenu(hMenu, 0, %MF_BYCOMMAND, 1, "move up Z order")
        InsertMenu(hMenu, 0, %MF_BYCOMMAND, 2, "move down Z order")
    '    InsertMenu(hMenu, 0, %MF_BYCOMMAND, 3, "Edit Title")
    '    InsertMenu(hMenu, 0, %MF_BYCOMMAND, 4, "Edit Control Type")
        InsertMenu(hMenu, 0, %MF_BYCOMMAND, 5, "Delete Control")
    
        MenuChoice = TrackPopupMenuEx(hmenu, _
                             %mf_enabled or %MF_BYCOMMAND or %TPM_RETURNCMD, _
                             pt.X, pt.Y, _
                             hWnd, byval %NULL)
        select case as long menuchoice
            case 1 ' up z order
                rezorder ( lx, 0)
            case 2 ' down z order
                rezorder ( lx, 1)
            case 3 ' edit title
                ? "edit title"
            case 4 ' edit type
                ? "edit type"
            case 5 ' delete
                DeleteControl(lx)
        end select
    
    end sub
    '--------------------------------------------------------------------------------
    function GrProc(byval hWnd as dword, byval wMsg as dword, byval wParam as dword, byval lParam as long) as long
      'local p as pointapi
      static bx as long
      local bxtop, i, l, lresult, n, x, y as long
      local s as string
      static drawmode as long ' either %DRAWING or %DRAGGING or %IDLING
      static hcursor, dragcursor, drawcursor, sizecursor as dword
    
      select case wMsg
        '
        case %wm_user + 400
            SizeCursor = LoadCursor(%NULL,byval %IDC_SIZENWSE)
            DragCursor = LoadCursor(%NULL,byval %IDC_HAND)
            DrawCursor = LoadCursor(%NULL,byval %IDC_CROSS)
            hcursor    = getcursor
    
        case %wm_mousemove
            x = lo(word, lparam)
            y = hi(word, lparam)
            lresult = inabox( X, Y)
            select case as long lo(word,lresult)
                case 1 ' sizing
                    setcursor SizeCursor
                case 2 ' dragging
                    setcursor DragCursor
                case else
                    setcursor hCursor
            end select
            '
            select case as long drawmode
                case %DRAWING
                    setcursor DrawCursor
                    gBX(bx).drawto( X, Y)
                    DrawInZOrder
                    function = 1
                    exit function
                case %DRAGGING
                    setcursor DragCursor
                    gBX(bx).dragto( X, Y )
                    DrawInZOrder
                    function = 1
                    exit function
                case %SIZING
                    setcursor SizeCursor
                    gBX(bx).sizeto( X, Y )
                    DrawInZOrder
                    function = 1
                    exit function
                case else ' idling
            end select
        '
        case %wm_lbuttondown
            x = lo(word, lparam)
            y = hi(word,lparam)
            lresult = inabox( X, Y)
            if lresult = -1 then
                bx = 0
                lresult = 0
            else
                bx = hi(word, lresult)
                lresult = lo(word, lresult)
            end if
            's = str$(bx)
            'sendmessage getparent(hWnd), %WM_SETTEXT, 0, strptr(s)
            select case as long lresult
                '
                case 0 ' user clicked ouside a box so start a new one
                    drawmode = %DRAWING
                    n = 0
                    bxtop = ubound(gBX)
                    for i = 0 to bxtop
                        n = max(n,gBX(i).GetIndex)
                    next
                    bx = bxtop + 1
                    redim preserve gBX(0 to bxtop + 1) as ICtlBox
                    let gBX(bx) = class "CtlBoxClass"
                    gBX(bx).SetIndex ( n + 1 ) ' the highest number in use + 1
                    gBX(bx).SetZPos ( bx )
                    gBX(bx).startdraw( X, Y)
                case 1 ' user clicked on sizing border
                    drawmode = %sizing
                    gBX(bx).startsize( X, Y)
                case 2 ' user clicked elsewhere inside box, prepare to drag existing box
                    drawmode = %DRAGGING
                    gBX(bx).startdrag( X, Y)
            end select
            function = 1
            exit function
        '
        case %wm_lbuttonup
            x = lo(word, lparam)
            y = hi(word,lparam)
            setcursor hcursor
            select case as long drawmode
                case %DRAWING
                    l = gBX(bx).GetSize
                    if (lo(word,l) < %MINVIABLEBOXSIDE) or (hi(word,l) < %MINVIABLEBOXSIDE) then
                        n = ubound(gBX)
                        if n = 0 then
                            erase gBX()
                        else
                            redim preserve gBX ( 0 to n - 1 ) as global ICtlBox ' get rid of last controlbox
                        end if
                    else
                        gBX(bx).enddraw( X, Y)
                    end if
                    drawmode = %IDLING
                    if ubound(gBX) > -1 then
                        DrawInZOrder
                    else
                        graphic attach hGW, 0, redraw
                        graphic clear %white
                        graphic redraw
                        s = "IDLING"
                        sendmessage hGW, %WM_SETTEXT, 0, strptr(s)
                    end if
                    function = 1
                    exit function
                case %DRAGGING
                    gBX(bx).enddrag( X, Y)
                    drawmode = %IDLING
                    DrawInZOrder
                    function = 1
                    exit function
                case %SIZING
                    gBX(bx).endsize( X, Y)
                    drawmode = %IDLING
                    DrawInZOrder
                    function = 1
                    exit function
            end select
        '
        case %wm_rbuttondown
            x = lo(word, lparam)
            y = hi(word,lparam)
            lresult = inabox( X, Y)
            if lresult = -1 then
                exit select
            else
                i = hi(word, lresult)
            end if
            gBX(i).SetDrawmode (%RCLICKSUBJECT)
            DrawInZorder
            RClickMenu ( i, X, Y )
            if i <= ubound(gBX) then
                if isobject(gBX(i)) then
                    gBX(i).SetDrawMode (%IDLING)
                    DrawInZOrder
                end if
            end if
            function = 1
            exit function                      ' Right button pressed
        '
        case %wm_rbuttonup
            exit function                      ' Right button pressed
        '
        case %wm_ncrbuttonup
            beep
        '
        case %wm_destroy
            for i = 0 to ubound(gBX)
                gBX(i) = nothing
            next
      end select
    
     function = CallWindowProc(gOriginalProc, hWnd, wMsg, wParam, lParam)
    
    end function
    
    '-----------------------------------------------------------------------------
    function pbmain () as long
        local X, Y, W, H as long
        local skey as string
        local hWndChildofGW as dword
    
        console get loc to x, y    ' Console pos on screen
    
        console get size to w, h   ' Console size
    
        x = x + (w - 200) / 2      ' Calculate center x pos
    
        y = y + (h - 200) / 2      ' Calculate center y pos
    
        graphic window "draw boxes", 100, 100, 500, 500 to hGW
    
        hWndChildofGW  = GetWindow(hGW, %GW_CHILD)                       ' Retrieve static handle of graphic window
        gOriginalProc  = SetWindowLong(hWndChildofGW, %GWL_WNDPROC, codeptr(GrProc)) ' Subclasses Graphic control
        sendmessage hWndChildofGW, %wm_user + 400, 0, 0 ' initialise WndProc
        graphic attach hGW, 0, redraw
        graphic color %black, %white
        do
            sleep 2
        loop until hGW = 0
    
        SetWindowLong(hWndChildofGW, %GWL_WNDPROC, gOriginalProc) ' remove subclassing
    
        graphic waitkey$ to sKey
    end function

  • #2
    Subclassing limit

    Hi Chris,
    You are right about the limit of subclassing.
    There must be a zillion hours invested in your program.
    I limit myself to Mouse and Keyboard and it does work great when used with GRAPHIC INKEY$ in a Graphic Console.
    I am betting Michael will laugh at you.....
    But don't let him put you down.
    You must have learned a lot in trying that technique.
    Old QB45 Programmer

    Comment


    • #3
      Hi Guy, actually I just re-thought the code while busy with other stuff and wrote it from scratch on Monday & Tuesday this week. Would have been finished quicker if I had understood how to debug with #break on - it's only taken 3 years to learn that one!

      I am betting Michael will laugh at you.....
      Oh surely not?

      Comment


      • #4
        I'm not laughing at all.

        Something I have figured out: the GRAPHICS additions to PB/CC are quite popular, so it's not a surprise people are trying all kinds of new things with them.

        It's "GUI on the cheap" I guess, and if there is one thing I ALREADY knew it's that anything on the cheap is a big fan favorite.
        Michael Mattias
        Tal Systems (retired)
        Port Washington WI USA
        [email protected]
        http://www.talsystems.com

        Comment

        Working...
        X