Controls flickering on scroll

I have created the following example to show an issue I have when scrolling a ContainerControl (and/or Canvas) containing controls.

As you can see there is horrible flicker! :frowning:

I am performing the move in ValueChanged on the scrollbar ā€œContainerControl11.Left = 0-me.valueā€ I assume that is the ā€œcorrectā€ way to move the container around?

I have managed to reduce the flicker to the following using some code here

There is a little flicking here, its hard to pick up with the gif. The ComboBox, RadioButton and Labels are flickering, but everything else is pretty solid.

Does anyone have any more pointers as this just looks nasty.

Thanks in advance.

Ah the sheer joy of OS drawn Win 32 controls :slight_smile:
Which is why MS mostly doesnā€™t use OS drawn control and relies on self drawn controls instead (and there are places in VS 2015 you can still find they do use OS drawn controls and you get the exact same issues)

We donā€™t use self drawn controls

Not sure if freeing updates and unfreezing them would help or hurt
Something like freeze the update, do the scroll unfreeze the update might alleviate the flickering
Or I could be really wrong on that and it might make it worse

Public Sub FreezeUpdate(extends w as Window)
  #if TargetWin32
    // Use the WM_SETREDRAW message instead to lock the redraw
    // state of the window (note that this can be used for controls as well)
    Const WM_SETREDRAW = &hB
    call SendMessage( w.Handle, WM_SETREDRAW, 0, 0 )
  #endif
End Sub
Public Sub UnfreezeUpdate(extends w as Window)
  #if TargetWin32
     // Use the WM_SETREDRAW message instead to unlock the redraw
    // state of the window (note that this can be used for controls as well)
    Const WM_SETREDRAW = &hB
    call SendMessage( w.Handle, WM_SETREDRAW, 1, 0 )
  #endif
End Sub

Thanks Norman.

I think thatā€™s the exact code I linked above. I used it and it took the flickering from image 1 to image 2. Iā€™m still getting some flicker just on ComboBox, RadioButton and Labels, are they rendered differently somehow?

[quote=308457:@Norman Palardy]Ah the sheer joy of OS drawn Win 32 controls :slight_smile:
Which is why MS mostly doesnā€™t use OS drawn control and relies on self drawn controls instead (and there are places in VS 2015 you can still find they do use OS drawn controls and you get the exact same issues)

We donā€™t use self drawn controls

Not sure if freeing updates and unfreezing them would help or hurt
Something like freeze the update, do the scroll unfreeze the update might alleviate the flickering
Or I could be really wrong on that and it might make it worse

Public Sub FreezeUpdate(extends w as Window)
  #if TargetWin32
    // Use the WM_SETREDRAW message instead to lock the redraw
    // state of the window (note that this can be used for controls as well)
    Const WM_SETREDRAW = &hB
    call SendMessage( w.Handle, WM_SETREDRAW, 0, 0 )
  #endif
End Sub

Public Sub UnfreezeUpdate(extends w as Window) #if TargetWin32 // Use the WM_SETREDRAW message instead to unlock the redraw // state of the window (note that this can be used for controls as well) Const WM_SETREDRAW = &hB call SendMessage( w.Handle, WM_SETREDRAW, 1, 0 ) #endif End Sub [/quote]

Does this also work for 64bit builds? (you are using TargetWin32)

Win32 is the name of the framework, like Cocoa in macOS. Not the executable bit number.

It may well need to be a different declare for 64 bit Windows
I didnā€™t check MSDN and just pulled this out of some existing code
So it may need to be written as

Public Sub FreezeUpdate(extends w as Window)
  #if TargetWindows
   #if Target32Bit
    // Use the WM_SETREDRAW message instead to lock the redraw
    // state of the window (note that this can be used for controls as well)
    Const WM_SETREDRAW = &hB
    call SendMessage( w.Handle, WM_SETREDRAW, 0, 0 )
  #else
    // 64 bit windows declare here
  #endif
#endif
End Sub
Public Sub UnfreezeUpdate(extends w as Window)
  #if TargetWindows
   #if Target32Bit
    // Use the WM_SETREDRAW message instead to unlock the redraw
    // state of the window (note that this can be used for controls as well)
    Const WM_SETREDRAW = &hB
    call SendMessage( w.Handle, WM_SETREDRAW, 1, 0 )
  #else
    // 64 bit windows declare here
  #endif
  #endif
End Sub

Not in this case
It is the old synonym for TargetWindows
I could literally write

#if TargetWin32
#if Target64Bit
... whatever 64 bit declares
#endif
#endif

but since we now have both TargetWindows in the same manner we have TargetLinux and TargetMacOS it just reads nicer

I have to ask, Is it really necessary to scroll the controls? I consider that to be bad UX. Is it possible to group the controls logically onto pages of a tabpanel or something? I personally avoid having to scroll at all costs.

