Author Topic: [SDK] 08 - Take control of your TRACKBAR  (Read 7570 times)

Patrice Terrier

  • Administrator
  • *****
  • Posts: 1991
    • zapsolution
[SDK] 08 - Take control of your TRACKBAR
« on: February 09, 2020, 05:10:49 pm »
WORK IN PROGRESS

This time I will show you how to create a complete control from ground zero (no SUPERCLASS)

Because I am not pleased with the standard Windows trackbar behavior, I choose it as an example.

1 - We start creating a new CLASS

'// Create an image trackbar control
Code: [Select]
FUNCTION zTrackbar (BYVAL hOwner AS LONG, zFullpathImageName AS ASCIIZ, BYVAL x AS LONG, BYVAL y AS LONG, BYVAL tW AS LONG, BYVAL tH AS LONG, BYVAL ButID AS LONG, BYVAL tMin AS LONG, BYVAL tMax AS LONG, BYVAL tVal AS LONG ) AS LONG
    LOCAL wc  AS WNDCLASSEX
    LOCAL zClass AS ASCIIZ * 10
    LOCAL hCtrl, IsInitialized AS LONG

    IF tMin = tMax THEN EXIT FUNCTION

    zClass = "ZTRACKBAR"
    wc.cbSize = SIZEOF(wc)
    IsInitialized = GetClassInfoEx(zInstance, zClass, wc)
    IF IsInitialized    = 0 THEN
       wc.cbSize        = SIZEOF(wc)
       wc.style         = %CS_HREDRAW OR %CS_VREDRAW OR %CS_DBLCLKS OR %CS_PARENTDC
       wc.lpfnWndProc   = CODEPTR(TrackProc)
       wc.cbClsExtra    = 0
       wc.cbWndExtra    = %EXTEND_EXTRA * 4
       wc.hInstance     = zInstance
       wc.hIcon         = %NULL ' LoadIcon(wc.hInstance, "PROGRAM")
       wc.hCursor       = %NULL
       wc.hbrBackground = %NULL ' Don't paint the class window background
       wc.lpszMenuName  = %NULL
       wc.lpszClassName = VARPTR(zClass)
       wc.hIconSm       = wc.hIcon
       IF RegisterClassEx(wc) THEN IsInitialized = %TRUE
    END IF
'
    IF IsInitialized THEN
       LOCAL Img AS LONG
'      // Create GDIPLUS image from file
       Img = zCreateImageFromFile(zFullpathImageName)
       IF Img THEN
          LOCAL imgW, imgH AS LONG
'         // Get the Thumb GDIPLUS image size
          CALL zGetImageSize(Img, imgW, imgH)
          Style& = %WS_CHILD OR %WS_VISIBLE OR %WS_TABSTOP ' OR %BS_OWNERDRAW

          hCtrl = CreateWindowEx(%WS_EX_TRANSPARENT, zClass, "", Style&, x, y, tW, tH, hOwner, ButID, zInstance, BYVAL %NULL)
          IF hCtrl THEN
'            // Save new properties
             CALL zSetProperty(hCtrl, %TRACK_IMAGE, Img)
             CALL zSetProperty(hCtrl, %TRACK_MINVAL, tMin)
             CALL zSetProperty(hCtrl, %TRACK_MAXVAL, tMax)
             CALL zSetProperty(hCtrl, %TRACK_VALUE, tVal)
             CALL zSetProperty(hCtrl, %TRACK_WAS_VALUE, tVal)

             FUNCTION = hCtrl
          ELSE
'            // Delete image
             CALL zDisposeImage(Img)
          END IF
       END IF
    END IF
END FUNCTION

To use it, just call it that way:

hBut = zTrackbar(
hMain, _ ' Handle to the parent owner
"BTN_Star13.png", _ ' Full path name to the image being used to draw the THUMB
X, _ ' The control X location
Y, _ ' The control Y location
Width, _ ' The control width
Height, _ ' The control height
Control_ID, _ ' The control unique identifier
MinValue, _  ' The minimum trackbar value (use LONG only)
MaxValue, _ ' The maximum trackbar value (use LONG only)
SeedValue, _ ' The default trackbar value at startup (use LONG only)
ARGB color, _ ' The ARGB color to draw the TRACK of the bar
)

