5

I have a Form with the following properties:

  • Background Image
  • Scrollable Panel with a transparent background, and Dock = DockStyle.Fill
  • PictureBox with a large Width and Height which shows scroll bars

Now all controls are set to DoubleBuffered including the form itself. Everything works as expected except when scrolling the Panel for the PictureBox, the form background image scrolls with it repeating itself showing vertical and horizontal tearing although its static image that fits the form's size, and when you stop scrolling it shows properly. This only happens when dragging the scrollbars, if i click on any point in the scrollbars to move it, it shows properly.

As per my understanding Double Buffering should eliminate such cases, but even with double buffering its the same, maybe a little bit better but still its a huge problem when scrolling.

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

Anthony
  • 9,451
  • 9
  • 45
  • 72
YazX
  • 444
  • 4
  • 12

3 Answers3

14

You are doing battle with a Windows system option, named "Show window content while dragging". It is turned on for all modern versions of Windows. Turning it off is not a realistic goal, since it is a system option it affects all windows of all apps. There is no back-door to selectively bypass this option.

With it enabled, the OS optimizes the scrolling of a window. It performs a fast bitblt to move the pixels in the video frame buffer and generates a paint message for only the part of the window that is revealed by the scroll. Like the bottom few rows of pixels when you scroll down. Underlying winapi call is ScrollWindowEx(). Intention is to provide an app with a more responsive UI, a lot less work has to be done to implement the scroll.

You can probably see where this is heading, ScrollWindowEx() also moves the pixels that were painted by the form's BackgroundImage. You can see that. Next thing you see is the side-effect of the optimized paint, it only redraws the part of the window that was revealed. So the moved background image pixels don't get redrawn. Looks like a "smearing" effect.

There is a simple workaround for that, just implement an event handler for the panel's Scroll event and call Invalidate(). So the entire panel gets redrawn again:

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

But now you'll notice the side-effect of the paint no longer being optimized. You still see the pixels getting moved, then overdrawn. How visible that is depends a great deal on how expensive the BackgroundImage is to draw. Usually never cheap because it doesn't have the optimal pixel format (32bppPArgb) and doesn't have the right size so needs to be rescaled to fit the window. The visual effect resembles the "pogo", rapid jittering on one edge of the panel.

Pretty unlikely you'll find that acceptable or want to do the work to optimize the BackgroundImage. Stopping ScrollWindowEx() from doing its job requires a pretty big weapon, you can pinvoke 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 pretty well, the background image pixels are now rock-steady. Any other pixels, well, not so much. Another visual effect, lets call it a "wrinkle". Getting rid of that artifact can be done by putting the window into compositing mode. Which double-buffers the entire window surface, including the child controls:

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

Only remaining artifact is the side-effect of this not being very 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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
0

It is not easy but it is doable and the following worked fine for me though took me 2 hours to discover:

First you need to make sure the column gets null value without the default 'null' icon, before add it to the grid:

    DataGridViewImageColumn imagecol = new DataGridViewImageColumn { ImageLayout = DataGridViewImageCellLayout.Stretch };
    imagecol.DefaultCellStyle.NullValue = null;
    grid.Columns.Add(imagecol);

Then you need to delete this column value for all rows within ANY event that resizes or moves the rows of that particular column (the example here is for scroll event):

private void DataGridViewScrollEventHandler(object sender, ScrollEventArgs e)
{
    if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
    {
        DataGridView grid = (DataGridView)sender;
        foreach (DataGridViewRow row in grid.Rows)
        {
            row.Cells[1].Value = null;
        }
    }
}

Finally you need to fill the image value again for all rows you delete it at paint event:

private void DataGridViewPaintEventHandler(object sender, PaintEventArgs e)
{
    DataGridView grid = (DataGridView)sender;
    foreach (DataGridViewRow row in grid.Rows)
    {
        row.Cells[1].Value = myImage;
    }
}

If you have a lot of rows you need to do this for the visible rows only for performance. There is a property for this so it would be doable.

0

The best solution is to set the form's background image again on the control scroll event

private void panel1_Scroll(object sender, ScrollEventArgs e) {
    /*
         Your Code if any exists
    */

    //reset the form's background image again in the scroll event
    this.BackgroundImage = Properties.Resources.your_background_image;
}
Mahbub Alam
  • 368
  • 2
  • 6