Custom Draw in ListView.
-
There's an excellent article here on CodeProject by Michael Dunn about intercepting NM_CUSTOMDRAW messages to modify the look of a ListView -- not as involved as OwnerDraw, though, which is nice. Here's the link: http://www.codeproject.com/listctrl/lvcustomdraw.asp[^] Unfortunately, the article is not written for C#. I was wondering how to hook into the message stream to intercept and act on NM_CUSTOMDRAW messages in C#. I asked at the article, but Michael does not know. I've seen the example code in the C# help on using OwnerDraw, but that of course requires handling almost all of the drawing chores. Can we, in C#, hook into the message stream and just do custom draw type stuff like described in Michael's excellent article? If so, how? What event handler do I need? Where do I declare it? Stuff like that. Or do I have to create a ListView derived class and override the WndProc? (which I'd rather not do if I can avoid it)
-
There's an excellent article here on CodeProject by Michael Dunn about intercepting NM_CUSTOMDRAW messages to modify the look of a ListView -- not as involved as OwnerDraw, though, which is nice. Here's the link: http://www.codeproject.com/listctrl/lvcustomdraw.asp[^] Unfortunately, the article is not written for C#. I was wondering how to hook into the message stream to intercept and act on NM_CUSTOMDRAW messages in C#. I asked at the article, but Michael does not know. I've seen the example code in the C# help on using OwnerDraw, but that of course requires handling almost all of the drawing chores. Can we, in C#, hook into the message stream and just do custom draw type stuff like described in Michael's excellent article? If so, how? What event handler do I need? Where do I declare it? Stuff like that. Or do I have to create a ListView derived class and override the WndProc? (which I'd rather not do if I can avoid it)
Unfortunately, you do have to derive from the class and subclass the window. Subclass the ListView[^], and handle the WM_NOTIFY[^] message. When
WM_NOTIFY
is received, convert the memory that thelParam
parameter points to to an NMHDR[^] instance, and check itscode
field. If it is set toNM_CUSTOMDRAW
, convert the same memory to an NMLVCUSTOMDRAW[^] structure, and work with it as described in Mike's article. -
Unfortunately, you do have to derive from the class and subclass the window. Subclass the ListView[^], and handle the WM_NOTIFY[^] message. When
WM_NOTIFY
is received, convert the memory that thelParam
parameter points to to an NMHDR[^] instance, and check itscode
field. If it is set toNM_CUSTOMDRAW
, convert the same memory to an NMLVCUSTOMDRAW[^] structure, and work with it as described in Mike's article.Wow! One of the best answers I've received here at CodeProject. Thanks a bunch for the links. I'll work through it and see what I can do. Thanks again.
-
Unfortunately, you do have to derive from the class and subclass the window. Subclass the ListView[^], and handle the WM_NOTIFY[^] message. When
WM_NOTIFY
is received, convert the memory that thelParam
parameter points to to an NMHDR[^] instance, and check itscode
field. If it is set toNM_CUSTOMDRAW
, convert the same memory to an NMLVCUSTOMDRAW[^] structure, and work with it as described in Mike's article.Hmm. I'm sure this is a totally rookie problem. I've done my fair share of casting in C++, but I'm pretty new to C#. How do I cast the lParam to my NMHDR struct in C#? In my class (which per the link you supplied is derived from NativeWindow), I have NMHDR defined:
private struct NMHDR { IntPtr hwndFrom; IntPtr idFrom; UInt32 code; }
Then in the overridden WndProc, I have:
//Check to see if it is a WM\_NOTIFY msg if (m.Msg == WM\_NOTIFY) { //Convert the lParam (within m) to NMHDR struct }
And since I am pretty new to C#, I don't know the syntax for this casting.
-
Hmm. I'm sure this is a totally rookie problem. I've done my fair share of casting in C++, but I'm pretty new to C#. How do I cast the lParam to my NMHDR struct in C#? In my class (which per the link you supplied is derived from NativeWindow), I have NMHDR defined:
private struct NMHDR { IntPtr hwndFrom; IntPtr idFrom; UInt32 code; }
Then in the overridden WndProc, I have:
//Check to see if it is a WM\_NOTIFY msg if (m.Msg == WM\_NOTIFY) { //Convert the lParam (within m) to NMHDR struct }
And since I am pretty new to C#, I don't know the syntax for this casting.
OK. I answered my own question. In case anyone else out there runs into this sort of problem, here's what I found (and it seems to be working):
//Check to see if it is a WM\_NOTIFY msg if (m.Msg == WM\_NOTIFY) { NMHDR nmHdr = new NMHDR(); nmHdr = (NMHDR)m.GetLParam(typeof(NMHDR)); if (nmHdr.code == NM\_CUSTOMDRAW) { Debug.WriteLine("Got here."); } }
But now I have a different question (there's always something): Where can I find all the info on the struct.s I need (including, but not necessarily limited to: NMHDR, NMCUSTOMDRAW and NMLVCUSTOMDRAW)? I can find them easily enough by searching MS KB, but is there a file somewhere that I can cut and paste from or just include in my code that already has all of this stuff defined (in C#)? Manually coding all of the structs is kind of a pain. Thanks.
-
OK. I answered my own question. In case anyone else out there runs into this sort of problem, here's what I found (and it seems to be working):
//Check to see if it is a WM\_NOTIFY msg if (m.Msg == WM\_NOTIFY) { NMHDR nmHdr = new NMHDR(); nmHdr = (NMHDR)m.GetLParam(typeof(NMHDR)); if (nmHdr.code == NM\_CUSTOMDRAW) { Debug.WriteLine("Got here."); } }
But now I have a different question (there's always something): Where can I find all the info on the struct.s I need (including, but not necessarily limited to: NMHDR, NMCUSTOMDRAW and NMLVCUSTOMDRAW)? I can find them easily enough by searching MS KB, but is there a file somewhere that I can cut and paste from or just include in my code that already has all of this stuff defined (in C#)? Manually coding all of the structs is kind of a pain. Thanks.
-
You may find what you need at PInvoke.net[^], but they don't have all of the Win32 structures defined.
Boy, I hope you're not getting irritated with this thread yet because I like learning stuff. I'm just wondering if you've ever actually attempted what I'm talking about here. Your original response, and the associated links, made it seem as though this might be fairly easy. Unfortunately, the more I learn about it, the harder it seems to be getting. I was thinking about writing an article discussing OwnerDraw vs CustomDraw to customize the look of a ListView, but the CustomDraw thing seems to be monumental. Just from the standpoint of defining all the structs involved: NMHDR is easy, NMCUSTOMDRAW is fairly straightforward but even that also involves a RECT structure; don't even get me started on NMLVCUSTOMDRAW which involves RECT and COLORREF which itself is fairly complex. I am persistently running into unhandled exceptions involving read/write to protected memory which I am figuring must be due to not having completely figured all the structs out yet. I retrieve the LParam using:
nmlvCust = (NMLVCUSTOMDRAW)m.GetLParam(typeof(NMLVCUSTOMDRAW));
and put it back into the LParam using:
Marshal.StructureToPtr(nmlvCust, m.LParam, true);
But somewhere along the chain of messages, at the GetLParam point, I get the access violation. If you have any suggestions or ideas, please offer. If not, then I figure I'll abandon this for now and perhaps come back to it some other time. Regardless, thanks for your input -- it has certainly pointed me into areas I've not explored. It's been pretty cool thus far. Thanks.
-
Boy, I hope you're not getting irritated with this thread yet because I like learning stuff. I'm just wondering if you've ever actually attempted what I'm talking about here. Your original response, and the associated links, made it seem as though this might be fairly easy. Unfortunately, the more I learn about it, the harder it seems to be getting. I was thinking about writing an article discussing OwnerDraw vs CustomDraw to customize the look of a ListView, but the CustomDraw thing seems to be monumental. Just from the standpoint of defining all the structs involved: NMHDR is easy, NMCUSTOMDRAW is fairly straightforward but even that also involves a RECT structure; don't even get me started on NMLVCUSTOMDRAW which involves RECT and COLORREF which itself is fairly complex. I am persistently running into unhandled exceptions involving read/write to protected memory which I am figuring must be due to not having completely figured all the structs out yet. I retrieve the LParam using:
nmlvCust = (NMLVCUSTOMDRAW)m.GetLParam(typeof(NMLVCUSTOMDRAW));
and put it back into the LParam using:
Marshal.StructureToPtr(nmlvCust, m.LParam, true);
But somewhere along the chain of messages, at the GetLParam point, I get the access violation. If you have any suggestions or ideas, please offer. If not, then I figure I'll abandon this for now and perhaps come back to it some other time. Regardless, thanks for your input -- it has certainly pointed me into areas I've not explored. It's been pretty cool thus far. Thanks.
David Fleming wrote:
Boy, I hope you're not getting irritated with this thread yet because I like learning stuff.
Nope! :-)
David Fleming wrote:
I'm just wondering if you've ever actually attempted what I'm talking about here. Your original response, and the associated links, made it seem as though this might be fairly easy. Unfortunately, the more I learn about it, the harder it seems to be getting.
The devil is in the details! ;) No, I've never attempted to do a custom-draw listview, but I've worked with notification messages before, and this is a pretty typical case for them. I've got a lot of experience with interop, which is why I make it sound easy... but it's only easy if you've dealt with this stuff a lot.
COLORREF
==int
. You can useColorTranslator.FromWin32()
to convert it to aColor
. YourNMLVCUSTOMDRAW
structure should look like this:[StructLayout(LayoutKind.Sequential)]
struct NMLVCUSTOMDRAW
{
NMCUSTOMDRAW customDraw;
int textColor;
int textBackColor;
int subItemIndex;
//**From here on, the members are only declared for Common Controls 6
// - Windows XP and up! Therefore, if they are included, you can only
//use this struct on XP and above!**
uint itemType;
int faceColor;
//You may want to define the following 4 as enum values for ease of use
int iconEffect;
int iconPhase;
int partID;
int stateID;
RECT textRect;
uint groupAlignment;
}NMCUSTOMDRAW
,RECT
, andNMHDR
can be found on PInvoke.net[^]. Let me know if this is the same as or different than what you are doing. If it is the same, then the problem must lie elsewhere besides the struct definitions. If you need to support versions before Common Controls 6, but still want to support the XP features when they're available, you can use aBinaryReader
instead of structures. I'll give you more on that if you want. -
David Fleming wrote:
Boy, I hope you're not getting irritated with this thread yet because I like learning stuff.
Nope! :-)
David Fleming wrote:
I'm just wondering if you've ever actually attempted what I'm talking about here. Your original response, and the associated links, made it seem as though this might be fairly easy. Unfortunately, the more I learn about it, the harder it seems to be getting.
The devil is in the details! ;) No, I've never attempted to do a custom-draw listview, but I've worked with notification messages before, and this is a pretty typical case for them. I've got a lot of experience with interop, which is why I make it sound easy... but it's only easy if you've dealt with this stuff a lot.
COLORREF
==int
. You can useColorTranslator.FromWin32()
to convert it to aColor
. YourNMLVCUSTOMDRAW
structure should look like this:[StructLayout(LayoutKind.Sequential)]
struct NMLVCUSTOMDRAW
{
NMCUSTOMDRAW customDraw;
int textColor;
int textBackColor;
int subItemIndex;
//**From here on, the members are only declared for Common Controls 6
// - Windows XP and up! Therefore, if they are included, you can only
//use this struct on XP and above!**
uint itemType;
int faceColor;
//You may want to define the following 4 as enum values for ease of use
int iconEffect;
int iconPhase;
int partID;
int stateID;
RECT textRect;
uint groupAlignment;
}NMCUSTOMDRAW
,RECT
, andNMHDR
can be found on PInvoke.net[^]. Let me know if this is the same as or different than what you are doing. If it is the same, then the problem must lie elsewhere besides the struct definitions. If you need to support versions before Common Controls 6, but still want to support the XP features when they're available, you can use aBinaryReader
instead of structures. I'll give you more on that if you want.The struct I had defined was pretty much the same except for the COLORREF parts. I had seen on PInvoke what you said about COLORREF being an int. The good news is that the memory exception went away when I fixed all the structs (using mine as well as your new one). The bad news is that it still isn't actually doing anything. I'll include my overridden WndProc for you to look at, and I'll make a couple of comments on it below:
protected override void WndProc(ref System.Windows.Forms.Message m) { //Always perform default functionality base.WndProc(ref m); //Check to see if it is a WM\_NOTIFY msg if (m.Msg == WM\_NOTIFY) { //NMHDR nmHdr = new NMHDR(); //nmHdr = (NMHDR)m.GetLParam(typeof(NMHDR)); nmlvCust = (NMLVCUSTOMDRAW)m.GetLParam(typeof(NMLVCUSTOMDRAW)); if (nmlvCust.nmcd.hdr.code == NM\_CUSTOMDRAW) { //Have windows do default drawing in case we don't make any changes m.Result = (IntPtr)CDRF\_DODEFAULT; Debug.WriteLine("In CustomDraw. dwDrawStage = " + nmlvCust.nmcd.dwDrawStage ". Result = " + m.Result); if (nmlvCust.nmcd.dwDrawStage == CDDS\_PREPAINT) { //We will notify windows that we want messages for each item m.Result = (IntPtr)CDRF\_NOTIFYITEMDRAW; Debug.WriteLine("In PrePaint. dwDrawStage = " + nmlvCust.nmcd.dwDrawStage + ". Result = " + m.Result); } else if (nmlvCust.nmcd.dwDrawStage == CDDS\_ITEMPREPAINT) { //We will notify windows that we want messages for each subitem m.Result = (IntPtr)CDRF\_NOTIFYSUBITEMDRAW; Debug.WriteLine("In ItemPrePaint. dwDrawStage = " + mlvCust.nmcd.dwDrawStage + ". Result = " + m.Result); } else if (nmlvCust.nmcd.dwDrawStage == (CDDS\_ITEMPREPAINT | CDDS\_SUBITEM)) { //Here's where we do our stuff if (nmlvCust.iSubItem == 0) { nmlvCust.clrText = ColorTranslator.ToWin32(SystemColors.Control); nmlvCust.clrTextBk = ColorTranslator.ToWin32(SystemColors.Info); }