Note: That the control is "intelligent" it understands that if the size of the Width is larger than the Height, then you want an horizontal trackbar, else a vertical ;)
And swapping the Min Max values, means in case of horizontal, draw the track from left to right or from righ to left ; and in case of vertical, draw the track from top to bottom or bottom to top.

When thumb location changes, parent owner is notified with a standard command message, like this:
'// Send message to parent to notify the new value
CALL SendMessage(GetParent(hWnd), %WM_COMMAND, MAKLNG(GetDlgCtrlID(hWnd),0), zGetProperty(hWnd, %TRACK_VALUE))

2 - All the hard work is done is the TrackProc public callback (exported)

'// We handle all our custom trackbar messages there
Code: [Select]
FUNCTION TrackProc (BYVAL hWnd AS LONG, BYVAL Msg AS LONG, BYVAL wParam AS LONG, BYVAL lParam AS LONG) EXPORT AS LONG
    LOCAL rc AS RECT
    LOCAL hDC, graphics AS LONG

    SELECT CASE LONG Msg

    CASE %WM_MOUSEMOVE
         IF zGetProperty(hWnd, %TRACK_THUMB_MOVING) THEN
'           // Check if left mouse button still down
            LOCAL LeftButtonDown AS LONG
            LeftButtonDown = (wParam AND %MK_LBUTTON)
            IF LeftButtonDown = 0 THEN
               CALL zSetToolTipText(hWnd, (zGetCTLText(hWnd)))
'              // Send message to parent to notify the new value         
               CALL SendMessage(GetParent(hWnd), %WM_COMMAND, MAKLNG(GetDlgCtrlID(hWnd),0), zGetProperty(hWnd, %TRACK_VALUE))
            END IF
            CALL zSetProperty(hWnd, %TRACK_THUMB_MOVING, LeftButtonDown)
         END IF
         IF zGetProperty(hWnd, %TRACK_THUMB_MOVING) THEN
            CALL SetThumbLocation(hWnd, LOWRD(lParam), HIWRD(lParam))
         END IF

    CASE %WM_LBUTTONDOWN
         sToolTip$ = zGetToolTipText(hWnd)
         CALL SendMessage(hWnd, %WM_SETTEXT, 0, STRPTR(sToolTip$))
         CALL zSetProperty(hWnd, %TRACK_THUMB_MOVING, %TRUE)
         CALL SetThumbLocation(hWnd, LOWRD(lParam), HIWRD(lParam))

    CASE %WM_LBUTTONUP
         CALL zSetToolTipText(hWnd, (zGetCTLText(hWnd)))
         CALL zSetProperty(hWnd, %TRACK_THUMB_MOVING, %FALSE)
