News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Painting a control background: almost, but not quite

Started by NoCforMe, July 10, 2012, 04:09:49 AM

Previous topic - Next topic

NoCforMe

I'm putting some controls (trackbars and comboboxes) in a program window (not a dialog) which has a non-standard background (my preference), and I wanted to make them look less ugly by painting their backgrounds to match the client area.

Figured out a really simple way to do this, which works--almost. But there are some problems.

The method I hit on was to look for the WM_CTLCOLORSTATIC message (in the control's parent's window procedure); on receipt, all you need to do is return a handle to a brush to paint the background. Two instructions--really simple:


; All we need to do here (on receipt of a WM_CTLCOLORSTATIC message)
; is return a handle to the desired brush for the background:

do_bkgnd:
MOV EAX, BackgroundBrush
RET


How did I come up with that message? I added some code to capture all messages from the window proc with the control. This was the only message that could possibly pertain to  painting a control's background (kind of strange, though, that it was the WM_CTLCOLORSTATIC message, which MSDN says is supposed to be sent for static text controls or edit controls that are read-only or disabled).

It works fine, as you can see from attached little program. The control background gets painted OK. However, as soon as one clicks on the control to move the slider, an ugly rectangle appears around the control. WTF?????

Anyone know why this happens? My thoughts are 1) there's a gap between the background rectangle of the control and the area of the client which doesn't get painted (or gets painted improperly): not likely, but possible; 2) there's a "drawing stage" where the boundary of the control window gets painted that I'm not handling, or 3) something else entirely.

Here's the complete program:


;============================================
; -- Trackbar tests (messages, etc.) --
;
;============================================


include \masm32\include\masm32rt.inc


;============================================
; Defines, macros, prototypes, etc.
;============================================

WinMain PROTO :DWORD

$mainWindowWidth EQU 500
$mainWindowHeight EQU 300

$X EQU 100
$Y EQU 100
$Width EQU 150
$Height EQU 40

;===== Window styles: =====
$mainWinStyles EQU WS_OVERLAPPEDWINDOW OR WS_CLIPCHILDREN OR WS_VISIBLE
$trackbarStyles EQU TBS_AUTOTICKS OR TBS_BOTTOM OR TBS_HORZ OR WS_CHILD OR WS_VISIBLE

;===== Window background colors: =====
$bkRED EQU 254
$bkGRN EQU 243
$bkBLUE EQU 199

$BkColor EQU $bkRED OR ($bkGRN SHL 8) OR ($bkBLUE SHL 16)


;============================================
; HERE BE DATA
;============================================
.data

InstanceHandle HWND ?
MainWinHandle HWND ?
BackgroundBrush HBRUSH ?

MainClassName DB "test", 0
TrackbarClassName DB "msctls_trackbar32", 0

MainTitleText DB "Trackbar Test", 0


;============================================
; CODE LIVES HERE
;============================================
.code


start:
INVOKE GetModuleHandle, NULL
MOV InstanceHandle, EAX

INVOKE WinMain, EAX
INVOKE ExitProcess, EAX


;====================================================================
; Mainline proc
;====================================================================

WinMain PROC hInst:DWORD
LOCAL wc:WNDCLASSEX, msg:MSG, wX:DWORD, wY:DWORD


; Create  brush to set background color:
INVOKE CreateSolidBrush, $BkColor
MOV BackgroundBrush, EAX

; Register class for parent window:
MOV wc.cbSize, SIZEOF WNDCLASSEX
MOV wc.style,  CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW
MOV wc.lpfnWndProc, OFFSET MainWindowProc
MOV wc.cbClsExtra, NULL
MOV wc.cbWndExtra, NULL
MOV EAX, hInst
MOV wc.hInstance, EAX
MOV EAX, BackgroundBrush
MOV wc.hbrBackground, EAX
MOV wc.lpszMenuName,  NULL
MOV wc.lpszClassName, OFFSET MainClassName
MOV wc.hIcon, NULL
INVOKE LoadCursor, NULL, IDC_ARROW
MOV wc.hCursor, EAX
MOV wc.hIconSm, 0
INVOKE RegisterClassEx, ADDR wc

