WinForms Layered Controls with background images cause breaks on scroll

I have one Form

with the following properties:

  • Background image
  • Scrollable Panel

    with transparent background andDock = DockStyle.Fill

  • PictureBox

    with large Width

    and Height

    that show scrollbars

All controls are now set to DoubleBuffered, including the form. Everything works as expected, except that when the panel is scrolled for the PictureBox, the background image of the form scrolls, repeating it, showing vertical and horizontal breaking, although its a static image that fits the size of the form, and when you stop scrolling, it displays correctly. This only happens when I drag the scroll bars, if I click any point in the scroll bars to move it, it displays correctly.

According to my understanding, Double Buffering should rule out such cases, but even with double buffering, it may be a little better, but it's still a huge problem when scrolling.

I tried to put all the controls inside another panel instead of using the form background image and put that panel on the form, but that didn't make any difference.

+1


source to share


1 answer


You are fighting a Windows system option called "Show window contents while dragging." It is included for all modern versions of Windows. Disabling is not a realistic goal, as it is a system setting that affects all windows in all applications. There is no retrace for selective bypass of this option.

When enabled, the OS optimizes window scrolling. It performs fast bit-bit to move pixels in the video clip buffer and generates a paint message for only the portion of the window that is displayed by the scroll. Like the bottom few rows of pixels when scrolling down. Base call winapi ScrollWindowEx () . The intention is to provide an application with a more flexible user interface, much less scrolling needs to be done to perform scrolling.

You can probably see where the title is, ScrollWindowEx () also moves the pixels that were drawn by the BackgroundImage form. You see it. The next thing you see is a side effect of the optimized paint, it only redraws the part of the window that was detected. Therefore, the moved pixels of the background image are not redrawn. Similar to the "smearing" effect.

There is an easy way to do this: just execute an event handler for the Scroll event panel and call Invalidate (). So the whole panel is redrawn again:

    private void panel1_Scroll(object sender, ScrollEventArgs e) {
        panel1.Invalidate();
    }

      

But now you will notice that the paint side effect is no longer optimized. You can still see the pixels move and then overload. How visible this is depends a lot on how expensive the BackgroundImage is. Usually never cheap because it doesn't have an optimal pixel format (32bppPArgb) and doesn't have the right size, so it needs to be scaled to fit the window. The visual effect resembles a "pogo" shaking quickly at one edge of the panel.



It is rather unlikely that you will find this acceptable or want to do the job of optimizing the BackgroundImage. It takes a rather large weapon to stop ScrollWindowEx () from doing its job, you can output LockWindowUpdate (). Like this:

 using System.Runtime.InteropServices;
 ...
    private void panel1_Scroll(object sender, ScrollEventArgs e) {
        if (e.Type == ScrollEventType.First) {
            LockWindowUpdate(this.Handle);
        }
        else {
            LockWindowUpdate(IntPtr.Zero);
            panel1.Update();
            if (e.Type != ScrollEventType.Last) LockWindowUpdate(this.Handle);
        }
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool LockWindowUpdate(IntPtr hWnd);

      

Works well, background image pixels are now stable. Any other pixels, well, not many. Another visual effect is to call it "wrinkle". You can get rid of this artifact by placing the window in layout mode. Which double buffer fills the entire window surface, including child controls:

    protected override CreateParams CreateParams {
        get {
            const int WS_EX_COMPOSITED = 0x02000000;
            var cp = base.CreateParams;
            cp.ExStyle |= WS_EX_COMPOSITED;
            return cp;
        }
    }

      

The only remaining artifact is a side effect of this not-so-cheap code. It probably doesn't look that smooth when you scroll. Which otherwise tells you why windows were designed to be opaque 28 years ago.

+6


source







All Articles