'        // Send message to parent to notify the new value         
         CALL SendMessage(GetParent(hWnd), %WM_COMMAND, MAKLNG(GetDlgCtrlID(hWnd),0), zGetProperty(hWnd, %TRACK_VALUE))

    CASE %WM_ERASEBKGND
         FUNCTION = 1: EXIT FUNCTION

    CASE %WM_PAINT, %WM_PRINT
         LOCAL Img, imgW, imgH, tMin, tMax, tVal, tx, ty, tW, tH, x, y, ImgAttr AS LONG
         LOCAL ARGB AS LONG, A AS BYTE, R AS BYTE, G AS BYTE, B AS BYTE

         Img  = zGetProperty(hWnd, %TRACK_IMAGE)
         tMin = zGetProperty(hWnd, %TRACK_MINVAL)
         tMax = zGetProperty(hWnd, %TRACK_MAXVAL)
         tVal = zGetProperty(hWnd, %TRACK_VALUE)
         ARGB = zGetProperty(hWnd, %TRACK_ARGB)
         IF Img THEN
            LOCAL ps AS PAINTSTRUCT
            CALL GetClientRect(hWnd, rc)
            IF Msg = %WM_PAINT THEN
               hDC = BeginPaint(hWnd, ps)
            ELSE ' // WM_PRINT
               hDC = wParam
            END IF
            IF GdipCreateFromHDC(hDC, graphics) = 0 THEN

               CALL zGetImageSize(Img, imgW, imgH)
               CALL GetThumbTrackLocation(hWnd, tx, ty)

               LOCAL LEFTorTOPfront, LEFTorTOPback, MIDDLEfront AS LONG

               CALL zSplitColorARGB(ARGB, A, R, G, B)

               CALL GdipCreatePen1(zColorARGB(A, RGB(R, G, B)), 1, %UnitPixel, LEFTorTOPfront)
               CALL GdipCreatePen1(zColorARGB(255, RGB(55, 60, 74)), 1, %UnitPixel, LEFTorTOPback)
               CALL GdipCreatePen1(zColorARGB(A, RGB(MAX&(R - 74, 0), MAX&(G - 84, 0), MAX&(B - 28, 0))), 1, %UnitPixel, MIDDLEfront)

               LOCAL MIDDLEback, RIGHTorBOTTOMfront, RIGHTorBOTTOMback AS LONG
               CALL GdipCreatePen1(zColorARGB(255, RGB(0, 0, 0)), 1, %UnitPixel, MIDDLEback)
               CALL GdipCreatePen1(zColorARGB(A, RGB(MIN&(R + 4,255), MAX&(G - 10, 0), MIN&(B + 28, 255))), 1, %UnitPixel, RIGHTorBOTTOMfront)
               CALL GdipCreatePen1(zColorARGB(255, RGB(87, 94, 110)), 1, %UnitPixel, RIGHTorBOTTOMback)

               IF TrackOrientation(hWnd) = %TRACK_HORZ THEN
                   y = rc.nBottom \ 2

                   IF tMin > tMax THEN
                       GdipDrawLineI(graphics, LEFTorTOPback, 0, y - 1, tx + ImgW \ 2, y - 1)
                       GdipDrawLineI(graphics, LEFTorTOPfront, tx + 1 + ImgW \ 2, y - 1, rc.nRight, y - 1)
                       GdipDrawLineI(graphics, MIDDLEback, 0, y, tx + ImgW \ 2, y)
                       GdipDrawLineI(graphics, MIDDLEfront, tx + 1 + ImgW \ 2, y, rc.nRight, y)
                       GdipDrawLineI(graphics, RIGHTorBOTTOMback, 0, y + 1, tx + ImgW \ 2, y + 1)
                       GdipDrawLineI(graphics, RIGHTorBOTTOMfront, tx + 1 + ImgW \ 2, y + 1, rc.nRight, y + 1)
                   ELSE
                       GdipDrawLineI(graphics, LEFTorTOPfront, 0, y - 1, tx + ImgW \ 2, y - 1)
                       GdipDrawLineI(graphics, LEFTorTOPback, tx + 1 + ImgW \ 2, y - 1, rc.nRight, y - 1)
                       GdipDrawLineI(graphics, MIDDLEfront, 0, y, tx + ImgW \ 2, y)
                       GdipDrawLineI(graphics, MIDDLEback, tx + 1 + ImgW \ 2, y, rc.nRight, y)
                       GdipDrawLineI(graphics, RIGHTorBOTTOMfront, 0, y + 1, tx + ImgW \ 2, y + 1)
                       GdipDrawLineI(graphics, RIGHTorBOTTOMback, tx + 1 + ImgW \ 2, y + 1, rc.nRight, y + 1)
                   END IF
               ELSE
                   x = rc.nRight \ 2

                   IF tMin > tMax THEN
                       GdipDrawLineI(graphics, LEFTorTOPback, x - 1, 0, x - 1, ty + ImgH \ 2)
                       GdipDrawLineI(graphics, LEFTorTOPfront, x - 1, ty + 1 + ImgH \ 2, x - 1, rc.nBottom)
                       GdipDrawLineI(graphics, MIDDLEback, x, 0, x, ty + ImgH \ 2)
                       GdipDrawLineI(graphics, MIDDLEfront, x, ty + 1 + ImgH \ 2, x, rc.nBottom)
                       GdipDrawLineI(graphics, RIGHTorBOTTOMback, x + 1, 0, x + 1, ty + ImgH \ 2)
                       GdipDrawLineI(graphics, RIGHTorBOTTOMfront, x + 1, ty + 1 + ImgH \ 2, x + 1, rc.nBottom)
                   ELSE
                       GdipDrawLineI(graphics, LEFTorTOPfront, x - 1, 0, x - 1, ty + ImgH \ 2)
                       GdipDrawLineI(graphics, LEFTorTOPback, x - 1, ty + 1 + ImgH \ 2, x - 1, rc.nBottom)
                       GdipDrawLineI(graphics, MIDDLEfront, x, 0, x, ty + ImgH \ 2)
                       GdipDrawLineI(graphics, MIDDLEback, x, ty + 1 + ImgH \ 2, x, rc.nBottom)
                       GdipDrawLineI(graphics, RIGHTorBOTTOMfront, x + 1, 0, x + 1, ty + ImgH \ 2)
                       GdipDrawLineI(graphics, RIGHTorBOTTOMback, x + 1, ty + 1 + ImgH \ 2, x + 1, rc.nBottom)
                   END IF
               END IF

               '// Draw thumb tracker only during %WM_PAINT
               IF Msg = %WM_PAINT THEN
                  CALL GdipDrawImageRectRectI(graphics, Img, _
                                              tx, ty, ImgW, ImgH, _
                                              0, 0, ImgW, ImgH, %UnitPixel, _
                                              ImgAttr)
               END IF

               '// Release pen resources
               GdipDeletePen(LEFTorTOPfront)
               GdipDeletePen(LEFTorTOPback)
               GdipDeletePen(MIDDLEfront)
               GdipDeletePen(MIDDLEback)
               GdipDeletePen(RIGHTorBOTTOMfront)
               GdipDeletePen(RIGHTorBOTTOMback)

               CALL GdipDeleteGraphics(graphics)
            END IF
            IF Msg = %WM_PAINT THEN
               CALL EndPaint(hWnd, ps)
            END IF
            FUNCTION = 0: EXIT FUNCTION
         END IF

    CASE %WM_DESTROY
