DEfliker Splitter with a child ScrollArea

Share your advanced PureBasic knowledge/code with the community.
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

DEfliker Splitter with a child ScrollArea

Post by ChrisR »

By default, a ScrollArea flashes quite strongly when resized as a child of a Splitter!
As the ScrollArea is auto resized internally by the Splitter, WM_SETREDRAW Off/On, RedrawWindow... have no effect.

A tip to remove flickering from a ScrollArea resized with a SplitterGadget.
Any other tips are welcome

Code: Select all

; A tip to remove flickering from a ScrollArea resized with a SplitterGadget:
;   BindGadgetEvent Splitter. In the callback, replaced in the Splitter, the child ScrollArea with a Canvas gadget.
;   The ScrollArea resized is then drawn in the Canvas by sending a WM_Print message.
;   On left button up, replaced in reverse, the Canvas by the ScrollArea in the Splitter.

EnableExplicit

Enumeration Window
  #Window_0
EndEnumeration

Enumeration Gadgets
  #ScrlArea_1
  #Canv_1
  #Txt_1
  #Btn_1
  #PrintScrlArea_2
  #ScrlArea_2
  #Canv_2
  #Txt_2
  #Btn_2
  #Splitter
EndEnumeration

Enumeration Image
  #Imag
EndEnumeration