INVOKE GetSystemMetrics, SM_CXSCREEN
MOV EDX, $mainWindowWidth
CALL CenterDim
MOV wX, EAX
INVOKE GetSystemMetrics, SM_CYSCREEN
MOV EDX, $mainWindowHeight
CALL CenterDim
MOV wY, EAX

; Create our main window:
INVOKE CreateWindowEx, WS_EX_OVERLAPPEDWINDOW, ADDR MainClassName,
ADDR MainTitleText, $mainWinStyles, wX, wY, $mainWindowWidth,
$mainWindowHeight, NULL, NULL, hInst, NULL
MOV MainWinHandle, EAX

; Create trackbar:
INVOKE CreateWindowEx, WS_EX_LEFT, ADDR TrackbarClassName,
NULL, $trackbarStyles, $X, $Y, $Width,
$Height, MainWinHandle, NULL, hInst, NULL

;============= Message loop ===================
msgloop:
INVOKE GetMessage, ADDR msg, NULL, 0, 0
OR EAX, EAX ;EAX = 0 = exit
JZ exit
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP msgloop

exit: MOV EAX, msg.wParam
RET

;===== This code doesn't actually need to be executed: =====
INVOKE InitCommonControls

WinMain ENDP


;====================================================================
; Main Window Proc
;====================================================================

MainWindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD

MOV EAX, uMsg
CMP EAX, WM_CTLCOLORSTATIC
JE do_bkgnd
CMP EAX, WM_CLOSE
JE do_close

dodefault:
; use DefWindowProc for all other messages:
INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET

; All we need to do here (on receipt of a WM_CTLCOLORSTATIC message)
; is return a handle to the desired brush for the background:

do_bkgnd:
MOV EAX, BackgroundBrush
RET

do_close:
INVOKE PostQuitMessage, NULL
MOV EAX, TRUE
RET


MainWindowProc ENDP


;====================================================================
; CenterDim
;
; Returns half screen dimension (X or Y) minus half window dimension
;
; On entry,
; EAX = screen dimension
; EDX = window dimension
;
; Returns:
; sdim/2 - wdim/2
;====================================================================

CenterDim PROC

SHR EAX, 1 ;divide screen dimension by 2
SHR EDX, 1 ;divide window dimension by 2
SUB EAX, EDX
RET ;Return w/# in EAX

CenterDim ENDP


END start


jj2007

Apparently the control gets the focus. Try catching the focus messages, or maybe WM_NCPAINT.

By the way, exit is not a good name for a label, as it conflicts with the exit macro. You have probably modified that in your Masm32 installation, but us normal people here see an error message every time you post something with jz exit.

NoCforMe

Quote from: jj2007 on July 10, 2012, 04:19:09 AM
By the way, exit is not a good name for a label, as it conflicts with the exit macro. You have probably modified that in your Masm32 installation, but us normal people here see an error message every time you post something with jz exit.

Want to know where I got that label from, jj? Copied it straight out of one of the example files when I first downloaded MASM32 and set it up. Put it in the template file I use to create programs and forgot about it.

Thing is, I don't use macros (at least not the MASM32 ones), so I don't really care. Maybe I'll get around to changing it someday ...

Regarding WM_SETFOCUS: not sure what I would do if I handled that message: repaint the focus rectangle?

I'll have to look into WM_NCPAINT.

NoCforMe

WM_NCPAINT: oy vey, what a mess! Check out these comments from MSDN's own page on this message:

Quote
wParam doesn't contain Region at all
You have to clip nonclient area with GetClientRect and ExcludeClipRect yourself.

