Hi; back again after just a few weeks hiatus and the place looks completely different!
Anyhow, I'm trying to set the text of "items" in a list-view control without any success. Using Microsoft's rather convoluted nomenclature, the "items" in a listview are what I would call the "header" row cells, while "subitems" are everything below this (i.e., the contents of each column).
I can easily set text colors for "subitems", by capturing the NM_CUSTOMDRAW notification and responding appropriately. This is illustrated in the attached test program.
But no matter what I do, I can only seem to affect the color of subitems (contents of cells below the first, "header" row).
First of all, I can set the color of all text by using the LVM_SETTEXTCOLOR message. This sets the color globally to a single color value.
Setting the color of individual subitems is a little trickier. You need to respond to different flavors of NM_CUSTOMDRAW notifications differently. When you receive such a notification with the dwDrawStage field set to CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM, then you can set the text color (simply by stuffing the clrText field of the NMLVCUSTOMDRAW structure with the desired color. With me so far?
I set up my little demo program to log all the NM_CUSTOMDRAW notifications. Here's what I get when I run it:
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM)
NM_CUSTOMDRAW notification (dwDrawStage=CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM)
This seems to be what the MSDN documentation says I should expect (except that for each subitem notification, both CDDS_ITEM and CDDS_SUBITEM are set, which isn't what they say should happen.)
What I need is for someone with more knowledge than I about these controls to guide me.
One thing I discovered, using WinSpy++ (very useful tool) is that the header row of a list view control is actually a separate control. Where the listview is of class SysListView32, the header is of class SysHeader32. Perhaps this explains why all operations seem to be able to affect only the text color of everything except the header row ...
There's another small thing, which may be due to my misunderstanding of how to set up listviews. In my little demo app I create a 3x3 listview (excluding header). But instead of getting 3 columns, I get 4, the last of which extends to the right edge of the control, no matter how wide I make it. Is this the way they're supposed to work? I find that I can make it look like 3 columns if I carefully adjust the overall width of the control, but this seems like a kluge to me. Am I missing something here?
They're very useful controls, but man do they have a lot of "quirks"!
Thanks in advance for any help here.
So for listviews, the header is a seperate control.
For listviews the list item refers to the first cell in the row, the sub-items are all other cells in the same row. So subitem 0 is what we see as the 2nd cell in the row. So for 3x3 grid of data it would be:
item 0, subitem 0, subitem 1
item 1, subitem 0, subitem 1
item 2, subitem 0, subitem 1
So for that we need 3 columns.
LVM_GETHEADER message will get the listviews header. Might have to check the header reference on MSDN to see if you can make use of some of the message or notifications for that: http://msdn.microsoft.com/en-us/library/windows/desktop/ff485936%28v=vs.85%29.aspx (http://msdn.microsoft.com/en-us/library/windows/desktop/ff485936%28v=vs.85%29.aspx), or perhaps sublassing the header proc and handling the drawing in that proc might be what you need to do.
Invoke SendMessage, hListview, LVM_GETHEADER, 0, 0 ; returns header handle in eax, maybe save it later for re-use
mov hHeaderListview, eax
Invoke SetWindowLong, eax, GWL_WNDPROC, OFFSET ListViewHeaderProc
mov pListviewHeaderOldProc, eax ; save old procedure, for later use in our sublass of header
ListViewHeaderProc PROC hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.if (whatever to test for ownerdraw stuff, do it here)
.else ; otherwise call default header handling proc
invoke CallWindowProc, pListviewHeaderOldProc, hWin, uMsg, wParam, lParam
.endif
ret
ListViewHeaderProc ENDP
Rough example code off the top of my head :D maybe that will point you in the right direction though.
QuoteThere's another small thing, which may be due to my misunderstanding of how to set up listviews. In my little demo app I create a 3x3 listview (excluding header). But instead of getting 3 columns, I get 4, the last of which extends to the right edge of the control, no matter how wide I make it. Is this the way they're supposed to work? I find that I can make it look like 3 columns if I carefully adjust the overall width of the control, but this seems like a kluge to me. Am I missing something here?
Yeh, its pretty much the way you describe it, and i do that as well, adjust colum widths to align up close to the edge of the control overall. Think of the extra as windows saying, "here is where more columns will go possibly, so ill just go ahead and make some spare space here and leave it blank for the moment" ;)
Quote from: fearless on June 12, 2012, 09:48:55 AM
So for listviews, the header is a seperate control.
[...]
LVM_GETHEADER message will get the listviews header.
Thank you! So my suspicions (via WinSpy) were correct. I think you've given me all the ammunition I need.
LVM_GETHEADER gets you a handle to the header control. You can then send it all kinda messages to make it do what you want.
Hmm; except that no messages have any effect on text color. So I guess you're right: you'd need to subclass the header control.
I'm guessing the following sequence:
- Get the control handle (using LVM_GETHEADER).
- Subclass the control (divert its window proc to yours).
- Capture NM_CUSTOMDRAW notifications.
- Process the notifications similarly to how I've handled the listview notifications.
Hopefully that might do it, let us know how you get on.
Alas, I can't see any way to do this (remember, I'm trying to set text colors in a header control for a listview control).
According to this MSDN page (http://msdn.microsoft.com/en-us/library/windows/desktop/ff919569%28v=vs.85%29.aspx#CustomDraw_Notifications), for header controls you get a pointer to a NMCUSTOMDRAW structure with the notification code. Problem is, unlike the NMLVCUSTOMDRAW structure, there are no fields to set text color. So I don't see how I can change text colors in the header.
What I need is a function (or message) that can change the color of a control, given its window handle. Unfortunately, all I can find is SetTextColor(), which takes a handle to a device context.
Anyone have any ideas? I guess I could handle WM_PAINT for the header, but how would I do that? How would I know which part of the control is being painted?
You just answered your own question:
Quoteyou get a pointer to a NMCUSTOMDRAW structure
Quoteall I can find is SetTextColor(), which takes a handle to a device context
Now go back and look at the fields of NMCUSTOMDRAW, find it? The NMCUSTOMDRAW structure contains a hdc field. This is the handle you use to do any drawing to, or even pass to SetTextColor
What you said makes perfect sense. You raised my hopes. But when I tried it, no joy.
Here's what I did (this from the header-control's window proc):
MOV EAX, uMsg
CMP EAX, WM_NOTIFY
JE do_notify
dodefault:
INVOKE CallWindowProc, OldHdrProcPtr, hWin, uMsg, wParam, lParam
RET
do_notify:
MOV ECX, lParam ;ECX--> NMHDR structure.
CMP [ECX + NMHDR.code], NM_CUSTOMDRAW
JNE dodefault
; Handle NM_CUSTOMDRAW notification:
paintItem:
PUSH EBX
MOV EBX, [ECX + NMCUSTOMDRAW.hdc]
INVOKE SetTextColor, EBX, $colorWhite
INVOKE SetBkColor, EBX, $colorBlack
POP EBX
MOV EAX, CDRF_DODEFAULT
RET
Basically setting the foreground and background text colors each time I got an NM_CUSTOMDRAW notification. No effect.
I realize this is overkill (I should be much more selective about when I set the text colors), but the point is I couldn't get the colors to "stick" into the DC.
Of course, I may not be returning the correct values from these notifications ... back to the laboratory.
You need to respond to the different "draw stages". You do the drawing when dwDrawStage == CDDS_PREPAINT. This is where you can change the text color, font, etc...
No, I think it is done in response to CDDS_ITEMPREPAINT
Quote from: Gunner on June 13, 2012, 12:52:00 PM
You need to respond to the different "draw stages". You do the drawing when dwDrawStage == CDDS_PREPAINT. This is where you can change the text color, font, etc...
No, I think it is done in response to CDDS_ITEMPREPAINT
Yes, I'm not sure either (and I'm not sure the MSDN documentation is correct on this point, based on capturing notifications in my test program).
But if I set the text color regardless of what the drawing stage is, as I'm doing indiscriminately in the code above, shouldn't it "stick" at some stage? As it is, it has no effect at all.
Im wondering if the header passes its draw notifications to its parent the listview, if thats the case, you might just have to subclass the listview and not the header, and with the LVM_GETHEADER you will have the handle to the header and can seperate the logic for drawing if its the header or the list items or subitems. maybe. not sure.
OK, further research has revealed the following:
It does not appear that any messages intended specifically for the header control are sent to the listview-control proc. I checked for any such messages (where the handle was the header handle, not the listview handle) and found none. So it appears that normally (i.e., if the header isn't subclassed), these messages are handled internally by the GDI. In other words, the listview proc only receives messages for the listview control (which may include the header control, but not using a separate handle for that control).
(By the way, I'm not sure why I would want to subclass the listview as you suggested: don't all its messages come through its parent's window proc anyhow?)
OK, when I subclass the header control, I get the following messages (sent to a log file):
Hdr. proc(): msg=WM_WINDOWPOSCHANGING
Hdr. proc(): msg=WM_WINDOWPOSCHANGING
Hdr. proc(): msg=WM_WINDOWPOSCHANGING
Hdr. proc(): msg=WM_WINDOWPOSCHANGING
Hdr. proc(): msg=WM_WINDOWPOSCHANGING
Hdr. proc(): msg=WM_WINDOWPOSCHANGING
Hdr. proc(): msg=WM_WINDOWPOSCHANGING
Hdr. proc(): msg=WM_WINDOWPOSCHANGING
Hdr. proc(): msg=WM_PAINT
Hdr. proc(): msg=WM_NCPAINT
Hdr. proc(): msg=WM_ERASEBKGND
Actually, these are only the "known" messages (i.e., those that are in our windows.inc file). There were a bunch of other messages captured, with values 1205, 1207. 120A, 120B and 120F (all hex). What these messages are, I have no idea.
Unfortunately, the header proc receives no WM_NOTIFY messages at all. So I don't see how I can possibly change colors in the header. It must be possible, though, since there are plenty of applications that do just that.
Any more ideas?
==============================================
Hmm, interesting: when I subclass the listview, here are the messages I get (ignore the "hdr. proc()"--that's just the text I used in my message-capturing code):
Hdr. proc(): unknown msg (0x1036)
Hdr. proc(): unknown msg (0x101B)
Hdr. proc(): unknown msg (0x101B)
Hdr. proc(): unknown msg (0x101B)
Hdr. proc(): unknown msg (0x1007)
Hdr. proc(): unknown msg (0x1007)
Hdr. proc(): unknown msg (0x1007)
Hdr. proc(): msg=WM_USER
Hdr. proc(): msg=WM_PAINT
Hdr. proc(): msg=WM_NOTIFY
Hdr. proc(): msg=WM_ERASEBKGND
Hdr. proc(): msg=WM_TIMER
Hdr. proc(): msg=WM_NCHITTEST
Hdr. proc(): msg=WM_SETCURSOR
Hdr. proc(): msg=WM_MOUSEMOVE
Hdr. proc(): msg=WM_NCHITTEST
Hdr. proc(): msg=WM_SETCURSOR
Hdr. proc(): msg=WM_MOUSEMOVE
Hdr. proc(): msg=WM_NCHITTEST
Hdr. proc(): msg=WM_SETCURSOR
Hdr. proc(): msg=WM_MOUSEMOVE
Hdr. proc(): msg=WM_NCHITTEST
Hdr. proc(): msg=WM_SETCURSOR
Hdr. proc(): msg=WM_MOUSEMOVE
Hdr. proc(): msg=WM_NCHITTEST
Hdr. proc(): msg=WM_SETCURSOR
Hdr. proc(): msg=WM_MOUSEMOVE
Hdr. proc(): msg=WM_NCHITTEST
Hdr. proc(): msg=WM_SETCURSOR
Hdr. proc(): msg=WM_MOUSEMOVE
Hdr. proc(): msg=WM_NCHITTEST
Hdr. proc(): msg=WM_SETCURSOR
Hdr. proc(): msg=WM_NCMOUSEMOVE
There's a different set of "unknown" messages at the top there.
You don't have to subclass anything. This is what I get:
QuoteNotify from Header (LVHeaders.asm, 208)
Notify from Header (LVHeaders.asm, 208)
Notify from Header (LVHeaders.asm, 208)
Notify from Header (LVHeaders.asm, 208)
Notify from Listview (LVHeaders.asm, 210)
Notify from Listview (LVHeaders.asm, 210)
Notify from Listview (LVHeaders.asm, 210)
Notify from Listview (LVHeaders.asm, 210)
Here is a sample:
_NOTIFY:
mov edi, lParam
mov eax, (NMHEADER ptr[edi]).hdr.code
.if eax == NM_CUSTOMDRAW
mov ecx, (NMCUSTOMDRAW ptr[edi]).hdr.hwndFrom
.if ecx == hHeader
PrintText "Notify from Header"
.elseif ecx == ListviewHandle
PrintText "Notify from Listview"
.endif
mov eax, (NMCUSTOMDRAW ptr[edi]).dwDrawStage
.if eax == CDDS_PREPAINT
; *** HAVE TO *** return this here in in order
; to get other notifications.
mov eax, CDRF_NOTIFYITEMDRAW
ret
.elseif eax == CDDS_ITEMPREPAINT
; do drawing here
; tell control we changed font
mov eax, CDRF_NEWFONT
; or you can return CDRF_DODEFAULT
ret
.else
mov eax, CDRF_DODEFAULT
ret
.endif
.endif
I cannot seem to get anything to change either for some reason. I am going to keep playing with this. Everything seems to say it can be done this way. The other way is owner drawen and that is a lot of work just to change the text color.
Ok, without the owner draw flag this is from just playing around:
(http://www.gunnerinc.com/images/masm/header.png)
I would wrap this code up into a function and call it when needed with params.
So, the modify the code above and came up with this:
_NOTIFY:
mov edi, lParam
mov eax, (NMHEADER ptr[edi]).hdr.code
.if eax == NM_CUSTOMDRAW
mov ecx, (NMCUSTOMDRAW ptr[edi]).hdr.hwndFrom
.if ecx == hHeader
mov eax, (NMCUSTOMDRAW ptr[edi]).dwDrawStage
.if eax == CDDS_PREPAINT
; *** HAVE TO *** return this here in in order
; to get other notifications.
mov eax, CDRF_NOTIFYITEMDRAW
ret
.elseif eax == CDDS_ITEMPREPAINT
; do drawing here
mov edx, (NMCUSTOMDRAW ptr[edi]).dwItemSpec
mov ebx, (NMCUSTOMDRAW ptr[edi]).hdc
.if edx == 0 ; column 1
mov eax, (NMCUSTOMDRAW ptr[edi]).rc.left
mov HeadRect.left, eax
mov ecx, (NMCUSTOMDRAW ptr[edi]).rc.top
mov HeadRect.top, ecx
mov edx, (NMCUSTOMDRAW ptr[edi]).rc.right
mov HeadRect.right, edx
mov eax, (NMCUSTOMDRAW ptr[edi]).rc.bottom
mov HeadRect.bottom, eax
invoke FillRect, ebx, addr HeadRect, hBrush
invoke DrawEdge, ebx, addr HeadRect, EDGE_ETCHED, BF_RECT or BF_FLAT
invoke SetTextColor, ebx, Blue
invoke SetBkMode, ebx, TRANSPARENT
mov esi, (NMCUSTOMDRAW ptr[edi]).rc.left
add esi, 27
mov ecx, (NMCUSTOMDRAW ptr[edi]).rc.top
add ecx, 5
invoke TextOut, ebx, esi, ecx, offset Col1HeaderText, sizeof Col1HeaderText
.elseif edx == 1 ; column 2
invoke SetTextColor, ebx, Blue
invoke SetBkColor, ebx, Green
invoke SetBkMode, ebx,OPAQUE
mov esi, (NMCUSTOMDRAW ptr[edi]).rc.left
add esi, 27
mov ecx, (NMCUSTOMDRAW ptr[edi]).rc.top
add ecx, 5
invoke TextOut, ebx, esi, ecx, offset Col2HeaderText, sizeof Col2HeaderText
mov eax, (NMCUSTOMDRAW ptr[edi]).rc.left
mov HeadRect.left, eax
mov ecx, (NMCUSTOMDRAW ptr[edi]).rc.top
mov HeadRect.top, ecx
mov edx, (NMCUSTOMDRAW ptr[edi]).rc.right
mov HeadRect.right, edx
mov eax, (NMCUSTOMDRAW ptr[edi]).rc.bottom
mov HeadRect.bottom, eax
invoke DrawEdge, ebx, addr HeadRect, EDGE_ETCHED, BF_RECT or BF_FLAT
.elseif edx == 2 ; column 3
invoke SetTextColor, ebx, Green
invoke SetBkMode, ebx,TRANSPARENT
mov esi, (NMCUSTOMDRAW ptr[edi]).rc.left
add esi, 27
mov ecx, (NMCUSTOMDRAW ptr[edi]).rc.top
add ecx, 5
invoke TextOut, ebx, esi, ecx, offset Col3HeaderText, sizeof Col3HeaderText
mov eax, (NMCUSTOMDRAW ptr[edi]).rc.left
mov HeadRect.left, eax
mov ecx, (NMCUSTOMDRAW ptr[edi]).rc.top
mov HeadRect.top, ecx
mov edx, (NMCUSTOMDRAW ptr[edi]).rc.right
mov HeadRect.right, edx
mov eax, (NMCUSTOMDRAW ptr[edi]).rc.bottom
mov HeadRect.bottom, eax
invoke DrawEdge, ebx, addr HeadRect, EDGE_ETCHED, BF_RECT or BF_FLAT
.endif
;PrintDec edx
; tell control we did the drawing
mov eax, CDRF_SKIPDEFAULT
ret
.else
mov eax, CDRF_DODEFAULT
ret
.endif
.endif
.endif
Ok, I looked into it and Custom Draw is simple actually. Wrote a tutorial for customizing the Listview and Header without using Owner Draw, you can find tut and sample code here: http://www.dreamincode.net/forums/topic/283055-masm-custom-draw-listview-and-header/
Nicely done. Have to try out that code.
very cool, Rob :t
I am enjoying writing tutorials ;) When I start, I always say to myself "What am I going to write?". I break the code down and the words just flow. At some point, I have to figure out where to stop, as there is so much to write about.
Glad folks like it! :t
First of all, let me say nice job documenting that. Very helpful.
But before we go further, I wonder if it's a problem that I'm using Windows 2000 (Pro). Here's what I see when I run your tutorial app:
Is this correct? Notice there are no labels on the first two header columns, and nothing happens when I click on them. Is that because my OS is out of date?
(BTW, I have version 5.81.3502 of comctl32.dll, if that tells you anything.)
Fired up both VMs - with Win2K and XP.
Win2K
No text unless I specifically add it in InsertLVColumns and you can't click the columns, Win2k is a bit outdated. There comes a time when you cannot support all OS's.
XP
No text unless I added it also in InsertLVColumns and you can click column 2. That is the only one I wrote code for, you have to write code for whatever else :-) It is just to get you started.
I guess for 2k, you will have to do Owner Draw since the "cool" stuff comes with later OS's
Quote from: Gunner on June 18, 2012, 11:43:27 AM
I guess for 2k, you will have to do Owner Draw since the "cool" stuff comes with later OS's
Well, more precisely, I'd have to use owner draw if I want to mess around with the header. My little demo posted up there shows that I can manipulate the listview control itself just fine using custom draw.
Guess it's finally time to fire up that "new" XP computer of mine ... shame, really, since Win2K does about 95% of what I need it to do.
For XP and above, you don't need Owner Draw, Custom Draw works as it should.