protected virtual bool HandleCustomDraw(ref Message m)
{
const int CDDS_PREPAINT = 1;
const int CDDS_POSTPAINT = 2;
const int CDDS_PREERASE = 3;
const int CDDS_POSTERASE = 4;
//const int CDRF_NEWFONT = 2;
//const int CDRF_SKIPDEFAULT = 4;
const int CDDS_ITEM = 0x00010000;
const int CDDS_SUBITEM = 0x00020000;
const int CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT);
const int CDDS_ITEMPOSTPAINT = (CDDS_ITEM | CDDS_POSTPAINT);
const int CDDS_ITEMPREERASE = (CDDS_ITEM | CDDS_PREERASE);
const int CDDS_ITEMPOSTERASE = (CDDS_ITEM | CDDS_POSTERASE);
const int CDDS_SUBITEMPREPAINT = (CDDS_SUBITEM | CDDS_ITEMPREPAINT);
const int CDDS_SUBITEMPOSTPAINT = (CDDS_SUBITEM | CDDS_ITEMPOSTPAINT);
const int CDRF_NOTIFYPOSTPAINT = 0x10;
//const int CDRF_NOTIFYITEMDRAW = 0x20;
//const int CDRF_NOTIFYSUBITEMDRAW = 0x20; // same value as above!
const int CDRF_NOTIFYPOSTERASE = 0x40;
// There is a bug in owner drawn virtual lists which causes lots of custom draw messages
// to be sent to the control *outside* of a WmPaint event. AFAIK, these custom draw events
// are spurious and only serve to make the control flicker annoyingly.
// So, we ignore messages that are outside of a paint event.
if (!this.isInWmPaintEvent)
return true;
// One more complication! Sometimes with owner drawn virtual lists, the act of drawing
// the overlays triggers a second attempt to paint the control -- which makes an annoying
// flicker. So, we only do the custom drawing once per WmPaint event.
if (!this.shouldDoCustomDrawing)
return true;
NativeMethods.NMLVCUSTOMDRAW nmcustomdraw = (NativeMethods.NMLVCUSTOMDRAW)m.GetLParam(typeof(NativeMethods.NMLVCUSTOMDRAW));
//System.Diagnostics.Debug.WriteLine(String.Format("cd: {0:x}, {1}, {2}", nmcustomdraw.nmcd.dwDrawStage, nmcustomdraw.dwItemType, nmcustomdraw.nmcd.dwItemSpec));
// Ignore drawing of group items
if (nmcustomdraw.dwItemType == 1) {
// This is the basis of an idea about how to owner draw group headers
//nmcustomdraw.clrText = ColorTranslator.ToWin32(Color.DeepPink);
//nmcustomdraw.clrFace = ColorTranslator.ToWin32(Color.DeepPink);
//nmcustomdraw.clrTextBk = ColorTranslator.ToWin32(Color.DeepPink);
//Marshal.StructureToPtr(nmcustomdraw, m.LParam, false);
//using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) {
// g.DrawRectangle(Pens.Red, Rectangle.FromLTRB(nmcustomdraw.rcText.left, nmcustomdraw.rcText.top, nmcustomdraw.rcText.right, nmcustomdraw.rcText.bottom));
//}
//m.Result = (IntPtr)((int)m.Result | CDRF_SKIPDEFAULT);
return true;
}
switch (nmcustomdraw.nmcd.dwDrawStage) {
case CDDS_PREPAINT:
//System.Diagnostics.Debug.WriteLine("CDDS_PREPAINT");
// Remember which items were drawn during this paint cycle
if (this.prePaintLevel == 0)
this.drawnItems = new List<OLVListItem>();
// If there are any items, we have to wait until at least one has been painted
// before we draw the overlays. If there aren't any items, there will never be any
// item paint events, so we can draw the overlays whenever
this.isAfterItemPaint = (this.GetItemCount() == 0);
this.prePaintLevel++;
base.WndProc(ref m);
// Make sure that we get postpaint notifications
m.Result = (IntPtr)((int)m.Result | CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYPOSTERASE);
return true;
case CDDS_POSTPAINT:
//System.Diagnostics.Debug.WriteLine("CDDS_POSTPAINT");
this.prePaintLevel--;
// When in group view, we have two problems. On XP, the control sends
// a whole heap of PREPAINT/POSTPAINT messages before drawing any items.
// We have to wait until after the first item paint before we draw overlays.
// On Vista, we have a different problem. On Vista, the control nests calls
// to PREPAINT and POSTPAINT. We only want to draw overlays on the outermost
// POSTPAINT.
if (this.prePaintLevel == 0 && (this.isMarqueSelecting || this.isAfterItemPaint)) {
this.shouldDoCustomDrawing = false;
// Draw our overlays after everything has been drawn
using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) {
this.DrawAllDecorations(g, this.drawnItems);
}
}
break;
case CDDS_ITEMPREPAINT:
//System.Diagnostics.Debug.WriteLine("CDDS_ITEMPREPAINT");
// When in group view on XP, the control send a whole heap of PREPAINT/POSTPAINT
// messages before drawing any items.
// We have to wait until after the first item paint before we draw overlays
this.isAfterItemPaint = true;
// This scheme of catching custom draw msgs works fine, except
// for Tile view. Something in .NET's handling of Tile view causes lots
// of invalidates and erases. So, we just ignore completely
// .NET's handling of Tile view and let the underlying control
// do its stuff. Strangely, if the Tile view is
// completely owner drawn, those erasures don't happen.
if (this.View == View.Tile) {
if (this.OwnerDraw && this.ItemRenderer != null)
base.WndProc(ref m);
} else {
base.WndProc(ref m);
}
m.Result = (IntPtr)((int)m.Result | CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYPOSTERASE);
return true;
case CDDS_ITEMPOSTPAINT:
//System.Diagnostics.Debug.WriteLine("CDDS_ITEMPOSTPAINT");
// Remember which items have been drawn so we can draw any decorations for them
// once all other painting is finished
if (this.Columns.Count > 0) {
OLVListItem olvi = this.GetItem((int)nmcustomdraw.nmcd.dwItemSpec);
if (olvi != null)
this.drawnItems.Add(olvi);
}
break;
case CDDS_SUBITEMPREPAINT:
//System.Diagnostics.Debug.WriteLine(String.Format("CDDS_SUBITEMPREPAINT ({0},{1})", (int)nmcustomdraw.nmcd.dwItemSpec, nmcustomdraw.iSubItem));
// There is a bug in the .NET framework which appears when column 0 of an owner drawn listview
// is dragged to another column position.
// The bounds calculation always returns the left edge of column 0 as being 0.
// The effects of this bug become apparent
// when the listview is scrolled horizontally: the control can think that column 0
// is no longer visible (the horizontal scroll position is subtracted from the bounds, giving a
// rectangle that is offscreen). In those circumstances, column 0 is not redraw because
// the control thinks it is not visible and so does not trigger a DrawSubItem event.
// To fix this problem, we have to detected the situation -- owner drawing column 0 in any column except 0 --
// trigger our own DrawSubItem, and then prevent the default processing from occuring.
// Are we owner drawing column 0 when it's in any column except 0?
if (!this.OwnerDraw)
return false;
int columnIndex = nmcustomdraw.iSubItem;
if (columnIndex != 0)
return false;
int displayIndex = this.Columns[0].DisplayIndex;
if (displayIndex == 0)
return false;
int rowIndex = (int)nmcustomdraw.nmcd.dwItemSpec;
OLVListItem item = this.GetItem(rowIndex);
if (item == null)
return false;
// OK. We have the error condition, so lets do what the .NET framework should do.
// Trigger an event to draw column 0 when it is not at display index 0
using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) {
// Correctly calculate the bounds of cell 0
Rectangle r = item.GetSubItemBounds(0);
// We can hardcode "0" here since we know we are only doing this for column 0
DrawListViewSubItemEventArgs args = new DrawListViewSubItemEventArgs(g, r, item, item.SubItems[0], rowIndex, 0,
this.Columns[0], (ListViewItemStates)nmcustomdraw.nmcd.uItemState);
this.OnDrawSubItem(args);
// If the event handler wants to do the default processing (i.e. DrawDefault = true), we are stuck.
// There is no way we can force the default drawing because of the bug in .NET we are trying to get around.
System.Diagnostics.Trace.Assert(!args.DrawDefault, "Default drawing is impossible in this situation");
}
m.Result = (IntPtr)4;
return true;
case CDDS_SUBITEMPOSTPAINT:
//System.Diagnostics.Debug.WriteLine("CDDS_SUBITEMPOSTPAINT");
break;
// I have included these stages, but it doesn't seem that they are sent for ListViews.
// http://www.tech-archive.net/Archive/VC/microsoft.public.vc.mfc/2006-08/msg00220.html
case CDDS_PREERASE:
//System.Diagnostics.Debug.WriteLine("CDDS_PREERASE");
break;
case CDDS_POSTERASE:
//System.Diagnostics.Debug.WriteLine("CDDS_POSTERASE");
break;
case CDDS_ITEMPREERASE:
//System.Diagnostics.Debug.WriteLine("CDDS_ITEMPREERASE");
break;
case CDDS_ITEMPOSTERASE:
//System.Diagnostics.Debug.WriteLine("CDDS_ITEMPOSTERASE");
break;
}
return false;
}