Current version

v1.10.4 (stable)

Navigation

Main page
Archived news
Downloads
Documentation
   Capture
   Compiling
   Processing
   Crashes
Features
Filters
Plugin SDK
Knowledge base
Contact info
Forum
 
Other projects
   Altirra

Search

Archives

01 Dec - 31 Dec 2013
01 Oct - 31 Oct 2013
01 Aug - 31 Aug 2013
01 May - 31 May 2013
01 Mar - 31 Mar 2013
01 Feb - 29 Feb 2013
01 Dec - 31 Dec 2012
01 Nov - 30 Nov 2012
01 Oct - 31 Oct 2012
01 Sep - 30 Sep 2012
01 Aug - 31 Aug 2012
01 June - 30 June 2012
01 May - 31 May 2012
01 Apr - 30 Apr 2012
01 Dec - 31 Dec 2011
01 Nov - 30 Nov 2011
01 Oct - 31 Oct 2011
01 Sep - 30 Sep 2011
01 Aug - 31 Aug 2011
01 Jul - 31 Jul 2011
01 June - 30 June 2011
01 May - 31 May 2011
01 Apr - 30 Apr 2011
01 Mar - 31 Mar 2011
01 Feb - 29 Feb 2011
01 Jan - 31 Jan 2011
01 Dec - 31 Dec 2010
01 Nov - 30 Nov 2010
01 Oct - 31 Oct 2010
01 Sep - 30 Sep 2010
01 Aug - 31 Aug 2010
01 Jul - 31 Jul 2010
01 June - 30 June 2010
01 May - 31 May 2010
01 Apr - 30 Apr 2010
01 Mar - 31 Mar 2010
01 Feb - 29 Feb 2010
01 Jan - 31 Jan 2010
01 Dec - 31 Dec 2009
01 Nov - 30 Nov 2009
01 Oct - 31 Oct 2009
01 Sep - 30 Sep 2009
01 Aug - 31 Aug 2009
01 Jul - 31 Jul 2009
01 June - 30 June 2009
01 May - 31 May 2009
01 Apr - 30 Apr 2009
01 Mar - 31 Mar 2009
01 Feb - 29 Feb 2009
01 Jan - 31 Jan 2009
01 Dec - 31 Dec 2008
01 Nov - 30 Nov 2008
01 Oct - 31 Oct 2008
01 Sep - 30 Sep 2008
01 Aug - 31 Aug 2008
01 Jul - 31 Jul 2008
01 June - 30 June 2008
01 May - 31 May 2008
01 Apr - 30 Apr 2008
01 Mar - 31 Mar 2008
01 Feb - 29 Feb 2008
01 Jan - 31 Jan 2008
01 Dec - 31 Dec 2007
01 Nov - 30 Nov 2007
01 Oct - 31 Oct 2007
01 Sep - 30 Sep 2007
01 Aug - 31 Aug 2007
01 Jul - 31 Jul 2007
01 June - 30 June 2007
01 May - 31 May 2007
01 Apr - 30 Apr 2007
01 Mar - 31 Mar 2007
01 Feb - 29 Feb 2007
01 Jan - 31 Jan 2007
01 Dec - 31 Dec 2006
01 Nov - 30 Nov 2006
01 Oct - 31 Oct 2006
01 Sep - 30 Sep 2006
01 Aug - 31 Aug 2006
01 Jul - 31 Jul 2006
01 June - 30 June 2006
01 May - 31 May 2006
01 Apr - 30 Apr 2006
01 Mar - 31 Mar 2006
01 Feb - 29 Feb 2006
01 Jan - 31 Jan 2006
01 Dec - 31 Dec 2005
01 Nov - 30 Nov 2005
01 Oct - 31 Oct 2005
01 Sep - 30 Sep 2005
01 Aug - 31 Aug 2005
01 Jul - 31 Jul 2005
01 June - 30 June 2005
01 May - 31 May 2005
01 Apr - 30 Apr 2005
01 Mar - 31 Mar 2005
01 Feb - 29 Feb 2005
01 Jan - 31 Jan 2005
01 Dec - 31 Dec 2004
01 Nov - 30 Nov 2004
01 Oct - 31 Oct 2004
01 Sep - 30 Sep 2004
01 Aug - 31 Aug 2004