Honestly I wouldnt do this, but it was just a test project to highlight the issue. However just having a control moved on a window resize (which is quite common) would cause the problem, this many controls is just showing it on a higher spec pc.

Itā€™s less jarring when done on window resize, because pc users have come to expect things to be less than perfect when resizing a window.

[quote=308457:@Norman Palardy]Ah the sheer joy of OS drawn Win 32 controls :slight_smile:
Which is why MS mostly doesnā€™t use OS drawn control and relies on self drawn controls instead
[/quote]
I know of no way to reduce flicker to 0 using Windows and OS drawn Win32 controls
Win32 does not double buffer like OS X

VB mostly avoided OS drawn controls and used ā€œself drawn controlsā€
As do Word, Excel, Powerpoint etc
As I mentioned the one spot we know of where MS does use them in VS they flicker like mad there too

Long term it would likely need to be a move to WPF, WinForms or Universal or whatever UI kit MS is now saying is the future that will make the Windows UI toolkit fully double buffered so flicker is non-existent

Thanks for the info Norman. Iā€™ll try to design around it.

Iā€™d suggest posting your sample code
It cant hurt to let others kick at it
There may be something else to try once we see code
You never know

[quote=308724:@Norman Palardy]Iā€™d suggest posting your sample code
It cant hurt to let others kick at it
There may be something else to try once we see code
You never know[/quote]

Hereā€™s a link to the test project. Its super simple code.

https://www.dropbox.com/s/3tvenrnt702bx38/ScollTest.xojo_binary_project?dl=0

You might also see that the ā€œUntitledā€ label also changes when the scrollbar is moved (see below) from nice to as if its been scaled horizontally a pixel or two.

FYI _ I only did this quickly on one of my VMā€™s so it may behave differently on real pc

Try this

  1. change scrollbar1.valuechanged to just the following
		ContainerControl11.Left = 0-me.value
		ContainerControl11.Invalidate
  1. add a method to ContainerControl1 as follows
Public Sub Invalidate(eraseBackground As Boolean = True)
  for i as integer = 0 to me.ControlCount() - 1
    if me.Control(i) isa RectControl then
      RectControl(me.Control(i)).Invalidate
    end if
  next
End Sub
  1. there does seem to be some weirdness with labels with transparent set to ON so IF This is not a requirement then turn it off on Label1, Label2, Label3

1+2) Not much different Norman. It went back to image 1 above.
3) This worked

For the specific example, do you really need the LiveScroll?

Langue is right, it is no point to use LiveScroll in this case. I would probably set a scrollbar steps so it moves for instance 3 pixels at a time.

There is another thing you can do, especially since I see you are moving the slider pretty fast. Whenever one tries to draw faster than 60 frames a second, bad flicker occurs.

Limit the scroll actions to one per 60th a second. Use ticks with something like this in slider ValueChanged :

static previousTicks as double = Ticks TimerSlide.Mode = Timer.ModeOff TimerSlide.Mode = Timer.ModeSingle if ticks >= previousTicks+1 then ContainerControl11.Left = 0-me.value ContainerControl11.Invalidate previousTicks = Ticks end if

TimerSlide is here to complete the scroll in case the last change was not taken into account because it was before tick.

It contains this into Action :

ContainerControl11.Left = 0-me.value ContainerControl11.Invalidate

[quote=308737:@]1+2) Not much different Norman. It went back to image 1 above.
3) This worked[/quote]

you have to do 1 + 2 + 3 :slight_smile:

I did them all at once :slight_smile: Iā€™d be impressed if changing transparency on Label1,2,3 altered all the flickering on the form :wink: I was letting you know what happened at each stage. Overall it went back to image 1 in terms of flickering but the number 3 fixed the label issue.

Its surprising how many windows apps (non-uwp) actually use scrolling windows with controls. I guess I have just become immune to spotting them. Hereā€™s an example from a program I regularly use called BeyondCompare, they are using both tabbed controls and scrolling as thereā€™s a lot of options in this program.

I also checked with Inspect and it does use standard windows controls, not custom ones.

[quote=308751:@Michel Bujardet]static previousTicks as double = Ticks
TimerSlide.Mode = Timer.ModeOff
TimerSlide.Mode = Timer.ModeSingle
if ticks >= previousTicks+1 then
ContainerControl11.Left = 0-me.value
ContainerControl11.Invalidate
previousTicks = Ticks
end if[/quote]

This is almost a full fix however Iā€™m still seeing some trailing edge corruption, but not on every control, see here:

While this looks like a good fix it should technically be linked to the refresh rate of the monitor not just 60Hz

Unfortunately the Screen class doesnā€™t return the Hz of the monitor. So Iā€™ll probably need to dig this out of the registry if this does become my fix for the problem.