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.

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:

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.

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.

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 increasepublic class BufferedListView : ListViewpublic 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 withFlash. 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!