Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Listview colors for individual lines (e.g. highlighting)


  • Please log in to reply
28 replies to this topic
evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
This code allows you to apply colors to a line (or as many as you like) in a listview - both background and text colors can be changed. This allows for effects such as:
- Highlighting alternating lines to make them easier to read.
- Highlighting certain lines to attract a user's attention to them.
- Flashing a line by toggling between colors.

Credit for this code goes to shimanov, I'm just providing a couple of extra little examples. Updated with ideas from toralf.

I think it might be possible to highlight specific "cells" within a listview by using CDRF_NOTIFYSUBITEMDRAW but I haven't been able to figure it out.


/* struct NMHDR { 
    HWND            hwndFrom;       uint4       0 
    UINT            idFrom;         uint4       4 
    UINT            code;           uint4       8 
}                                               12 
*/ 

/* struct NMCUSTOMDRAW { 
    NMHDR           hdr;            12          0 
    DWORD           dwDrawStage;    uint4       12 
    HDC             hdc;            uint4       16 
    RECT            rc;             16          20 
    DWORD_PTR       dwItemSpec;     uint4       36 
    UINT            uItemState;     uint4       40 
    LPARAM          lItemlParam;    int4        44 
}                                               48 
*/ 

/* struct NMLVCUSTOMDRAW { 
    NMCUSTOMDRAW    nmcd;           48          0 
    COLORREF        clrText;        uint4       48 
    COLORREF        clrTextBk;      uint4       52 

    #if (_WIN32_IE >= 0x0400) 
        int         iSubItem;       int4        56 
    #endif 

    #if (_WIN32_IE >= 0x560) 
        DWORD       dwItemType;     uint4       60 
        COLORREF    clrFace;        uint4       64 
        int         iIconEffect;    int4        68 
        int         iIconPhase;     int4        72 
        int         iPartId;        int4        76 
        int         iStateId:       int4        80 
        RECT        rcText;         16          84 
        UINT        uAlign;         uint4       100 
    #endif 
}                                               104 
*/ 

Gui, +LastFound
Gui, Add, ListView, x5 y5 w200 h200 vLV_Sample, index|day  
LV_Add( "", 1, "Monday" ) 
LV_Add( "", 2, "Tuesday" ) 
LV_Add( "", 3, "Wednesday" ) 
LV_Add( "", 4, "Thursday" ) 
LV_Add( "", 5, "Friday" ) 
LV_Add( "", 6, "Saturday" ) 
LV_Add( "", 7, "Sunday" ) 
LV_ModifyCol( 1, "AutoHdr" ) 
LV_ModifyCol( 2, "AutoHdr" ) 
Gui, Show, x50 y50 w210 h210
;nothing special up to this point 


LV_ColorInitiate() ; (Gui_Number, Control) - defaults to: (1, SysListView321)


Msgbox, Example 1: Highlighting alternating lines in the listview.
Loop, % LV_GetCount()
  {
  If ( Mod( A_Index, 2 ) )
    LV_ColorChange(A_Index, "0xFFFFFF", "0xFF0000") 
  }


Msgbox, Example 2: Highlighting specific lines in the listview.
LV_ColorChange() ; clear all highlighting
LV_ColorChange(6, "0xFFFFFF", "0xFF0000") 
LV_ColorChange(7, "0xFFFFFF", "0xFF0000") 


Msgbox, Example 3: Flashing specific lines in the listview.
LV_ColorChange() ; clear all highlighting
Loop, 3 { 
    LV_ColorChange(6) ; default color (highlight off)
    LV_ColorChange(7) 
    Sleep, 500 
    LV_ColorChange(6, "0xFFFFFF", "0xFF0000") 
    LV_ColorChange(7, "0xFFFFFF", "0xFF0000") 
    Sleep, 500 
  } 
Return 

GuiClose:
GuiEscape:
  Exitapp



