Unified Diff

Custom Draw ListView with Double Buffering

by Bob on 30 May 2008, under .NET, C++/CLI, How To, Win32 & MFC, Windows

For one of my pet projects, I needed to add a graphic to certain ListView rows. I considered owner-drawing the control, but that seemed messy. What I really wanted was a way to draw on top of whatever Windows painted. I couldn’t find a nifty .NET way to do this, so remembering a technique from the MFC days, I decided to custom draw the ListView. This would let me inject my own drawing code and let Windows handle the rest. Perfect!

I had originally implemented the ListView using a control style to eliminate the annoying flicker every time the list was repainted. Once I started custom drawing the control, I saw odd artifacts in the ListView’s client area when I scrolled or moved the mouse. If I turned off double buffering, it worked fine.

I studied the custom draw process looking for my mistake, but I couldn’t find any. I even used the .NET Reflector to look at Microsoft’s implementation. The message I’m filtering has a handle to the control’s back-buffer. So it seemed like I should just be able to draw directly into that. But no matter what I did, it produced the same artifacts.

After about three days of banging my head, I managed to make it work by drawing into my own buffer and then using BitBlt() to copy my image to the control’s buffer. I also had to set the double buffer style with a window message rather than using the built-in property, although admittedly, I’m not sure why.

I like visual aids, so here’s a code snippet from my project. I changed names and removed a bunch of unnecessary lines for easier reading.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include "stdafx.h"
#include "MyListViewControl.h"
 
using namespace MyListViewControl;
using namespace System::Drawing;
using namespace System::Windows::Forms;
 
MyListViewControl::MyListViewControl() { }
 
bool MyListViewControl::CustomDraw( Message% m )
{
	System::Drawing::Rectangle^ mprc;
	System::String^ cchQueueIndex;
	ListViewItem^ mpItem;
	LPNMCUSTOMDRAW lpnmcd;
	Graphics^ mpgfx;
	HDC hmemdc;
	RECT sOrderRect;
	RECT sStopRect;
	Brush^ mpbgbr;
	Brush^ mpfgbr;
	int nOrderLen;
	int nOrderOffset;
	int nRightEdge;
	int nRectWidth;
	int nItem;
 
	// Grab the custom draw info
	lpnmcd = (LPNMCUSTOMDRAW) m.LParam.ToPointer();
 
	// Figure out which drawing stage we're in
	switch( lpnmcd->dwDrawStage )
	{	
 
	// Control Pre-paint
	case( CDDS_PREPAINT ):
 
		// Request to be notified during item draw
		__super::WndProc( m );
		m.Result = (System::IntPtr) CDRF_NOTIFYITEMDRAW;
		return true;
 
 
	// List Item Pre-paint
	case( CDDS_ITEMPREPAINT ):
 
		// Request to be notified after item draw
		__super::WndProc( m );
		m.Result = (System::IntPtr) CDRF_NOTIFYPOSTPAINT;
		return true;
 
 
	// List Item Post-paint
	case( CDDS_ITEMPOSTPAINT ):
 
		__super::WndProc( m );
 
		// Get the current item
		nItem = static_cast<int>( lpnmcd->dwItemSpec );
		mpItem = (ListViewItem^) this->Items[nItem];		
 
		// Get the device context and item bounds
		mpgfx = this->CreateGraphics();
		mprc = this->GetItemRect( nItem, ItemBoundsPortion::Label );
 
		// *** Your custom drawing code here ***
 
		// Copy the bubble to the screen DC
		hmemdc = reinterpret_cast<HDC>( static_cast<void*>(mpgfx->GetHdc()) );
		BitBlt( 
			lpnmcd->hdc, 
			sOrderRect.left, 
			mprc->Y, 
			nRectWidth, 
			mprc->Height, 
			hmemdc, 
			sOrderRect.left, 
			mprc->Y, 
			SRCCOPY );
		mpgfx->ReleaseHdc( (System::IntPtr) hmemdc );
 
		// Let Windows redraw the border
		m.Result = (System::IntPtr) CDRF_DODEFAULT;
		return true;
	}
 
	return false;
}
 
void MyListViewControl::WndProc( Message% m )
{
	LPNMHDR lpnmhdr;
 
	switch( m.Msg )
	{
 
	// Reflected Notifications
	case( OCM_NOTIFY ):
 
		// Get the notification
		lpnmhdr = (LPNMHDR) m.LParam.ToPointer();
		if( lpnmhdr == NULL ) break;
 
		// We want custom draw messages
		if( lpnmhdr->code == NM_CUSTOMDRAW  
			&& lpnmhdr->hwndFrom == this->Handle.ToPointer() )
		{
			if( this->CustomDraw(m) ) return;
		}
 
		break;
 
	}
 
	__super::WndProc( m );
}

And here’s the header file with the double buffering message call:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#pragma once
 
namespace MyListView
{
	public ref class MyListView : 
		public System::Windows::Forms::ListView
	{
 
	private:
		bool CustomDraw( System::Windows::Forms::Message% m );
 
	public:
		virtual property bool DoubleBuffered
		{
			bool get() override
			{
				LRESULT lStyles = ::SendMessage( 
					(HWND) this->Handle.ToPointer(), 
					LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0 );
				return (lStyles & LVS_EX_DOUBLEBUFFER) != 0;
			}
 
			void set( bool fValue ) override
			{
				// I tried using the DoubleBuffered property, but I
				// get artifacts. So, let's do this the MFC way...
				::SendMessage( 
					(HWND) this->Handle.ToPointer(), 
					LVM_SETEXTENDEDLISTVIEWSTYLE, 
					LVS_EX_DOUBLEBUFFER, 
					LVS_EX_DOUBLEBUFFER );
			}
		}
 
	protected:
		virtual void WndProc( System::Windows::Forms::Message% m ) override;
 
	public:
		MyListViewControl();
 
	};
}

I spend days searching online and couldn’t find anyone who talked about custom drawing a .NET ListView with double buffering. I hope somebody will find this useful.


Leave a Reply

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!