Transparent Gadgets

Everything else that doesn't fall into one of the other PB categories.
Amiga5k
Enthusiast
Enthusiast
Posts: 329
Joined: Fri Apr 25, 2003 8:57 pm

Post by Amiga5k »

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
*** Diapers and politicians need to be changed...for the same reason! ***
*** Make every vote equal: Abolish the Electoral College ***
*** www.au.org ***
User avatar
grabiller
Enthusiast
Enthusiast
Posts: 309
Joined: Wed Jun 01, 2011 9:38 am
Location: France - 89220 Rogny-Les-Septs-Ecluses
Contact:

Re:

Post by grabiller »

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.


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
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Transparent Gadgets

Post by netmaestro »

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
User avatar
grabiller
Enthusiast
Enthusiast
Posts: 309
Joined: Wed Jun 01, 2011 9:38 am
Location: France - 89220 Rogny-Les-Septs-Ecluses
Contact:

Re: Transparent Gadgets

Post by grabiller »

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.
Well in fact I was rather messing with skinning (and subclassing) the Windows Edit Control (the String Gadget) and was confronted with several problems.

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 )
( #Null for the entier client area but you can specify a smaller rectangle if you want).

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 )
Instead of:

Code: Select all

GetWindowRect_( hWnd, @rc )
hDC = GetWindowDC_( hWnd )
I was first using the Non-Client area because I wanted to 'skin' the Edit Control with a custom image for the control borders. But unfortunately I did not found a solution to avoid flicking by using the Non-Client area.

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
I'll report here the result with my 'ghost' control experiments, once I've got something.

Cheers,
Guy.
guy rabiller | radfac founder / ceo | raafal.org
Post Reply