LV_ColorInitiate(Gui_Number=1, Control="") ; initiate listview color change procedure 
{ 
  global hw_LV_ColorChange 
  If Control =
    Control =SysListView321
  Gui, %Gui_Number%:+Lastfound 
  Gui_ID := WinExist() 
  ControlGet, hw_LV_ColorChange, HWND,, %Control%, ahk_id %Gui_ID% 
  OnMessage( 0x4E, "WM_NOTIFY" ) 
} 

LV_ColorChange(Index="", TextColor="", BackColor="") ; change specific line's color or reset all lines
{ 
  global 
  If Index = 
    Loop, % LV_GetCount() 
      LV_ColorChange(A_Index) 
  Else
    { 
    Line_Color_%Index%_Text := TextColor 
    Line_Color_%Index%_Back := BackColor 
    WinSet, Redraw,, ahk_id %hw_LV_ColorChange% 
    } 
}



WM_NOTIFY( p_w, p_l, p_m )
{ 
  local  draw_stage, Current_Line, Index
  if ( DecodeInteger( "uint4", p_l, 0 ) = hw_LV_ColorChange ) { 
      if ( DecodeInteger( "int4", p_l, 8 ) = -12 ) {                            ; NM_CUSTOMDRAW 
          draw_stage := DecodeInteger( "uint4", p_l, 12 ) 
          if ( draw_stage = 1 )                                                 ; CDDS_PREPAINT 
              return, 0x20                                                      ; CDRF_NOTIFYITEMDRAW 
          else if ( draw_stage = 0x10000|1 ){                                   ; CDDS_ITEM 
              Current_Line := DecodeInteger( "uint4", p_l, 36 )+1 
              LV_GetText(Index, Current_Line) 
              If (Line_Color_%Index%_Text != ""){ 
                  EncodeInteger( Line_Color_%Index%_Text, 4, p_l, 48 )   ; foreground 
                  EncodeInteger( Line_Color_%Index%_Back, 4, p_l, 52 )   ; background 
                } 
            } 
        } 
    } 
} 

DecodeInteger( p_type, p_address, p_offset, p_hex=true )
{ 
  old_FormatInteger := A_FormatInteger 
  ifEqual, p_hex, 1, SetFormat, Integer, hex 
  else, SetFormat, Integer, dec 
  StringRight, size, p_type, 1 
  loop, %size% 
      value += *( ( p_address+p_offset )+( A_Index-1 ) ) << ( 8*( A_Index-1 ) ) 
  if ( size <= 4 and InStr( p_type, "u" ) != 1 and *( p_address+p_offset+( size-1 ) ) & 0x80 ) 
      value := -( ( ~value+1 ) & ( ( 2**( 8*size ) )-1 ) ) 
  SetFormat, Integer, %old_FormatInteger% 
  return, value 
} 

EncodeInteger( p_value, p_size, p_address, p_offset )
{ 
  loop, %p_size% 
    DllCall( "RtlFillMemory", "uint", p_address+p_offset+A_Index-1, "uint", 1, "uchar", p_value >> ( 8*( A_Index-1 ) ) ) 
} 


toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
Very cool stuff. Thanks for sharing it.

I guess it can be even more simplified (put into function calls) like it was done with the statusbar functions. But maybe Chris wants to implement it directly into AHK, since I seem to remember that he wanted to take a look into it (although I do not find it in the planned features list) since some users asked for it. Either way you have reduced R&D and helped a lot of people. I'll use it.
Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
This is a mod of the third example. It introduces the following functions that (I hope) will simpily the use of this feature.
LV_ActivateColor()
LV_ChangeColor(Index, TextColor, BackColor)

The original code had a problem when the user sorted the lines in a different order, the color didn't follow the lines. If you had given row 6 a color, row 6 would keep the color regardless how the lines in the listview change. This code will make sure the color stays with the line. It uses the index of each line to assign the color.

