Flicker-free expansion (resize) of a window to the left
Asked Answered
P

1

2

Say you have a form that you can expand to the left to show additional controls:

Collapsed:

Collapsed form

Expanded:

Expanded form

The simplest way to achieve this in Delphi is to use alRight as the primary anchor for all controls (instead of alLeft) and then simply adjust the width and X coordinate of the form. Either you can set the Width and Left properties individually, or you can use a function that sets them simultaneously, like

if FCollapsed then
  SetWindowPos(Handle, 0, Left - Width, Top, 2 * Width, Height, 0)
else
  SetWindowPos(Handle, 0, Left + Width div 2, Top, Width div 2, Height, 0)

The problem is that there is quite noticeable flickering in the always-visible part of the form (in this example, the buttons) while expanding or collapsing. Try it yourself!

It is possible for the operating system to resize the form to the left without any flickering at all -- just grab the left edge of the form using the mouse and drag the mouse to the left or right -- but I am unable to find any function in the Windows API that exposes this kind of resizing.

I have tried to use several different Windows API functions to resize and reposition the form, tried their various parameters (for instance, the SWP_* flags), tried LockWindowUpdate, WM_SETREDRAW, TForm.DoubleBuffered etc. to no avail. I also examined the possibility to use the WM_SYSCOMMAND SC_SIZE approach.

I am not yet sure if the problem lies at the OS level or the VCL level.

Any suggestions?

Edit: I am very surprised to see that this Q received close votes. Let me try to clarify:

  1. Create a new VCL forms application.

  2. Add a few buttons to the right side of the main form and a memo to the left. Set Anchors to [alTop, alRight] on all controls. On the OnClick handler of the buttons, add the following code:

    if FCollapsed then
      SetWindowPos(Handle, 0, Left - Width, Top, 2 * Width, Height, 0)
    else
      SetWindowPos(Handle, 0, Left + Width div 2, Top, Width div 2, Height, 0);
    
    FCollapsed := not FCollapsed;
    

    where FCollapsed is private boolean field of the form (initialized to false).

  3. Now, click the buttons repeatedly. (Or give one of them keyboard focus and hold the Enter key for a few seconds.) You will probably notice that the region with the buttons on your monitor will not display a perfect still image, but will flicker. In addition, you might actually see 'ghosts' of the buttons to the left of the actual column of buttons.

I am unable to capture this millisecond flickering using screen capture, so instead I used a digital camera to record my screen:

https://privat.rejbrand.se/VCLFormExpandFlicker.mp4

In this video clip, it is apparent that the column of buttons isn't a static image on the screen; instead, for a few milliseconds each time the form is resized, this region is something else than it should be. It is equally apparent that there is a 'ghost' column of buttons to the left.

My question is if there is any reasonably simple way to get rid of these visual artefacts (that at least to me are very visible even if you expand/collapse the form a single time).

On my Windows 10/Delphi 10.1 computer at work, the form is resized in a perfect manner when I drag its left-most edge using the mouse: the unaffected client area of the form is perfectly static on the monitor. However, on my Windows 7/Delphi 2009 PC at home, I do see that there is a lot of repositioning going on when I do this.

