Search code examples
c#winformstablelayoutobjectlistview

Black background and artifacts on ObjectListView when form gets focus


I have a multiple TableLayoutPanels on MDI forms. Each table layout has multiple controls (including ObjectListView) and only the top-most is set to visible.

I implemented the solution as given in this SO answer and it works great when opening a form for the first time, because there is no flickering on the layout's child controls whilst resizing.

I also added:

    protected override void OnShown(EventArgs e)
    {
        base.OnShown(e);

        foreach (CoTableLayoutPanel tlp in this.Controls)
        {
            if (tlp != null)
            {
                tlp.BeginUpdate();
                tlp.Size = firstLayout.Size;
                tlp.EndUpdate();
            }
        }
    }

I do the above so that when I switch layouts within the form they are already all the correct size and I avoid further flickering whilst resizing. This also works fine.

However, when I have an ObjectListView control on this derived table layout, and I switch between forms, the control is partially drawn (especially the border), a black background is shown quickly and also the last column is resized everytime (column's FillsFreeSpace property).

If I use the standard TableLayoutPanel, the list control behaves as expected, ie. no artifacts, no black background shown, no column resizing. However, I get the flickering back when I open the form.

The ObjectListView has properties such as Invalidate and Refresh, and I've tried to call these on the form's OnGotFocus method. The problem still persists.

Is this a problem of the ObjectListView or can I fix the problem from within the derived table layout?

EDIT

The problem is caused by this method:

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= WS_EX_COMPOSITED;
        return cp;
    }
}

Commenting out this method will make the ObjectListView work as expected but the flickering returns.

Any workaround to the above method?


Solution

  • What appears as black is just the unpainted part of the window. If you have a lot of controls on a form then repainting them all takes enough time to produce noticeable artifacts. It is black when you use a layered window, you get one when you use the Opacity or TransparencyKey property. If you don't then the artifact tends to be a lot less objectionable because the unpainted parts tend to be white.

    Most programmers assume it is a flicker issue, but it is not and the DoubleBuffered property cannot solve it. Suppressing it would require double-buffering all of the controls, using the same buffer. Roughly the approach taken by WPF.

    Double-buffering everything is what the WS_EX_COMPOSITED style flag does. Purely done by the operating system, .NET is not involved. It is an early version of Aero, first available on XP. The OS creates a bitmap for the toplevel window and tells its controls to draw into that bitmap instead of directly to the video framebuffer. When the painting is done, it blits the bitmap in one whack to the framebuffer. Does not make the painting any faster, but the user perceives it as very smooth. If you use the DoubleBuffered property now then you want to turn that off.

    I discovered the technique for Winforms btw, published it first in a forum post on the MSDN forums, 10 years ago already. It has been copied many, many times since, spread like a wildfire. I have not gotten a lot of negative feedback about it, it solves the problem for the vast majority of programmers. The only troublemaker I know of is TabControl, specifically when it has too many tabs and displays the left/right navigation glyphs. Its visual styles renderer has never not been a problem, when those glyphs show up, it start repainting itself over-and-over again. Looks like a very rapid flicker, you can't miss it. Easy to work around.

    So no, forge ahead and use my solution. It is a good one and doing it any other way would be highly untrivial. You'd basically have to re-invent WPF to completely eliminate it, using windowless controls that draw themselves on the parent window's surface. VB6 did this btw, one reason why VB6 UI looks so dated. Replacing controls with code can get you there too (Label and PictureBox are particularly wasteful) but that tends to take a lot of code and you cannot possibly beat the 3 lines of code solution :)