'        // Delete the GDIPLUS thumb image
         CALL zDisposeImage(zGetProperty(hWnd, %TRACK_IMAGE))
    END SELECT

    FUNCTION = DefWindowProc(hWnd, Msg, wParam, lParam)

END FUNCTION

Because of the use of our composited double buffer, the drawing is flicker free even while resizing the main form.

3 - To compute THUMB location and current VALUE there are a few extra functions

Code: [Select]
'// Detect track bar orientation
FUNCTION TrackOrientation(BYVAL hWnd AS LONG) AS LONG
    LOCAL rc AS RECT
    CALL GetClientRect(hWnd, rc): IF rc.nRight > rc.nBottom THEN FUNCTION = %TRACK_HORZ ' -1
END FUNCTION

'// Compute X,Y thumb location based on the current tVal (value)
SUB GetThumbTrackLocation(BYVAL hWnd AS LONG, BYREF tx AS LONG, BYREF ty AS LONG)
    LOCAL rc AS RECT, increment AS DOUBLE, Img, imgW, imgH, tMin, tMax, tVal, range AS LONG

    CALL GetClientRect(hWnd, rc)

    Img  = zGetProperty(hWnd, %TRACK_IMAGE): CALL zGetImageSize(Img, imgW, imgH)
    tMin = zGetProperty(hWnd, %TRACK_MINVAL)
    tMax = zGetProperty(hWnd, %TRACK_MAXVAL)
    tVal = zGetProperty(hWnd, %TRACK_VALUE)

    IF TrackOrientation(hWnd) = %TRACK_HORZ THEN
        ty = (rc.nBottom - imgW) \ 2
        range = rc.nRight - imgW
        increment = (tMax - tMin) / range
        IF increment = 0 THEN
            tx = 0
        ELSE
            tx = (tVal - tMin) / increment
        END IF
    ELSE
        tx = (rc.nRight - imgW) \ 2 + 1
        range = rc.nBottom - imgH
        increment = (tMax - tMin) / range
        IF increment = 0 THEN
            ty = 0
        ELSE
            ty = (tVal - tMin) / increment
        END IF
    END IF
    CALL zSetProperty(hWnd, %TRACK_XY, MAKLNG(tx,ty))
