On a side note, I, for one, was very interested in seeing things done the 'API way' here, using lparam, wparam etc. I think this could make a very interesting mini-tutorial: Create a very simple app with one or more windows, some buttons and possibly other gadgets without using the "traditional" PB methods. Most likely it would be Windows only, unless someone would also like to do one for Linux and/or MacOsX.
This info could be useful for when we want/need to do somethng that PB does not natively support (yet).
Anyone else?
Russell
Transparent Gadgets
*** Diapers and politicians need to be changed...for the same reason! ***
*** Make every vote equal: Abolish the Electoral College ***
*** www.au.org ***
*** Make every vote equal: Abolish the Electoral College ***
*** www.au.org ***
- grabiller
- Enthusiast
- Posts: 309
- Joined: Wed Jun 01, 2011 9:38 am
- Location: France - 89220 Rogny-Les-Septs-Ecluses
- Contact:
Re:
Hi,
Any idea why this does not work for Buttons, Edit controls, etc.. ?
When doing:
SendMessage_(GadgetID(#ListView_1), #WM_SETREDRAW, 0, 0)
For the listview it works, but for other types of control (button, edit, etc..) we don't even recieve #WM_PAINT nor #WM_ERASEBKGND !?
How we are supposed to apply this way of handling custom drawing for other types of controls then ?
Thanks in advance for any help.
Cheers,
Guy.
Any idea why this does not work for Buttons, Edit controls, etc.. ?
When doing:
SendMessage_(GadgetID(#ListView_1), #WM_SETREDRAW, 0, 0)
For the listview it works, but for other types of control (button, edit, etc..) we don't even recieve #WM_PAINT nor #WM_ERASEBKGND !?
How we are supposed to apply this way of handling custom drawing for other types of controls then ?
Thanks in advance for any help.
Cheers,
Guy.
Sparkie wrote:You are very welcome dell_jockey
This code is 100% flicker free for me. I used a different approach here by handling more of the ListViewGadget redraw, which reduces the flicker as well as giving you better control for customizing the output.
Code: Select all
;/========================================================= ;/ Code : Transparent ListViewgadget ;/ Author : Sparkie ;/ Rel Date : 04/16/06 ;/ PB Version : PB 4.00 Beta10 ;/ OS Support : Windows 98/NT/ME/2000/XP/Server 2003 ;/========================================================= ;/=============================================== ;/ Globals ;/=============================================== Global oldCallback, winW, winH UseJPEGImageDecoder() UsePNGImageDecoder() ;/=============================================== ;/ Constants / Enumerations ;/=============================================== #Window_Main = 1 #ListView_1 = 1 #Text_Display = 2 #Image_Lv = 1 #Image_Win = 2 ;/=============================================== ;/ Procedure: Proportional image resizing ;/=============================================== Procedure.l ImageSizer(imageIs.l, imgW.f, imgH.f) mainWidth.f = ImageWidth(imageIs) mainHeight.f = ImageHeight(imageIs) percentageW.f = mainWidth / imgW percentageH.f = mainHeight /imgH If percentageW > percentageH Or percentageW = percentageH percentageWH.f = percentageW ElseIf percentageH > percentageW percentageWH.f = percentageH EndIf newWidth = mainWidth / percentageWH newHeight = mainHeight / percentageWH newImage = ResizeImage(imageIs, newWidth, newHeight) ProcedureReturn newImage EndProcedure ;/=============================================== ;/ Procedure: Create Main window background brush ;/=============================================== Procedure CreateWindowBrush(img$) ;...Load our selected image for window background bgImage = LoadImage(#Image_Win, img$) If bgImage ;...Resize image if it's too small for our use If ImageWidth(#Image_Win) > 640 Or ImageHeight(#Image_Win) > 480 sizedImage = ImageSizer(#Image_Win, 640, 480) ElseIf ImageWidth(#Image_Win) < 320 Or ImageHeight(#Image_Win) > 240 sizedImage = ImageSizer(#Image_Win, 320, 240) EndIf ;...Create brush for window background hBrush = CreatePatternBrush_(sizedImage) imgW = ImageWidth(#Image_Win) ;...Window size will = 300 if image size < 300 ;...or else Window size will = image size if image size >= 300 If imgW < 300 winW = 300 Else winW = ImageWidth(#Image_Win) EndIf imgH = ImageHeight(#Image_Win) If imgH < 250 winH = 250 Else winH = ImageHeight(#Image_Win) EndIf If hBrush result = hBrush Else MessageRequester("Error", "Could not create brush", #MB_ICONERROR) result = 0 EndIf Else MessageRequester("Error", "Could not load image", #MB_ICONERROR) result = 0 EndIf ProcedureReturn result EndProcedure ;/=============================================== ;/ Procedure: Create Main window background brush ;/=============================================== Procedure.l GetImageBG() bgImage$ = OpenFileRequester("Choose Background Image", "c:\Documents And Settings\Owner\My Documents\My Pictures\", "Image Files (BMP, JPG, PNG)|*.BMP;*.jpg;*.png", 0) ;bgImage$ = "C:\Documents And Settings\Owner\My Documents\My Pictures\100_4753.JPG" If bgImage$ hBrush = CreateWindowBrush(bgImage$) Else ;...No image selected. End End EndIf ProcedureReturn hBrush EndProcedure ;/=============================================== ;/ Procedure: Do Painting of ListViewGadget ;/=============================================== Procedure PaintIt(hwnd) Static previousScrollPos ;...Get ListView DC lvDc = GetDC_(hwnd) ;...Get image DC bgDc = StartDrawing(ImageOutput(#Image_Lv)) ;...Get gadget# gadId = GetDlgCtrlID_(hwnd) ;...Get first visible item firstItem = SendMessage_(hwnd, #LB_GETTOPINDEX, 0, 0) ;...Get total number of items totalItems = SendMessage_(hwnd, #LB_GETCOUNT, 0, 0) - 1 ;...Get client rect for ListViewGadget GetClientRect_(hwnd, @gadRc.RECT) ;...Determine border size isBorder = GetWindowLong_(GadgetID(gadId), #GWL_EXSTYLE) & #WS_EX_CLIENTEDGE If isBorder border = GetSystemMetrics_(#SM_CYEDGE) Else border = 0 EndIf ;...Draw fresh background, compensating for gadget x/y position ;...The source image is our window background image DrawImage(ImageID(#Image_Win), 0 - GadgetX(gadId) - border, 0 - GadgetY(gadId) - border) ;...Draw new text if visible For i = firstItem To totalItems SendMessage_(hwnd, #LB_GETITEMRECT, i, @itemRc.RECT) If itemRc\bottom <= gadRc\bottom ;...Get item text and state itemText$ = GetGadgetItemText(gadId, i, 0) itemState = GetGadgetItemState(gadId, i) ;...Set drawing mode for text and focus rect DrawingMode(#PB_2DDrawing_Outlined | #PB_2DDrawing_Transparent) If itemState = 0 ;...Text color for non-selected items currentTextColor = RGB(255, 0, 0) ;...Create a 5 px left margin itemRc\left + 5 ;...Draw selected text DrawText(itemRc\left, itemRc\top, itemText$, currentTextColor) Else ;...Text color for selected items currentTextColor = RGB(0, 0, 255) ;...Create a 5 px left margin itemRc\left + 5 ;...Draw selected text DrawText(itemRc\left, itemRc\top, itemText$, currentTextColor) ;...Reset margin itemRc\left - 5 ;...Draw our focus rect Box(itemRc\left, itemRc\top, itemRc\right - itemRc\left, itemRc\bottom - itemRc\top, #White) EndIf Else Break EndIf Next i ;...Eliminate border and scrollbar area from BitBlt clientW = gadRc\right - gadRc\left clientH = gadRc\bottom - gadRc\top ;...Copy our background image onto ListViewGadget DC BitBlt_(lvDc, 0, 0, clientW, clientH, bgDc, 0, 0, #SRCCOPY) ;...Clean up ReleaseDC_(hwnd, lvDc) StopDrawing() ;...Redraw scrollbar position as needed si.SCROLLINFO\cbSize = SizeOf(SCROLLINFO) si\fMask = #SIF_POS GetScrollInfo_(hwnd, #SB_VERT, @si) If si\nPos <> previousScrollPos SetScrollInfo_(hwnd, #SB_VERT, @si, #True) EndIf previousScrollPos = si\nPos EndProcedure ;/=============================================== ;/ Procedure: ListViewGadget Callback ;/=============================================== Procedure LVcallback(hwnd, msg, wParam, lParam) result = CallWindowProc_(oldCallback, hwnd, msg, wParam, lParam) doPaint = #False Select msg Case #WM_KEYDOWN If wParam = #VK_DOWN Or wParam = #VK_UP Or wParam = #VK_NEXT Or wParam = #VK_PRIOR Or wParam = #VK_HOME Or wParam = #VK_END doPaint = #True EndIf Case #WM_LBUTTONDOWN doPaint = #True result = 0 Case #WM_MOUSEMOVE If wParam <> 0 doPaint = #True result = 0 EndIf Case #WM_ERASEBKGND doPaint = #True result = 1 Case #WM_PAINT doPaint = #False result = 0 Case #WM_VSCROLL doPaint = #True result = 0 Case #WM_MOUSEWHEEL doPaint = #True result = 0 EndSelect ;...Redraw the ListViewGadget as needed If doPaint PaintIt(hwnd) EndIf ProcedureReturn result EndProcedure ;/=============================================== ;/ Create Main window and gadgets ;/=============================================== ;...Get image to use for window background hWinBrush = GetImageBG() If OpenWindow(#Window_Main, 0, 0, winW, winH, "Transparent ListViewGadget", #PB_Window_SizeGadget | #PB_Window_SystemMenu | #PB_Window_ScreenCentered) And CreateGadgetList(WindowID(#Window_Main)) ;...Set our window background image SetClassLong_(WindowID(#Window_Main), #GCL_HBRBACKGROUND, hWinBrush) ;...Ownerdraw our ListViewGadget ListViewGadget(#ListView_1, 20, 20, 200, winH - 40, #LBS_OWNERDRAWFIXED) ;...Optional: Un-comment next line to remove the ListView border ;SetWindowLong_(GadgetID(#ListView_1), #GWL_EXSTYLE, GetWindowLong_(GadgetID(#ListView_1), #GWL_EXSTYLE) & ~#WS_EX_CLIENTEDGE) ;...Add some items For i = 0 To 99 AddGadgetItem (#ListView_1, -1, "Item " + Str(i) + " of the Listview") Next i ;...Create our ListView background image GetClientRect_(GadgetID(#ListView_1), @lvRc.RECT) CreateImage(#Image_Lv, lvRc\right - lvRc\left, lvRc\bottom - lvRc\top) SetGadgetState(#ListView_1, 1) ;...Disable system drawing for ListViewGadget. We'll handle it ourselves. SendMessage_(GadgetID(#ListView_1), #WM_SETREDRAW, 0, 0) ;...Subclass the ListViewGadget oldCallback = SetWindowLong_(GadgetID(#ListView_1), #GWL_WNDPROC, @LVcallback()) ;/=============================================== ;/ Main window event loop ;/=============================================== previousWinWidth = WindowWidth(#Window_Main) previousWinHeight = WindowHeight(#Window_Main) Repeat event = WaitWindowEvent() If event = #PB_Event_SizeWindow ;...Resize ListViewGadget sizeChangeW = WindowWidth(#Window_Main) - previousWinWidth sizeChangeH = WindowHeight(#Window_Main) - previousWinHeight gadWidth = GadgetWidth(#ListView_1) + sizeChangeW gadHeight = GadgetHeight(#ListView_1) + sizeChangeH ResizeGadget(#ListView_1, #PB_Ignore, #PB_Ignore, gadWidth, gadHeight) ;...Resize our background drawing image to match window size ResizeImage(#Image_Lv, WindowWidth(#Window_Main), WindowHeight(#Window_Main)) previousWinWidth = WindowWidth(#Window_Main) previousWinHeight = WindowHeight(#Window_Main) EndIf Until event = #PB_Event_CloseWindow ;...Clean-up If hWinBrush DeleteObject_(hWinBrush) EndIf EndIf End
guy rabiller | radfac founder / ceo | raafal.org
- netmaestro
- PureBasic Bullfrog
- Posts: 8451
- Joined: Wed Jul 06, 2005 5:42 am
- Location: Fort Nelson, BC, Canada
Re: Transparent Gadgets
The background for a button is drawn by the Operating System on Windows. Returning brushes for WM_CTLCOLORSTATIC and other such approaches will not have any effect. However, you can make your button look any way you choose by setting the #BS_OWNERDRAW style bit and responding to messages appropriately in code. There should be several examples of ownerdrawn buttons on the forums if you do a search.
BERESHEIT
- grabiller
- Enthusiast
- Posts: 309
- Joined: Wed Jun 01, 2011 9:38 am
- Location: France - 89220 Rogny-Les-Septs-Ecluses
- Contact:
Re: Transparent Gadgets
Well in fact I was rather messing with skinning (and subclassing) the Windows Edit Control (the String Gadget) and was confronted with several problems.netmaestro wrote:The background for a button is drawn by the Operating System on Windows. Returning brushes for WM_CTLCOLORSTATIC and other such approaches will not have any effect. However, you can make your button look any way you choose by setting the #BS_OWNERDRAW style bit and responding to messages appropriately in code. There should be several examples of ownerdrawn buttons on the forums if you do a search.
It seems the listview is an exception, doing some validation in your place so you don't have to do it.
The first problem I had was flicking.. and after several days of investigation, I've found the (almost) bullet proof solution (valid for other gadgets too, especially if you use canvas). I say "almost" because there is one thing you can't do nothing about it: If the windows message queue is overloaded, then the paint messages (WM_PAINT,WM_ERASEBKGND,WM_NCPAINT) arrive too late to avoid flicking.
And what I've discovered is that one way to have the windows messages queue overloaded is to "forgot" to validate the region/rectangle you paint once you've done it, because else Windows keep on sending the paint message until the region is validated..
So the flicking problems, from what I get, have nothing to do with the fact you paint in the WM_ERASEBKGND or WM_PAINT event. Paint in WM_ERASEBKGND will not automagically make the problem disappear.
In short, if you don't need to erase the background (because your painting will take the entier client area), just return 1, and do your painting in WM_PAINT. The key then, is to not forgot to validate the rectangle area you just paint by using:
Code: Select all
ValidateRect_( hWnd, #Null )
Normaly, with this you are safe, even if your main windows is erasing its background with a plain color (and without using the SmartWindowRefresh( #MainWindow, 1 ) )
But for this to work, you have to paint exclusively into the client area, and not into the windows area (the 'NC' area).
This means you have to use:
Code: Select all
Protected rc.RECT, hDC.i
GetClientRect_( hWnd, @rc )
hDC = GetDC_( hWnd )
Code: Select all
GetWindowRect_( hWnd, @rc )
hDC = GetWindowDC_( hWnd )
So the solution was to set the client area with borders of 0 pixels (see below how) and do all the painting bliting etc.. into the client area.
But aside from the flicking problem, I was stuck with other problem, I was unable to avoid the Edit Control to paint without my permission

I was for most of the parts, except for the Caret for wich it seems there is nothing to do to hide it, and for the double click situation where the Edit Control decides to draw on screen even if you send SendMessage_ ( GadgetID(#StringEdit), #WM_SETREDRAW, 0, 0 ) right before sending it the double click message!
So the last solution I have in mind and wich I'm exploring now is to use a ghost control. My main gadget will be a CanvasGadget then I'll also create a always hidden (WM_SETREDRAW/0) StringGadget. I'll then send the input events from the canvas to the string control and use the EM_ messages to interact with it then paint the canvas with the result.
Regarding how to set the Client Area borders, here is my solution:
Code: Select all
Case #WM_WINDOWPOSCHANGING
; ...[ Local Variable ].................................................
Protected *wpos.WINDOWPOS = lParam
; ...[ Force #WM_NCCALCSIZE Message To Be Sent ]........................
*wpos\flags | #SWP_FRAMECHANGED
; ...[ Processed ]......................................................
ProcedureReturn 0
Case #WM_NCCALCSIZE
; ...[ MUST Calculate Client Area ].....................................
If wParam
; ...[ Local Variable ]...............................................
Protected *ncs.NCCALCSIZE_PARAMS = lParam
; ...[ Set New Client Size With Borders ].............................
*ncs\rgrc[0]\left = *ncs\lppos\x + cli_border_left
*ncs\rgrc[0]\right = *ncs\lppos\x + ( *ncs\lppos\cx - cli_border_right )
*ncs\rgrc[0]\top = *ncs\lppos\y + cli_border_top
*ncs\rgrc[0]\bottom = *ncs\lppos\y + ( *ncs\lppos\cy - cli_border_bottom )
; ...[ Processed ]......................................................
ProcedureReturn 0
EndIf
Cheers,
Guy.
guy rabiller | radfac founder / ceo | raafal.org