CatchImage(#Imag, ?Imag)
Global ResizeSplitter

Procedure BindSplitter()
  Static SplitterPosition
  Protected NewSplitterPosition, hDC
  NewSplitterPosition = GetGadgetState(#Splitter)
  If Not NewSplitterPosition = SplitterPosition
    SplitterPosition = NewSplitterPosition
    UpdateWindow_(GadgetID(#ScrlArea_1))   ; a little better for #ScrlArea_1 
    If Not ResizeSplitter
      HideGadget(#PrintScrlArea_2, #False)
      If GetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget) = #ScrlArea_1
        SetGadgetAttribute(#Splitter, #PB_Splitter_SecondGadget, #PrintScrlArea_2)
      Else
        SetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget, #PrintScrlArea_2)
      EndIf
      HideGadget(#ScrlArea_2, #True)
      ResizeSplitter = #True
    EndIf
    ResizeGadget(#ScrlArea_2, #PB_Ignore, #PB_Ignore, GadgetWidth(#PrintScrlArea_2), #PB_Ignore)
    ;RedrawWindow_(GadgetID(#ScrlArea_2), #Null, #Null, #RDW_INVALIDATE | #RDW_ERASENOW | #RDW_ALLCHILDREN | #RDW_UPDATENOW)
    hDC =  StartDrawing(CanvasOutput(#PrintScrlArea_2))
    If hDC
      SendMessage_(GadgetID(#ScrlArea_2), #WM_PRINT, hDC, #PRF_CHILDREN | #PRF_CLIENT | #PRF_NONCLIENT | #PRF_OWNED | #PRF_ERASEBKGND)
      StopDrawing()
    EndIf
  ;Else
  ;  Debug "Event received with same Splitter position"
  EndIf
EndProcedure

Procedure Open_Window_0(X = 0, Y = 0, Width = 580, Height = 320)
  If OpenWindow(#Window_0, X, Y, Width, Height, "DEFlicker ScrollArea", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered)
    
    ScrollAreaGadget(#ScrlArea_1, 0, 0, 0, 0, 512, 512, 10, #PB_ScrollArea_Single)
    SetGadgetColor(#ScrlArea_1, #PB_Gadget_BackColor, $999999)
    CanvasGadget(#Canv_1, 0, 0, DesktopUnscaledX(256), DesktopUnscaledX(256))
    SetGadgetAttribute(#Canv_1, #PB_Canvas_Image, ImageID(#Imag))
    TextGadget(#Txt_1, 230, 120, 160, 60, "( Fliker Side )", #PB_Text_Center | #SS_CENTERIMAGE)
    SetGadgetColor(#Txt_1, #PB_Gadget_BackColor, $400000) : SetGadgetColor(#Txt_1, #PB_Gadget_FrontColor, $40FFFF)
    ButtonGadget(#Btn_1, 40, 230, 220, 60, "Change (Flicker) Side ")
    CloseGadgetList()
    
    CanvasGadget(#PrintScrlArea_2, 0, 0, 0, 0)
    HideGadget(#PrintScrlArea_2, #True)
    
    ScrollAreaGadget(#ScrlArea_2, 0, 0, 0, 0, 512, 512, 10, #PB_ScrollArea_Single)
    SetGadgetColor(#ScrlArea_2, #PB_Gadget_BackColor, $999999)
    ; It does not work with a PB_Canvas_Container with button and text inside, they are not drawn as children when the #WM_PRINT message is sent ???
    CanvasGadget(#Canv_2, 0, 0, DesktopUnscaledX(256), DesktopUnscaledX(256))
    SetGadgetAttribute(#Canv_2, #PB_Canvas_Image, ImageID(#Imag))
    TextGadget(#Txt_2, 230, 120, 160, 60, "( DEFliker Side )", #PB_Text_Center | #SS_CENTERIMAGE)
    SetGadgetColor(#Txt_2, #PB_Gadget_BackColor, $400000) : SetGadgetColor(#Txt_2, #PB_Gadget_FrontColor, $40FFFF)
    ButtonGadget(#Btn_2, 40, 230, 220, 60, "Change (DEFlicker) Side ")
    CloseGadgetList()
    
    SplitterGadget(#Splitter, 0, 0, 580, 320, #ScrlArea_1, #ScrlArea_2, #PB_Splitter_Separator | #PB_Splitter_Vertical)
    BindGadgetEvent(#Splitter, @BindSplitter())
    ProcedureReturn #True
  EndIf
EndProcedure

;- Main Program
If Open_Window_0()
  Repeat
    If ResizeSplitter = #True
      ;Better here than using #WM_LBUTTONUP when an event is sent following a resize, without a left button up
      ;32768 Or $8000: the most significant bit is set. If the high-order bit is 1, the key is down, otherwise, it is up.
      If Not (GetKeyState_(#VK_LBUTTON) & 32768)
        HideGadget(#ScrlArea_2, #False)
        If GetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget) = #ScrlArea_1
          SetGadgetAttribute(#Splitter, #PB_Splitter_SecondGadget, #ScrlArea_2)
        Else
          SetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget, #ScrlArea_2)
        EndIf
        HideGadget(#PrintScrlArea_2, #True)
        RedrawWindow_(GadgetID(#Splitter), #Null, #Null, #RDW_INVALIDATE | #RDW_ERASENOW | #RDW_ALLCHILDREN | #RDW_UPDATENOW)
        ResizeSplitter = #False
      EndIf
    EndIf
    
    Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        Break
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #Btn_1, #Btn_2
            If GetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget) = #ScrlArea_1
              SetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget, #ScrlArea_2)
              SetGadgetAttribute(#Splitter, #PB_Splitter_SecondGadget, #ScrlArea_1)
            Else
              SetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget, #ScrlArea_1)
              SetGadgetAttribute(#Splitter, #PB_Splitter_SecondGadget, #ScrlArea_2)
            EndIf
            RedrawWindow_(GadgetID(#Splitter), #Null, #Null, #RDW_INVALIDATE | #RDW_ERASENOW | #RDW_ALLCHILDREN | #RDW_UPDATENOW)
        EndSelect
    EndSelect  
  ForEver
EndIf

DataSection
  Imag: : IncludeBinary #PB_Compiler_Home+"Examples\Sources\Data\Background.bmp"
EndDataSection
User avatar
jacdelad
Addict
Addict
Posts: 2010
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: DEfliker Splitter with a child ScrollArea

Post by jacdelad »

Hi Chris,
this is great and I stumbled across the flickering a few times already. I tried to adapt it into some kind of PBI to easily include it into my code, but it somehow doesn't work...
...could you turn it into a function (or two) to include it into other codes?
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: DEfliker Splitter with a child ScrollArea

Post by ChrisR »

Hi Jacdelad,
I'll see if I can make a pbi. It will probably have 2 functions, one for Bindevent and one for the event loop.
For now, I've replaced the CanvasGadget with an ImageGadget, it should be faster

Code: Select all

; A tip to remove flickering from a ScrollArea resized with a SplitterGadget:
;   BindGadgetEvent Splitter. In the callback, replaced in the Splitter, the child ScrollArea with an ImageGadget.
;   The ScrollArea resized is then drawn in the Image of the ImageGadget by sending a WM_Print message.
;   On left button up, replaced in reverse, the ImageGadget by the ScrollArea in the Splitter.

EnableExplicit

Enumeration Window
  #Window_0
EndEnumeration

Enumeration Gadgets
  #ScrlArea_1
  #Canv_1
  #Txt_1
  #Btn_1
  #PrintScrlArea_2
  #ScrlArea_2
  #Canv_2
  #Txt_2
  #Btn_2
  #Splitter
EndEnumeration

Enumeration Image
  #Imag
EndEnumeration

CatchImage(#Imag, ?Imag)
Global ResizeSplitter

Procedure BindSplitter()
  Static SplitterPosition, PrintScrlArea, imgScrlArea = #PB_Default
  Protected NewSplitterPosition, hDC
  
  NewSplitterPosition = GetGadgetState(EventGadget())
  If Not NewSplitterPosition = SplitterPosition
    SplitterPosition = NewSplitterPosition
    
    If Not ResizeSplitter
      If imgScrlArea = #PB_Default 
        imgScrlArea = CreateImage(#PB_Any, 1, 1)
        PrintScrlArea = ImageGadget(#PB_Any, 0, 0, 0, 0, 0)
      EndIf
      HideGadget(#ScrlArea_2, #True)
      If GetGadgetAttribute(EventGadget(), #PB_Splitter_FirstGadget) = #ScrlArea_1
        SetGadgetAttribute(EventGadget(), #PB_Splitter_SecondGadget, PrintScrlArea)
      Else
        SetGadgetAttribute(EventGadget(), #PB_Splitter_FirstGadget, PrintScrlArea)
      EndIf
      HideGadget(PrintScrlArea, #False)
      ResizeSplitter = #True
    EndIf
    
    If GadgetWidth(PrintScrlArea) And GadgetHeight(PrintScrlArea)
      ResizeGadget(#ScrlArea_2, #PB_Ignore, #PB_Ignore, GadgetWidth(PrintScrlArea), GadgetHeight(PrintScrlArea))
      ResizeImage(imgScrlArea, DesktopScaledX(GadgetWidth(PrintScrlArea)), DesktopScaledY(GadgetHeight(PrintScrlArea)))
      hDC =  StartDrawing(ImageOutput(imgScrlArea))
      If hDC
        SendMessage_(GadgetID(#ScrlArea_2), #WM_PRINT, hDC, #PRF_CHILDREN | #PRF_CLIENT | #PRF_NONCLIENT | #PRF_OWNED | #PRF_ERASEBKGND)
        StopDrawing()
      EndIf
      SetGadgetState(PrintScrlArea, ImageID(imgScrlArea))
    Else
      SetGadgetState(PrintScrlArea, 0)
    EndIf
  ;Else
    ;  Debug "Event received with same Splitter position"
  EndIf
  UpdateWindow_(GadgetID(EventGadget()))
EndProcedure

Procedure Open_Window_0(X = 0, Y = 0, Width = 580, Height = 320)
  If OpenWindow(#Window_0, X, Y, Width, Height, "DEFlicker ScrollArea", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered)
    
    ScrollAreaGadget(#ScrlArea_1, 0, 0, 0, 0, 512, 512, 10, #PB_ScrollArea_Single)
    SetGadgetColor(#ScrlArea_1, #PB_Gadget_BackColor, $999999)
    CanvasGadget(#Canv_1, 0, 0, DesktopUnscaledX(256), DesktopUnscaledX(256))
    SetGadgetAttribute(#Canv_1, #PB_Canvas_Image, ImageID(#Imag))
    TextGadget(#Txt_1, 230, 120, 160, 60, "( Fliker Side )", #PB_Text_Center | #SS_CENTERIMAGE)
    SetGadgetColor(#Txt_1, #PB_Gadget_BackColor, $400000) : SetGadgetColor(#Txt_1, #PB_Gadget_FrontColor, $40FFFF)
    ButtonGadget(#Btn_1, 40, 230, 220, 60, "Change (Flicker) Side ")
    CloseGadgetList()
    
    ScrollAreaGadget(#ScrlArea_2, 0, 0, 0, 0, 512, 512, 10, #PB_ScrollArea_Single)
    SetGadgetColor(#ScrlArea_2, #PB_Gadget_BackColor, $999999)
    ; It does not work with a PB_Canvas_Container with button and text inside, they are not drawn as children when the #WM_PRINT message is sent ???
    CanvasGadget(#Canv_2, 0, 0, DesktopUnscaledX(256), DesktopUnscaledX(256))
    SetGadgetAttribute(#Canv_2, #PB_Canvas_Image, ImageID(#Imag))
    TextGadget(#Txt_2, 230, 120, 160, 60, "( DEFliker Side )", #PB_Text_Center | #SS_CENTERIMAGE)
    SetGadgetColor(#Txt_2, #PB_Gadget_BackColor, $400000) : SetGadgetColor(#Txt_2, #PB_Gadget_FrontColor, $40FFFF)
    ButtonGadget(#Btn_2, 40, 230, 220, 60, "Change (DEFlicker) Side ")
    CloseGadgetList()
    
    SplitterGadget(#Splitter, 0, 0, 580, 320, #ScrlArea_1, #ScrlArea_2, #PB_Splitter_Separator | #PB_Splitter_Vertical)
    BindGadgetEvent(#Splitter, @BindSplitter())
    ProcedureReturn #True
  EndIf
EndProcedure

;- Main Program
If Open_Window_0()
  Repeat
    If ResizeSplitter = #True
      ;Better here than using #WM_LBUTTONUP when an event is sent following a resize, without a left button up
      ;32768 Or $8000: the most significant bit is set. If the high-order bit is 1, the key is down, otherwise, it is up.
      If Not (GetKeyState_(#VK_LBUTTON) & 32768)
        If GetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget) = #ScrlArea_1
          HideGadget(GetGadgetAttribute(#Splitter, #PB_Splitter_SecondGadget), #True)
          SetGadgetAttribute(#Splitter, #PB_Splitter_SecondGadget, #ScrlArea_2)
        Else
          HideGadget(GetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget), #True)
          SetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget, #ScrlArea_2)
        EndIf
        HideGadget(#ScrlArea_2, #False)
        RedrawWindow_(GadgetID(#Splitter), #Null, #Null, #RDW_INVALIDATE | #RDW_ERASENOW | #RDW_ALLCHILDREN | #RDW_UPDATENOW)
        ResizeSplitter = #False
      EndIf
    EndIf
    
 
    Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        Break
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #Btn_1, #Btn_2
            If GetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget) = #ScrlArea_1
              SetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget, #ScrlArea_2)
              SetGadgetAttribute(#Splitter, #PB_Splitter_SecondGadget, #ScrlArea_1)
            Else
              SetGadgetAttribute(#Splitter, #PB_Splitter_SecondGadget, #ScrlArea_2)
              SetGadgetAttribute(#Splitter, #PB_Splitter_FirstGadget, #ScrlArea_1)
            EndIf
            RedrawWindow_(GadgetID(#Splitter), #Null, #Null, #RDW_INVALIDATE | #RDW_ERASENOW | #RDW_ALLCHILDREN | #RDW_UPDATENOW)
        EndSelect
    EndSelect  
  ForEver
EndIf

DataSection
  Imag: : IncludeBinary #PB_Compiler_Home+"Examples\Sources\Data\Background.bmp"
EndDataSection
User avatar
jacdelad
Addict
Addict
Posts: 2010
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: DEfliker Splitter with a child ScrollArea

Post by jacdelad »

Hi Chris,
thanks for that. I was up late yesterday (and fell asleep three times at work, but don't tell anyone) and got confused by how it even works. I understand it a bit better now, but, and I don't know why, not completely. You replace one side with a picture of its content to remove the flickering?
Also, the left side still flickers, is this for demo reasons? I know I should be able to understand this, but because of whatever I don't...
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: DEfliker Splitter with a child ScrollArea

Post by ChrisR »

Re, I think you've got it right, that's it, replace the first or second or both of the splitter's child gadgets with image(s) that prevent flickering.
The example with the left side flickering is only for demonstration purposes, to show the difference between the 2 sides.
By clicking on one of the 2 buttons, you can change which side flickers and which side doesn't.

But don't tell anyone, there's a much much simpler way, by using WS_EX_COMPOSITED Extended Styles

Code: Select all

SetWindowLongPtr_(GadgetID(#ScrlArea), #GWL_EXSTYLE, GetWindowLongPtr_(GadgetID(#ScrlArea), #GWL_EXSTYLE) | #WS_EX_COMPOSITED)
Paints all descendants of a window in bottom-to-top painting order using double-buffering. Double-buffering allows the window and its descendents to be painted without flicker.
But in my case, I couldn't use WS_EX_COMPOSITED, hence the tip above, that shouldn't be used more than that :wink:
tatanas
Enthusiast
Enthusiast
Posts: 260
Joined: Wed Nov 06, 2019 10:28 am
Location: France

Re: DEfliker Splitter with a child ScrollArea

Post by tatanas »

But don't tell anyone, there's a much much simpler way, by using WS_EX_COMPOSITED Extended Styles
Unfortunately with WS_EX_COMPOSITED style the CPU usage becomes incredibly high (100% of one core) (got the same problem about flickering while using Splitter with Panel).
Here is an interesting topic about flickering : https://www.catch22.net/tuts/win32/flic ... e-drawing/
Windows 10 Pro x64
PureBasic 6.20 x64
Post Reply