WORK IN PROGRESSThis is the second post of a serie, where I shall try to explain how to take complete control over SDI window.
1 - SUPERCLASS.This is close to the oop concept where a new class can inherit from the properties of another one, to take advange of its existing features, while adding new properties or changing existing behaviors.
In this example we will create a new "
ZBUTIMAGE" button class, that inherits from the default "
BUTTON" class.
'// Create an image button control
FUNCTION zButImage (BYVAL hOwner AS LONG, zFullpathImageName AS ASCIIZ, BYVAL x AS LONG, BYVAL y AS LONG, BYVAL ButID AS LONG) AS LONG
LOCAL wc AS WNDCLASSEX
LOCAL zClass AS ASCIIZ * 10
LOCAL hBut, IsInitialized AS LONG
zClass = "ZBUTIMAGE"
wc.cbSize = SIZEOF(wc)
IsInitialized = GetClassInfoEx(zInstance, zClass, wc)
IF IsInitialized = 0 THEN
IsInitialized = GetClassInfoEx(%NULL, "BUTTON", wc)
IF IsInitialized THEN
CALL zButOldProc(wc.lpfnWndProc, 1)
CALL zButOldExtra(wc.cbWndExtra, 1)
wc.lpfnWndProc = CODEPTR(zImageButProc)
'// %EXTEND_EXTRA
'// We reserve %EXTEND_EXTRA bytes to easily store pivate properties
'// that can be retrieved using: zSetButProperty and zGetButProperty
wc.cbWndExtra = wc.cbWndExtra + (%EXTEND_EXTRA * 4)
wc.hInstance = zInstance
wc.lpszClassName = VARPTR(zClass)
IsInitialized = RegisterClassEx(wc)
END IF
END IF
IF IsInitialized THEN
LOCAL Img AS LONG
' // Create GDIPLUS image from file
Img = zCreateImageFromFile(zFullpathImageName)
IF Img THEN
LOCAL btW, btH, imgW, imgH AS LONG
' // Get the GDIPLUS image size
CALL zGetImageSize(Img, imgW, imgH)
btW = imgW \ 5: btH = imgH
Style& = %WS_CHILD OR %WS_VISIBLE OR %WS_TABSTOP ' OR %BS_OWNERDRAW
hBut = CreateWindowEx(%WS_EX_TRANSPARENT, zClass, "", Style&, x, y, btW, btH, hOwner, ButID, zInstance, BYVAL %NULL)
IF hBut THEN
' // Save Img handle as a property
CALL zSetButProperty(hBut, %IMAGE_EXTRA, Img)
FUNCTION = hBut
ELSE
' // Delete image
CALL zDisposeImage(Img)
END IF
END IF
END IF
END FUNCTION
Note that we extend the size of the
wc.cbWndExtra member in order to use it to store private properties for each new
ZBUTIMAGE.
'// Get button's property.
FUNCTION zGetButProperty (BYVAL hWnd AS LONG, BYVAL Item AS LONG) AS LONG
IF Item > 0 AND Item < %EXTEND_EXTRA + 1 AND IsWindow(hWnd) THEN
FUNCTION = GetWindowLong(hWnd, (zButOldExtra(0, 0) + Item - 1) * 4)
END IF
END FUNCTION
'// Set button's property.
SUB zSetButProperty (BYVAL hWnd AS LONG, BYVAL Item AS LONG, BYVAL V AS LONG)
IF Item > 0 AND Item < %EXTEND_EXTRA + 1 AND IsWindow(hWnd) THEN
CALL SetWindowLong(hWnd, (zButOldExtra(0, 0) + Item - 1) * 4, V)
END IF
END SUB
2 - FIVE-STATE button.Also because the ultimate purpose of this project is to show you how to create a complete skinned window, we use PNG files to customize the look of the button with the help of GDIPLUS.
Note: that each of the button uses 5 states like this one
State 1 - Normal
State 2 - Pressed
State 3 - Disabled
State 4 - Focus
State 5 - Hover
3 - DOUBLE BUFFER and WM_PRINT message.Most programmers do not handle the WM_PRINT and WM_PRINCLIENT messages, however they are very powerful, and we use them to paint the control in our private DC, aka DOUBLE BUFFER, to boost the display and suppress flickering while moving child controls during resize of the main window.
'// Double bufferFUNCTION zPaintToScreen (BYVAL hWnd AS LONG, BYVAL hDC AS LONG, BYVAL X AS LONG, BYVAL Y AS LONG, BYVAL Action AS LONG) AS LONG
LOCAL rc AS RECT
LOCAL bmW, bmH, ExistBitmap, hDCTemp, hPaintBitmap AS LONG
CALL GetClientRect(hWnd, rc)
IF Action THEN
' Create OFF screen bitmap to avoid flickering and allow smooth display
' ---------------------------------------------------------------------
ExistBitmap = zGetPaintBitmap(hWnd)
IF ExistBitmap THEN
CALL zGetBitmapSize(ExistBitmap, bmW, bmH)
IF MAX&(rc.nRight, X) < bmW + 1 AND MAX&(rc.nBottom, Y) < bmH + 1 THEN
FUNCTION = zGetPaintDC(hWnd): EXIT FUNCTION
END IF
CALL zDeleteObject(ExistBitmap)
CALL DeleteDC(zGetPaintDC(hWnd))
END IF
hDCTemp = CreateCompatibleDC(hDC)
hPaintBitmap = zCreateDIBSection(hDC, MAX&(rc.nRight, X), MAX&(rc.nBottom, Y), 24)
CALL SelectObject(hDCTemp, hPaintBitmap)
CALL zSetProperty(hWnd, %Z_PaintDC, hDCTemp)
CALL zSetProperty(hWnd, %Z_PaintBitmap, hPaintBitmap)
FUNCTION = hDCTemp
ELSE
' End of OFF screen drawing, fast BitBlt to the target display Window
' -------------------------------------------------------------------
' Draw final result to the target window
CALL BitBlt(hDC, X, Y, rc.nRight, rc.nBottom, zGetPaintDC(hWnd), 0, 0, %SRCCOPY)
END IF
END FUNCTION
CALL SendMessage(hMain,
%WM_PRINT, hDCmem, lParam&)
'// Draw our custom background there
SUB zDrawBackground()
LOCAL hMain AS LONG, hIC AS LONG, hDCmem AS LONG, rc AS RECT
hMain = zMainWindow(0)
CALL GetClientRect(hMain, rc)
hIC = zDisplayDC()
hDCmem = zPaintToScreen(hMain, hIC, rc.nRight, rc.nBottom, 1)
CALL zFillRect(hDCmem, 0, 0, rc.nRight, rc.nBottom, RGB(27,29,41))
CALL DrawEdge(hDCmem, rc, %EDGE_RAISED, %BF_RECT)
' // WP_PRINT and WM_PRINCLIENT is very valuable for double buffering technic
'lParam& = %PRF_CLIENT OR %PRF_CHILDREN OR %PRF_NONCLIENT OR %PRF_ERASEBKGND OR %PRF_CHECKVISIBLE OR %PRF_OWNED
lParam& = %PRF_CLIENT OR %PRF_CHILDREN OR %PRF_NONCLIENT OR %PRF_CHECKVISIBLE 'OR %PRF_OWNED
CALL SendMessage(hMain, %WM_PRINT, hDCmem, lParam&)
CALL DeleteDC(hIC)
END SUB
CASE %WM_PAINT,
%WM_PRINT CASE %WM_PAINT, %WM_PRINT
Img = zGetButProperty(hWnd, %IMAGE_EXTRA)
IF Img THEN
LOCAL ps AS PAINTSTRUCT
IF Msg = %WM_PAINT THEN
hDC = BeginPaint(hWnd, ps)
ELSE ' // WM_PRINT
hDC = wParam
END IF
IF GdipCreateFromHDC(hDC, graphics) = 0 THEN
CALL GetWindowRect(hWnd, rw)
CALL GetClientRect(hWnd, rc)
lp.x = rw.nLeft: lp.y = rw.nTop
CALL ScreenToClient(GetParent(hWnd), lp)
CALL zGetImageSize(Img, ImgW, ImgH)
ImgW = ImgW \ 5
IF IsWindowEnabled(hWnd) THEN
UseState = 1: IF hWnd = GetFocus() THEN UseState = 4
CALL GetCursorPos(lp)
CALL ScreenToClient(hWnd, lp)
IF PtInRect(rc, lp.x, lp.y) THEN
IF zIsLButtonDown() THEN
IF hWnd = GetFocus() THEN
IF GetCursor() = LoadCursor(%NULL, BYVAL %IDC_ARROW) THEN UseState = 2
END IF
ELSE
UseState = 5
END IF
END IF
ELSE
UseState = 3
END IF
CALL GdipDrawImageRectRectI(graphics, Img, _
0, 0, ImgW, ImgH, _
ImgW * UseState - ImgW, 0, ImgW, ImgH, %UnitPixel, _
ImgAttr)
CALL GdipDeleteGraphics(graphics)
END IF
IF Msg = %WM_PAINT THEN
CALL EndPaint(hWnd, ps)
END IF
FUNCTION = 0: EXIT FUNCTION
END IF
So far the window itself doesn't do much, however it has everything to resize, drag, iconize, maximize, restore, close.
Next step:
we will add the caption bar, and a new SUPERCLASS for PUSBUTTON and perhaps ...
start skinning the form (borders)
To be continued...