Quote
When wParam == 1, entire NC area has to be updated.

Quote
Getting the device context when handling WM_NCPAINT does not work as described. Additional undocumented flag 0x10000 has to be used:

case WM_NCPAINT:
{
    HDC hdc;
    hdc = GetDCEx(hwnd, (HRGN)wParam, DCX_WINDOW | DCX_INTERSECTRGN | 0x10000);
    // Paint into this DC
    ReleaseDC(hwnd, hdc);
}

(Verified on Windows XP)

Any other brilliant ideas? Plus, how do you know whether WM_NCPAINT is intended for the children (controls) or the parent window itself? Comparing window handles?

dedndave

WM_SETFOCUS is sent to the control (obviously)
but WM_KILLFOCUS is also sent to the window that looses focus
you might use that to regain focus by using SetFocus or SetForegroundWindow

another way to fly might be to subclass the controls
modify the behaviour so they don't get drawn in the first place
you might do that by subplanting the WM_SETFOCUS message
or by altering the appearance for the active state

if that is the only subclass you have going on, it is possible to use the same proc for different types of controls
i.e., you'd only have one proc to eliminate the lines for several control types

"but, i have to subclass all those controls ?" - you say
make a routine that creates the control and subclasses it in one shot

qWord

hi,
this seems to work:
;============================================
; -- Trackbar tests (messages, etc.) --
;
;============================================


include \masm32\include\masm32rt.inc


;============================================
; Defines, macros, prototypes, etc.
;============================================

WinMain PROTO :DWORD

$mainWindowWidth EQU 500
$mainWindowHeight EQU 300

$X EQU 100
$Y EQU 100
$Width EQU 150
$Height EQU 40

;===== Window styles: =====
$mainWinStyles EQU WS_OVERLAPPEDWINDOW OR WS_CLIPCHILDREN OR WS_VISIBLE
$trackbarStyles EQU TBS_AUTOTICKS OR TBS_BOTTOM OR TBS_HORZ OR WS_CHILD OR WS_VISIBLE

;===== Window background colors: =====
$bkRED EQU 254
$bkGRN EQU 243
$bkBLUE EQU 199

$BkColor EQU $bkRED OR ($bkGRN SHL 8) OR ($bkBLUE SHL 16)


;============================================
; HERE BE DATA
;============================================
.data

InstanceHandle HWND ?
MainWinHandle HWND ?
BackgroundBrush HBRUSH ?

MainClassName DB "test", 0
TrackbarClassName DB "msctls_trackbar32", 0

MainTitleText DB "Trackbar Test", 0


;============================================
; CODE LIVES HERE
;============================================
.code


start:
INVOKE GetModuleHandle, NULL
MOV InstanceHandle, EAX

INVOKE WinMain, EAX
INVOKE ExitProcess, EAX


SubClass proc uses esi edi ebx hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM

mov edi,rv(GetProp,hWnd,"pOrgWndProc")
.if uMsg == WM_SETFOCUS
invoke SendMessage,hWnd,WM_UPDATEUISTATE,UIS_SET OR (UISF_HIDEFOCUS SHL 16),0
.endif
invoke CallWindowProc,edi,hWnd,uMsg,wParam,lParam
ret

SubClass endp

;====================================================================
; Mainline proc
;====================================================================

WinMain PROC hInst:DWORD
LOCAL wc:WNDCLASSEX, msg:MSG, wX:DWORD, wY:DWORD


; Create  brush to set background color:
INVOKE CreateSolidBrush, $BkColor
MOV BackgroundBrush, EAX