Stuff

Powered by Pivot  
XML: RSS feed 
XML: Atom feed 

§ Why System.Windows.Forms.ListView sucks and how to fix it

It seems that a perpetual affliction of .NET WinForms-based applications is slow and flickery repainting. Part of the problem is .NET's insistence on using GDI+, which is not hardware accelerated to any useful extent. That still doesn't explain why so many controls flicker all of the time, even though they're based on Win32 controls that don't have the same problem. Today I hit this problem yet again in a tool, this time with ListView. It drives me absolutely nuts to see a system with a 3GHz Core 2 and a GeForce 8800 take four seconds to redraw a list view that has three columns and a hundred entries when I drag a column, and even worse, flicker the entire time.

Therefore, I had to sit down tonight and figure out how you could make a standard Win32 ListView update so slowly that a 1541 drive could almost keep up with it.

(Caveat: As usual, I do my primary work in XP. I'm too lazy to reboot into Windows 7 right now.)

The way I ended up debugging this involved parallel C++ and C# apps. Both were fairly vanilla apps made using the built-in app wizards, the C++ one containing a dialog with a list view, and the C# one being the same but with a WinForm. Okay, I'll admit that the C++ one was more annoying to write, because programming a Win32 list view directly is a lot of gruntwork. However, out of the box, the C++ app updated much more smoothly and didn't flicker madly. I'll spare you the debugging details -- which include ILDASM, WinDbg, Spy++, two instances of Visual Studio, and tracepoints in x86 assembly while debugging in mixed mode -- but I managed to figure out what was going on. The WinForms ListView is indeed a Win32 ListView with heavy subclassing, but it turns out the poor performance is caused by two bad design decisions on the part of the WinForms team:

  1. The Win32 list view is always in owner draw mode. Always. Even if you don't have OwnerDraw set in the control. Specifically, the WinForms ListView intercepts WM_NOTIFY + NM_CUSTOMDRAW and handles the item painting itself. In doing so, it ends up creating and destroying a lot of GDI+ contexts, and that kills redraw performance, just like we've seen with DataGridView.
  2. In its OnHandleCreated handler, ListView sets the text background color to transparent (ListView_SetTextBkColor(hwnd, CLR_NONE)). As it turns out, this kills the fast path in the Win32 list view code and switches it from incremental painting in opaque mode to a full erase + redraw over the entire control. You can spot the difference if you set a breakpoint on {,,user32}_NtUserRedrawWindow@16.

Both of these are fixable -- the first problem can be fixed by intercepting NM_CUSTOMDRAW and forcing it to return 0, thus restoring the built-in redraw code, and the second one by sending another LVM_SETTEXTBKCOLOR message to restore an opaque background color. With these two fixes, the C# app runs as smoothly as the C++ app. I don't know why the WinForms team chose such poor defaults.

Here's the code, in case anyone's interested in the details:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace cslistview
{
    public partial class Form1 : Form
    {
        [DllImport("user32")]
        private static extern bool SendMessage(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);

        private uint LVM_SETTEXTBKCOLOR = 0x1026;

        ListView lv;
        public Form1()
        {
            InitializeComponent();

            lv = new ListViewWithLessSuck();
            lv.Dock = DockStyle.Fill;
            lv.View = View.Details;
            Controls.Add(lv);

            for(int i=0; i<3; ++i)
                lv.Columns.Add("Column");

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 100; ++i)
                sb.Append((char)('A' + (i % 26)));

            for (int i = 0; i < 200; ++i)
            {
                ListViewItem lvi = new ListViewItem();
                lvi.Text = sb.ToString();
                lvi.SubItems.Add("0");
                lvi.SubItems.Add("0");
                lv.Items.Add(lvi);
            }

            this.Load += new EventHandler(Form1_Load);
        }

        void Form1_Load(object sender, EventArgs e)
        {
            SendMessage(lv.Handle, LVM_SETTEXTBKCOLOR, IntPtr.Zero, unchecked((IntPtr)(int)0xFFFFFF));
        }
    }

    class ListViewWithLessSuck : ListView
    {
        [StructLayout(LayoutKind.Sequential)]
        private struct NMHDR
        {
            public IntPtr hwndFrom;
            public uint idFrom;
            public uint code;
        }

        private const uint NM_CUSTOMDRAW = unchecked((uint)-12);

        protected override void  WndProc(ref Message m)
        {
            if (m.Msg == 0x204E)
            {
                NMHDR hdr = (NMHDR)m.GetLParam(typeof(NMHDR));
                if (hdr.code == NM_CUSTOMDRAW)
                {
                    m.Result = (IntPtr)0;
                    return;
                }
            }

            base.WndProc(ref m);
        }
    }
}