Gui, +LastFound 
Gui, Add, ListView, x5 y5 w200 h200 vLV_Sample, index|day  
Gui, Show, x50 y50 w210 h210 
LV_Add( "", 1, "Monday" ) 
LV_Add( "", 2, "Tuesday" ) 
LV_Add( "", 3, "Wednesday" ) 
LV_Add( "", 4, "Thursday" ) 
LV_Add( "", 5, "Friday" ) 
LV_Add( "", 6, "Saturday" ) 
LV_Add( "", 7, "Sunday" ) 
LV_ModifyCol( 1, "AutoHdr" ) 
LV_ModifyCol( 2, "AutoHdr" ) 
;nothing special up to this point

LV_ActivateColor()
Loop, 5 { 
    LV_ChangeColor(6, "", "")
    LV_ChangeColor(7, "", "")
    Sleep, 500 
    LV_ChangeColor(6, "0xFFFFFF", "0xFF0000")
    LV_ChangeColor(7, "0xFFFFFF", "0xFF0000")
    Sleep, 500 
  } 
return 

LV_ActivateColor(){
    global hw_LV_Sample
    OnMessage( 0x4E, "WM_NOTIFY" )
    ControlGet, hw_LV_Sample, HWND,, SysListView321 
  }

LV_ChangeColor(Index, TextColor, BackColor){
    global
    Line_Color_%Index%_Text := TextColor 
    Line_Color_%Index%_Back := BackColor 
    WinSet, Redraw,, ahk_id %hw_LV_Sample%
  }

WM_NOTIFY( p_w, p_l, p_m ) { 
    global  hw_LV_Sample 
    if ( DecodeInteger( "uint4", p_l, 0 ) = hw_LV_Sample ) { 
        if ( DecodeInteger( "int4", p_l, 8 ) = -12 ) {                            ; NM_CUSTOMDRAW 
            draw_stage := DecodeInteger( "uint4", p_l, 12 ) 
            if ( draw_stage = 1 )                                                 ; CDDS_PREPAINT 
                return, 0x20                                                      ; CDRF_NOTIFYITEMDRAW 
            else if ( draw_stage = 0x10000|1 ){                                   ; CDDS_ITEM 
                Current_Line := DecodeInteger( "uint4", p_l, 36 )+1 
                LV_GetText(Index, Current_Line)
                If (Line_Color_%Index%_Text != ""){ 
                    EncodeInteger( Line_Color_%Index%_Text, 4, p_l, 48 )   ; foreground 
                    EncodeInteger( Line_Color_%Index%_Back, 4, p_l, 52 )   ; background 
                  } 
              } 
          } 
      } 
  } 

DecodeInteger( p_type, p_address, p_offset, p_hex=true ){ 
    old_FormatInteger := A_FormatInteger 
    ifEqual, p_hex, 1, SetFormat, Integer, hex 
    else, SetFormat, Integer, dec 
    StringRight, size, p_type, 1 
    loop, %size% 
        value += *( ( p_address+p_offset )+( A_Index-1 ) ) << ( 8*( A_Index-1 ) ) 
    if ( size <= 4 and InStr( p_type, "u" ) != 1 and *( p_address+p_offset+( size-1 ) ) & 0x80 ) 
        value := -( ( ~value+1 ) & ( ( 2**( 8*size ) )-1 ) ) 
    SetFormat, Integer, %old_FormatInteger% 
    return, value 
  } 

EncodeInteger( p_value, p_size, p_address, p_offset ){ 
    loop, %p_size% 
        DllCall( "RtlFillMemory", "uint", p_address+p_offset+A_Index-1, "uint", 1, "uchar", p_value >> ( 8*( A_Index-1 ) ) ) 
  }

Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
Nice idea with the syntax and an elegant fix for the sorting/highlighting. It can be simplified a bit more by making the default color parameters blank if not passed, e.g.:

Reset color: LV_ChangeColor(6)
(instead of: LV_ChangeColor(6, "", "") )