; Register class for parent window:
MOV wc.cbSize, SIZEOF WNDCLASSEX
MOV wc.style,  CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW
MOV wc.lpfnWndProc, OFFSET MainWindowProc
MOV wc.cbClsExtra, NULL
MOV wc.cbWndExtra, NULL
MOV EAX, hInst
MOV wc.hInstance, EAX
MOV EAX, BackgroundBrush
MOV wc.hbrBackground, EAX
MOV wc.lpszMenuName,  NULL
MOV wc.lpszClassName, OFFSET MainClassName
MOV wc.hIcon, NULL
INVOKE LoadCursor, NULL, IDC_ARROW
MOV wc.hCursor, EAX
MOV wc.hIconSm, 0
INVOKE RegisterClassEx, ADDR wc

INVOKE GetSystemMetrics, SM_CXSCREEN
MOV EDX, $mainWindowWidth
CALL CenterDim
MOV wX, EAX
INVOKE GetSystemMetrics, SM_CYSCREEN
MOV EDX, $mainWindowHeight
CALL CenterDim
MOV wY, EAX

; Create our main window:
INVOKE CreateWindowEx, WS_EX_OVERLAPPEDWINDOW, ADDR MainClassName,
ADDR MainTitleText, $mainWinStyles, wX, wY, $mainWindowWidth,
$mainWindowHeight, NULL, NULL, hInst, NULL
MOV MainWinHandle, EAX

; Create trackbar:
INVOKE CreateWindowEx, WS_EX_LEFT, ADDR TrackbarClassName,
NULL, $trackbarStyles, $X, $Y, $Width,
$Height, MainWinHandle, NULL, hInst, NULL
mov edi,eax
fn SetProp,edi,"pOrgWndProc",rv(GetWindowLong,edi,GWL_WNDPROC)
invoke SetWindowLong,edi,GWL_WNDPROC,ADDR SubClass

;============= Message loop ===================
msgloop:
INVOKE GetMessage, ADDR msg, NULL, 0, 0
OR EAX, EAX ;EAX = 0 = exit
JZ ex
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
JMP msgloop

ex: MOV EAX, msg.wParam
RET

;===== This code doesn't actually need to be executed: =====
INVOKE InitCommonControls

WinMain ENDP


;====================================================================
; Main Window Proc
;====================================================================

MainWindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD

MOV EAX, uMsg
CMP EAX, WM_CTLCOLORSTATIC
JE do_bkgnd
CMP EAX, WM_CLOSE
JE do_close

dodefault:
; use DefWindowProc for all other messages:
INVOKE DefWindowProc, hWin, uMsg, wParam, lParam
RET

; All we need to do here (on receipt of a WM_CTLCOLORSTATIC message)
; is return a handle to the desired brush for the background:

do_bkgnd:
MOV EAX, BackgroundBrush
RET

do_close:
INVOKE PostQuitMessage, NULL
MOV EAX, TRUE
RET


MainWindowProc ENDP


;====================================================================
; CenterDim
;
; Returns half screen dimension (X or Y) minus half window dimension
;
; On entry,
; EAX = screen dimension
; EDX = window dimension
;
; Returns:
; sdim/2 - wdim/2
;====================================================================

CenterDim PROC

SHR EAX, 1 ;divide screen dimension by 2
SHR EDX, 1 ;divide window dimension by 2
SUB EAX, EDX
RET ;Return w/# in EAX

CenterDim ENDP


END start
MREAL macros - when you need floating point arithmetic while assembling!

NoCforMe

Thanks to all who replied.

You know what? After thinking about it some more, I'm just going to leave well enough alone and not worry about it.

I added another trackbar control, and it works as any user would expect it to: when you click on one, you get a focus rectangle to indicate the control is active. Click on the other one, and the focus rectangle follows your click. That's the way the GUI's 'spozed to work.

I just need to adjust the size of the controls so the outline doesn't look so funky.

Remember that great principle: KISS.

dedndave

 :t

it's a hell of a lot easier to code, too   :biggrin:

NoCforMe

Hells yeah: 4 lines of code:


CMP EAX, WM_CTLCOLORSTATIC
JE do_bkgnd

[...]

do_bkgnd:
MOV EAX, BackgroundBrush
RET