Comments

Comments posted:


Custom draw (WM_NOTIFY, NM_CUSTOMDRAW) != Owner draw (LVS_OWNERDRAWFIXED, WM_DRAWITEM)

pingpong - 15 09 09 - 22:18


Use a DataGridView instead, it sucks much less.

leppie - 16 09 09 - 00:41


AFAIK there's no GDI+ used within Windows.Forms at all. Certainly all of the standard control classes are simply wrappers around underlying windows API controls. But yes, GDI+ is very slow and thus effectively useless for use in professional interative controls or animation. Not that it isn't used in expensive software suites, Infragistics being a notable example I'm aware of (redraw speed is appalling).

It can be used but it's a poor solution IMHO.

locster - 16 09 09 - 02:19


fortunately wpf finally becomes usable in .net 4 thanks to proper text rendering.

tobi - 16 09 09 - 02:36


You can also get relatively good performance out of a .NET ListView if you set these styles:

this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);

And then do your own painting using your own backbuffer. A backbuffer the size of the entire list is expensive and sluggish on resize, so I use a buffer that's the size of a single list item, and render items to it one at a time so that they don't flicker on repaint.

Probably not nearly as fast as a fully native ListView but it at least lets you keep owner-draw and get rid of flicker.

Kevin Gadd (link) - 16 09 09 - 05:15


I too hate the flicker. Best and easiest way to really fix this issue is to create a buffered listview like so.

You will be amazed at the perf increase

public class BufferedListView : ListView
{
public BufferedListView() : base()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
}

Scott (link) - 16 09 09 - 06:33


DataGridView is more powerful than ListView, but in my experience its performance is awful due to similar painting problems. The ListView is OK enough that you could still use it, but I don't consider the DataGridView to be usable in shipping code. It's simply unacceptable IMO to have a window take more than four seconds to repaint.

Windows.Forms does use GDI+. In fact, it's the library behind the Graphics class. It's true that the system controls don't use GDI+, but the WinForms wrappers that are based on them do heavy amounts of subclassing and at times substitute System.Drawing.Graphics based rendering for the normal GDI-based system rendering. That's the main cause of the problem here and why you see rendering bugs in the WinForms controls that don't manifest in the system controls. The situation changed somewhat in .NET 2.0 when WinForms started using GDI via TextRenderer to bypass text rendering problems in GDI+, but unfortunately that causes problems where alternating between GDI and GDI+ for rendering produces poor performance.

As for double buffering, that's the lazy way. It works somewhat and is sometimes easy to do, but in my experience the refresh rate is still poor and CPU usage is high if the update code is still non-incremental. When writing a control from scratch, I always put in incremental update code first before resorting to double buffering of any kind. It's the only way you can hit interactive scrolling speeds and it's also much better over a remote link.

Phaeron - 16 09 09 - 15:44


Hey Phaeron, does the Mono implementation of System.Windows.Forms.ListView have the same issue as the Microsoft .NET Framework implementation?

King InuYasha (link) - 16 09 09 - 17:51


Wow... what a /mess/ the Mono repository is!