Peccant answered 16/2, 2018 at 11:11 Comment(19)
Have you tried DisableAlign/EnableAlign?Cuckooflower
@nil: Yes, but it doesn't remove the flickering.Peccant
I see, that changes nothing. In my test resizing the form at the edges did flicker, too. Would you provide a small sample to reproduce the issue? Edit, seems difference is where you grab and resize, too. Left: flickering. Right: none.Cuckooflower
Here there are some components - #7265916. I've also used TJvRollOut - wiki.delphi-jedi.org/wiki/JVCL_Help:TJvRollOut from Jedi, and it behaved nice.Bertsche
@nil: Just create a new VCL application, drop some controls to it, set Align to [alTop, alRight] on all controls and use the code I submitted. You can use TForm.OnClick, for example, and toggle FCollapsed on each invocation. The WM_COMMAND-style resizing is flicker-free at least on Windows 10 with visual themes enabled. I can imagine it not being flicker-free on Windows 7 without visual themes (but I haven't tested).Peccant
1. Does it help to put the always-visible part on a panel? 2. I once played with DeferWindowPos etc., but I don't remember if it mattered much.(See #2089500)Crabbed
Try handling the WM_ERASEBKGND message as it might be the root cause of the flicker.Poole
The flicker is considerably worse using the mouse on the left edge. W7, aero, XE2.Prosecutor
FWIW I don't agree your view about the API, the API has no concept of right anchoring/alignment. You should be seeing flicker resizing from the left of the form if the form has right anchored controls on it, simply because the controls have to be repositioned. I have no idea why you don't see that flicker.Prosecutor
You can get rid of flickering by having 2 copies of the form, but it is not a good solution because of the difficulty of keeping the content of the two in tandem. If it is just buttons and a memo, like you show then fair enough. Otherwise it gets too hard.Trimer
@SertacAkyuz: On my Windows 7 (Delphi 2009) PC at home, I do see horrible flickering when dragging the form's left edge using the mouse. On my Windows 10 (Delphi 10.1) PC at work, however, this operation is perfectly smooth: the unaffected region of the window (that is, the right part of it) is a perfect static image on the monitor while I resize the window using the mouse on the left edge.Peccant
@Ron: No, that doesn't help. The problem is that the VCL repositions the controls, I believe.Peccant
On Windows 8.1 behavior is similar to what you observed with Windows 7. Not sure if that helps: I tried to slow down repainting by putting a Sleep in WMEraseBkgnd handler when the form is (un)collapsing. The result is a slide-show, and does look like the right-side is not always visible in fact. Code: procedure TForm2.WMEraseBkgnd(var Message: TWMEraseBkgnd); begin if Collapsing then Sleep(200); inherited; end;Cuckooflower
What kind of buttons do you have (TButton, TSpeedButton etc)? You may try to use different button controls as flickering seems to depend also on the type (class) of components used (not all of them are Windows based). Also, adding an empty handler for WM_ERASEBKGND helped me reduce the flicker considerably.Pneumonia
@dsp_user: I only use windowed controls, partly because I want simple keyboard navigation; all of these display the same issue (that is, it doesn't depend on the particular type of controls). I tried to handle WM_ERASEBKGND, but it didn't improve the situation.Peccant
I will try to reproduce your problem and report back. Have a nice dayPneumonia
@dsp_user: Thanks, you too! :)Peccant
C# Windows Forms application has the same issue.Research
Yes, the flicker is quite visible. I've tried using SelectClipRgn and applying WS_CLIPCHILDREN on the parent window but nothing worked in this case. It's interesting to see that some Windows apps like Notepad flicker like crazy when resized while others (e.g Wordpad) are almost flicker-free. If you solve this,, please post the answer. I too might try some more complex approaches when I find the time.Pneumonia
A
1

I can provide some insight about why you see ghost images of the other half of your UI and possibly a way to stop it. The ghost image indicates that someone is copying your client area pixels (and copying them to the wrong place, always flush-left in your window) before you have a chance to redraw them with the correct pixels.

There are likely two different, overlapping sources of these ghost pixels.

The first layer applies to all Windows OSes and comes from a BitBlt inside SetWindowPos. You can get rid of that BitBlt in several ways. You can create your own custom implementation of WM_NCCALCSIZE to tell Windows to blit nothing (or to blit one pixel on top of itself), or alternately you can intercept WM_WINDOWPOSCHANGING (first passing it onto DefWindowProc) and set WINDOWPOS.flags |= SWP_NOCOPYBITS, which disables the BitBlt inside the internal call to SetWindowPos() that Windows makes during window resizing. This has the same eventual effect of skipping the BitBlt.

However, Windows 8/10 aero adds another, more troublesome layer. Apps now draw into an offscreen buffer which is then composited by the new, evil DWM.exe window manager. And it turns out DWM.exe will sometimes do its own BitBlt type operation on top of the one already done by the legacy XP/Vista/7 code. And stopping DWM from doing its blit is much harder; so far I have not seen any complete solutions.

For sample code that will break through the XP/Vista/7 layer and at least improve the performance of the 8/10 layer, please see:

How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?

Since you have multiple child windows, the situation is even a little more complicated. The BitBlt type operations I mentioned above happen on your whole top-level window as a whole (they treat the window as one set of pixels regardless of how many windows are underneath, and regardless of CLIPCHILDREN). But you need to have windows move atomically so that on the next redraw they are all positioned correctly. You may find BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos useful for that (but only go there if the above tricks do not work).

Aquileia answered 26/10, 2018 at 8:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.