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.