END SUB

'// Update X,Y thumb location
SUB SetThumbLocation(BYVAL hWnd AS LONG, x AS LONG, y AS LONG)
    LOCAL lp AS POINTAPI, rc AS RECT, increment AS DOUBLE, Img, imgW, imgH, tx, ty, tMin, tMax, tVal, range AS LONG
    CALL GetClientRect(hWnd, rc)
    Img  = zGetProperty(hWnd, %TRACK_IMAGE): CALL zGetImageSize(Img, imgW, imgH)
    tMin = zGetProperty(hWnd, %TRACK_MINVAL)
    tMax = zGetProperty(hWnd, %TRACK_MAXVAL)

    IF TrackOrientation(hWnd) = %TRACK_HORZ THEN
       tx = MIN&(MAX&(x - imgW \ 2, 0), rc.nRight - imgW)
       ty = ((rc.nBottom - imgW ) \ 2)
       range = rc.nRight - imgW
       increment = (tMax - tMin) / range
       tVal = (increment * tx) + tMin
    ELSE
       tx = (rc.nRight - imgW) \ 2 + 1
       ty = MIN&(MAX&(y - imgH \ 2, 0), rc.nBottom - imgH)
       range = rc.nBottom - imgH
       increment = (tMax - tMin) / range
       tVal = (increment * ty) + tMin
    END IF
    CALL zSetProperty(hWnd, %TRACK_VALUE, tVal)
    CALL zSetProperty(hWnd, %TRACK_XY, MAKLNG(tx,ty))

    IF tVal <> zGetProperty(hWnd, %TRACK_WAS_VALUE) THEN
       CALL zUpdateWindow(hWnd, 1)
       CALL zSetToolTipText(hWnd, (LTRIM$(STR$(tVal))))
    END IF
    CALL zSetProperty(hWnd, %TRACK_WAS_VALUE, tVal)

END SUB

'// Detect if mouse cursor hover track thumb
FUNCTION CheckOverThumb(BYVAL hWnd AS LONG, BYVAL x AS LONG, y AS LONG) AS LONG
    LOCAL r AS RECT, tx, ty, Img, imgW, imgH AS LONG, lp AS POINTAPI
    tx = LOWRD(zGetProperty(hWnd, %TRACK_XY))
    ty = HIWRD(zGetProperty(hWnd, %TRACK_XY))
    Img  = zGetProperty(hWnd, %TRACK_IMAGE): CALL zGetImageSize(Img, imgW, imgH)
    CALL GetClientRect(hWnd, r)
    CALL SetRect(r, tx, ty, tx + imgW, ty + imgH)
    FUNCTION = PtInRect(r, x, y)
END FUNCTION


For performance issue, the Min Max value and default value must use LONG exclusively, if you need to work with SINGLE (REAL on 4-bit) or DOUBLE (REAL on 8-bit) then you must first convert them to LONG like this:
rMin is real on 4-bit = 12335.44
nMin is int on 4-bit = CLNG(rMin * 100)
And of course reverse the process when you get the WM_COMMAND value notification.

ToolTip
In case of tooltip, the control displays the current value while dragging the track's thumb.

Keyboard support
So far, because I was a little lazy, I didn't put the keyboard handling, that's up to you if you need it ...

And now the screen shot of the result:



I hope you begin to see the unleash power of SDK, over anything else ;)

More to come...

« Last Edit: February 09, 2020, 05:54:27 pm by Patrice Terrier »
Patrice
(Always working with the latest Windows version available...)

Patrice Terrier

  • Administrator
  • *****
  • Posts: 1991
    • zapsolution
Re: [SDK] 08 - Take control of your TRACKBAR
« Reply #1 on: February 09, 2020, 06:19:30 pm »
.
Patrice
(Always working with the latest Windows version available...)