And changing the function to have the default values:
LV_ChangeColor(Index, TextColor="", BackColor=""){

I'll update the 2nd 2 examples in my 1st post with these changes to make it easier for users to find the best code to use.

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
I updated the whole of the first post's code to make it a single example, including the new syntax and a few small additions and a bugfix for global/local variables.

toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
Other possible changes:
- LV_ColorActivate can be called for different GUIs and Controls
- LV_ColorChange also allows the reset if "reset" is specified instead of a index number

LV_ColorActivate(GuiID=1, ControlNN="") ; initiate listview color change procedure 
{ 
  global hw_LV_Sample 
  Gui, %GuiID%:+Lastfound
  GuiHWND := WinExist()
  If ClassNN is space
      ClassNN = SysListView321
  ControlGet, hw_LV_Sample, HWND,, %ControlNN%, ahk_id %GuiHWND% 
  OnMessage( 0x4E, "WM_NOTIFY" ) 
} 

LV_ColorChange(Index, TextColor="", BackColor="") ; change specific line's color 
{ 
  global 
  If Index = Reset
    Loop, % LV_GetCount() 
      LV_ColorChange(A_Index) 
  Else {
      Line_Color_%Index%_Text := TextColor 
      Line_Color_%Index%_Back := BackColor 
      WinSet, Redraw,, ahk_id %hw_LV_Sample% 
    }
}
*not tested*
Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
Updated the first post to reflect those ideas and altered the syntax a little to make it clearer. (Thank goodness for being able to assign default values to parameters in functions, it keeps things so simple :D )

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Great demonstration (and clearly presented). I know this will be a popular topic since it has been frequently asked how to make some rows look different than others. Also, as was mentioned above, your work will make R&D easier when the time comes to integrate such features into AHK.

Thanks to Shimanov, Evl, and Toralf for this script.

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
I've noticed a problem with the highlighted lines not being re-drawn after LV_Modify is used to select a highlighted line and then un-selected - this also happens sometimes when using the mouse or keyboard to select a line, but less frequently (1 in 20 times maybe).

Any ideas why this might be?

Here's an example script to show what I mean:
https://ahknet.autoh... ... roblem.ahk

ambi
  • Guests
  • Last active:
  • Joined: --
I have merged the information here with evl's sample code.
The resulting example below not only highlights alternating rows in the listview using custom colors, but it also uses (different) custom colors for the selected row.

#NoEnv
SendMode Input

/*
struct NMHDR {
  HWND            hwndFrom;       uint4       0
  UINT            idFrom;         uint4       4
  UINT            code;           uint4       8
}                                             12

struct NMCUSTOMDRAW {
  NMHDR           hdr;            12          0
  DWORD           dwDrawStage;    uint4       12
  HDC             hdc;            uint4       16
  RECT            rc;             16          20
  DWORD_PTR       dwItemSpec;     uint4       36
  UINT            uItemState;     uint4       40
  LPARAM          lItemlParam;    int4        44
}                                             48

struct NMLVCUSTOMDRAW {
  NMCUSTOMDRAW    nmcd;       	48          0
  COLORREF        clrText;    	uint4       48
  COLORREF        clrTextBk;    uint4       52
#if (_WIN32_IE >= 0x0400)
  int         iSubItem;       	int4        56
#endif
#if (_WIN32_IE >= 0x560)
  DWORD       dwItemType;     	uint4       60
  COLORREF    clrFace;        	uint4       64
  int         iIconEffect;    	int4        68
  int         iIconPhase;     	int4        72
  int         iPartId;        	int4        76
  int         iStateId:       	int4        80
  RECT        rcText;         	16          84
  UINT        uAlign;         	uint4       100
#endif
}                                           104

struct LVITEM { 
  UINT mask;					uint4			0
  int iItem;										4
  int iSubItem;									8
  UINT state;					uint4			12
  UINT stateMask;			uint4			16
  LPTSTR pszText; 
  int cchTextMax; 
  int iImage; 
  LPARAM lParam;
#if (_WIN32_IE >= 0x0300)
  int iIndent;
#endif
#if (_WIN32_WINNT >= 0x560)
  int iGroupId;
  UINT cColumns; // tile view columns
  PUINT puColumns;
#endif
#if (_WIN32_WINNT >= 0x0600)
  int* piColFmt
  int iGroup
#endif
} 
*/

Gui, +LastFound
Gui, Add, ListView, x5 y5 w200 h200 gLV_Sample vLV_Sample, index|day 
LV_Add( "", 1, "Monday" )
LV_Add( "", 2, "Tuesday" )
LV_Add( "", 3, "Wednesday" )
LV_Add( "", 4, "Thursday" )
LV_Add( "", 5, "Friday" )
LV_Add( "", 6, "Saturday" )
LV_Add( "", 7, "Sunday" )
LV_ModifyCol( 1, "AutoHdr" )
LV_ModifyCol( 2, "AutoHdr" )
Gui, Show, x50 y50 w210 h210

LV_ColorInitiate() ; (Gui_Number, Control) - defaults to: (1, SysListView321)

; Highlighting alternating lines in the listview.
Loop, % LV_GetCount()
  {
  If ( Mod( A_Index, 2 ) )
    LV_ColorChange(A_Index, "0xFFFFFF", "0xFF0000")
  }
Return

LV_Sample:
	if A_GuiEvent = DoubleClick 
	{
		LV_GetText(myday, A_EventInfo, 2)
		MsgBox, %myday%!
	}
Return
	
GuiClose:
GuiEscape:
  Exitapp

LV_ColorInitiate(Gui_Number=1, Control="") ; initiate listview color change procedure
{
  global hw_LV_ColorChange, LvItem
  If Control =
    Control =SysListView321
  Gui, %Gui_Number%:+Lastfound
  Gui_ID := WinExist()
  ControlGet, hw_LV_ColorChange, HWND,, %Control%, ahk_id %Gui_ID%
  VarSetCapacity(LvItem, 36, 0)
  OnMessage( 0x4E, "WM_NOTIFY" )
}

LV_ColorChange(Index="", TextColor="", BackColor="") ; change specific line's color or reset all lines
{
  global
  If Index =
    Loop, % LV_GetCount()
      LV_ColorChange(A_Index)
  Else
    {
    Line_Color_%Index%_Text := TextColor
    Line_Color_%Index%_Back := BackColor
    WinSet, Redraw,, ahk_id %hw_LV_ColorChange%
    }
}

WM_NOTIFY( p_w, p_l, p_m )
{
  local draw_stage, Current_Line, Index, IsSelected=0
  Critical
  if ( DecodeInteger( "uint4", p_l, 0 ) = hw_LV_ColorChange ) {		; NMHDR->hwndFrom
  	if ( DecodeInteger( "int4", p_l, 8 ) = -12 ) {                ; NMHDR->code ; NM_CUSTOMDRAW
    	draw_stage := DecodeInteger( "uint4", p_l, 12 )							; NMCUSTOMDRAW->dwDrawStage
      Current_Line := DecodeInteger( "uint4", p_l, 36 )+1					; NMCUSTOMDRAW->dwItemSpec
     	if ( draw_stage = 1 )                                       ; CDDS_PREPAINT
        return, 0x20                                              ; CDRF_NOTIFYITEMDRAW
      else if ( draw_stage = 0x10000|1 ) {                        ; CDDS_ITEMPREPAINT
  			If ( DllCall("GetFocus") = hw_LV_ColorChange ) {				 	; Control has Keyboard Focus?
   				SendMessage, 4140, Current_Line-1, 2, , ahk_id %hw_LV_ColorChange% ; LVM_GETITEMSTATE
   				IsSelected := ErrorLevel
   				If ( IsSelected = 2 ) {																 ; LVIS_SELECTED
   					EncodeInteger( "0xFFFFFF", 4, p_l, 48 )							 ; NMCUSTOMDRAW->clrText ; foreground
   					EncodeInteger( "0x0000FF", 4, p_l, 52 )							 ; NMCUSTOMDRAW->clrTextBk ; background
 						EncodeInteger(0x0, 4, &LvItem, 12)									 ; LVITEM->state
						EncodeInteger(0x2, 4, &LvItem, 16)									 ; LVITEM->stateMask ; LVIS_SELECTED
    				SendMessage, 4139, Current_Line-1, &LvItem, , ahk_id %hw_LV_ColorChange% ; Disable Highlighting
    				; We want item post-paint notifications
    				Return, 0x00000010																	 ; CDRF_NOTIFYPOSTPAINT 
     			}
     			LV_GetText(Index, Current_Line)
     			If (Line_Color_%Index%_Text != "") {
        		EncodeInteger( Line_Color_%Index%_Text, 4, p_l, 48 ) ; NMLVCUSTOMDRAW->clrText ; foreground
        		EncodeInteger( Line_Color_%Index%_Back, 4, p_l, 52 ) ; NMLVCUSTOMDRAW->clrTextBk ; background
        	}
     		}
     	}
     	else if ( draw_stage = 0x10000|2 )						; CDDS_ITEMPOSTPAINT
				If ( IsSelected ) {
					EncodeInteger(0x02, 4, &LvItem, 12)				; LVITEM->state
					EncodeInteger(0x02, 4, &LvItem, 16)				; LVITEM->stateMask ; LVIS_SELECTED
					SendMessage, 4139, Current_Line-1, &LvItem, , ahk_id %hw_LV_ColorChange% ; LVM_SETITEMSTATE
				}
    }
  }
}

DecodeInteger( p_type, p_address, p_offset, p_hex=true )
{
  old_FormatInteger := A_FormatInteger
  ifEqual, p_hex, 1, SetFormat, Integer, hex
  else, SetFormat, Integer, dec
  StringRight, size, p_type, 1
  loop, %size%
      value += *( ( p_address+p_offset )+( A_Index-1 ) ) << ( 8*( A_Index-1 ) )
  if ( size <= 4 and InStr( p_type, "u" ) != 1 and *( p_address+p_offset+( size-1 ) ) & 0x80 )
      value := -( ( ~value+1 ) & ( ( 2**( 8*size ) )-1 ) )
  SetFormat, Integer, %old_FormatInteger%
  return, value
}

EncodeInteger( p_value, p_size, p_address, p_offset )
{
  loop, %p_size%
    DllCall( "RtlFillMemory", "uint", p_address+p_offset+A_Index-1, "uint", 1, "uchar", p_value >> ( 8*( A_Index-1 ) ) )
}


Roland
  • Members
  • 307 posts
  • Last active: Mar 09 2014 07:55 PM
  • Joined: 08 Jun 2006
Very nice, thanks!

Tekl
  • Members
  • 814 posts
  • Last active: May 03 2009 03:28 PM
  • Joined: 24 Sep 2004
Is it also possible to change colors from external controls like the detail view of the explorer?
Tekl

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Not with AHK :D
Posted Image

almex
  • Members
  • 10 posts
  • Last active: Aug 25 2009 08:10 PM
  • Joined: 16 Oct 2007
Sorry for bringing up an old thread, but I've been using this code (thanks again, evl!) and noticed that the color hex is reversed. For instance:

0x008000 shows correctly as green. BUT...
0x0000FF should be blue, but shows as red.
0xFF0000 should be red, but shows as blue.

I'll admit I don't fully understand the code behind DecodeInteger & EncodeInteger, but I believe the problem lies in one of those functions.

SKAN
  • Administrators
  • 9115 posts
  • Last active:
  • Joined: 26 Dec 2005

0x008000 shows correctly as green. BUT...
0x0000FF should be blue, but shows as red.
0xFF0000 should be red, but shows as blue.


Try swapping the values from RGB to be BGR.

For example:
0xFF0000 will be Blue


:)