The Mono implementation of ListView has very different performance characteristics because it is implemented as custom UI elements on top of a cross-platform drawing/theming layer. That's kind of cool in that you can retarget it much more easily and you aren't limited by crappy platform UI libraries. The downside is the risk that your UI doesn't match the native look and feel and lacks platform specific features (see also: Swing). As such, it definitely isn't affected by the peculiarities of the Win32 control like the .NET version. In terms of column scrolling, I'm afraid that the Mono implementation of ListView doesn't attempt to do anything special and simply invalidates the whole control (Redraw(true)). Whether or not it flickers, I couldn't say without actually running it; simply drawing a row at a time would largely avoid the problem even without double-buffering, as the flicker window is then limited to a single cell. In fact, it appears to just redraw itself any time any change to the item collection occurs, which is a bit disappointing. However, it does at least use graphics layer scrolling commands when scrolled.

Phaeron - 16 09 09 - 18:36


That's pretty slick except MS is all into WPF. Too bad because WinForms could have been a decent API to visually design form apps using Win32 controls.
I think WinForms was mainly produced to counter Borland's Delphi/Builder RAD products (they hired the same guy too). Oh well.

Rich - 17 09 09 - 16:00


WPF does fix a number of problems with WinForms, but has a couple of issues on its own:

1) The acronym only has a Hamming distance of 1 from WTF. Newbie mistake.
2) Too much of an emphasis on device independent rendering. Device independent rendering is good when you're drawing a map, not so much when you want a high-quality desktop UI.

As far as I'm concerned, Visual Studio 2010 is kind of the make-or-break for WPF -- they've been forced to fix long standing problems like the text rendering quality, and if Microsoft can't make it work well, it'll cast some doubt on WPF's usability for production applications.

Phaeron - 17 09 09 - 17:19


WPF was supposed to be the next great thing in the Windows API world, finally MS gets to ditch Win32. It came to being for both web app and desktop app development, complete with fancy graphics to compete even with
Flash. Well, didn't happen, everyone still used Flash and there were plenty of holdouts in the desktop (XP and below). Heck, even I agree that Win32 still works.
In any case, WinForms and Win32 aren't going away just yet, three cheers for legacy apps, LOL.

Rich - 18 09 09 - 05:03


Dig thru the listview code sometime with reflector and you'll see some amazing things relating to icon lists. You'll see how adding one icon can create 4 or 5 allocations and copies. How loading an ICO file gets converted to a bitmap, and then BACK to an icon. There is a line that essentially says "if large icon view, reload the entire icon list every time one is added".

Greg - 21 09 09 - 00:21


@Phaeron: Thanks for some really useful information on building high performance custom controls. I'm building my own special purpose listview and your tips helped a lot. I particularly like your point about adding double buffering only after everything else has been done to ensure high performance.

Avery (link) - 17 06 10 - 19:12


@Phaeron: Yes, great stuff, and I share your philosophy on how to get drawing fast and flicker-free. I recently spent two days trying to get fast, clean, flicker-free screen updates as I resized a window that had a several child windows. The best step I took was to start by *removing* all of the "helpful stuff" I had added to try to reduce flicker. I could then see what the original problems were, and when I started adding helpers back in, I got to flicker-free with almost none of them. I did add the offscreen bitmap (double buffering) for a graph I was drawing, but only after I had cleaned up everything else. On another page of the property sheet, I'm going to have to replace the Win32 edit control, because it flickers an amazing amount. With 10 lines of text, it will repaint 28 times on one resize. Ouch!

Tad Marshall (link) - 11 06 11 - 14:54

Comment form


Please keep comments on-topic for this entry. If you have unrelated comments about VirtualDub, the forum is a better place to post them.
Name:  
Remember personal info?

Email (Optional):
Your email address is only revealed to the blog owner and is not shown to the public.
URL (Optional):
Comment: /

An authentication dialog may appear when you click Post Comment. Simply type in "post" as the user and "now" as the password. I have had to do this to stop automated comment spam.



Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.