[ListView] Add Column Header SortArrow Icon

Put simple Tips and Tricks that are not entire Tutorials in this forum
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

[ListView] Add Column Header SortArrow Icon

Post by jNizM » 02 Sep 2021, 06:50

Add and set the Sort Arrow Icon in ListView Column Header

Wait for the ColumnClick Event and get the FMT attribute from the LVM_GETCOLUMN ListView message and send the HDF_SORTDOWN or HDF_SORTUP attribute with the LVM_SETCOLUMN ListView message.

Code: Select all

MyGui := Gui()
LV := MyGui.Add("ListView", "w300 r8 Grid -LV0x10", ["Column1", "Column2", "Column3"])
loop 7
	LV.Add(, A_Index * 1, A_Index * 2, A_Index * 3)
loop 3
	LV.ModifyCol(A_Index, "80 Integer")
LV.OnEvent("ColClick", LV_SortArrow)
MyGui.Show()


LV_SortArrow(LV, Column)
{
	static HDF_SORTUP       := 0x0400
	static HDF_SORTDOWN     := 0x0200
	static LVM_FIRST        := 0x1000
	static LVM_GETCOLUMNW   := LVM_FIRST + 95
	static LVM_SETCOLUMNW   := LVM_FIRST + 96
	static LVCF_FMT         := 0x0001

	Column -= 1
	LVCOLUMNW := Buffer(56, 0)
	NumPut("uint", LVCF_FMT, LVCOLUMNW, 0)
	SendMessage(LVM_GETCOLUMNW, Column, LVCOLUMNW, LV.hWnd)
	fmt := NumGet(LVCOLUMNW, 4, "int")
	if (fmt & HDF_SORTUP)
		NumPut("int", fmt & ~HDF_SORTUP | HDF_SORTDOWN, LVCOLUMNW, 4)
	else if (fmt & HDF_SORTDOWN)
		NumPut("int", fmt & ~HDF_SORTDOWN | HDF_SORTUP, LVCOLUMNW, 4)
	else
	{
		loop LV.GetCount("Column")
		{
			if ((i := A_Index - 1) != Column)
			{
				SendMessage(LVM_GETCOLUMNW, i, LVCOLUMNW, LV.hWnd)
				NumPut("int", NumGet(LVCOLUMNW, 4, "int") & ~(HDF_SORTDOWN | HDF_SORTUP), LVCOLUMNW, 4)
				SendMessage(LVM_SETCOLUMNW, i, LVCOLUMNW, LV.hWnd)
			}
		}
		NumPut("int", fmt | HDF_SORTUP, LVCOLUMNW, 4)
	}
	return SendMessage(LVM_SETCOLUMNW, Column, LVCOLUMNW, LV.hWnd)
}

Image


Thanks to Solar and HotKeyIt for the v1.1 code (https://autohotkey.com/board/topic/64736-lv-sortarrow-apply-sort-arrows-to-your-listview/)
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: [ListView] Add Column Header SortArrow Icon

Post by kczx3 » 02 Sep 2021, 09:21

Nice, keep them coming!

iPhilip
Posts: 791
Joined: 02 Oct 2013, 12:21

Re: [ListView] Add Column Header SortArrow Icon

Post by iPhilip » 08 May 2023, 12:59

@jNizM, Thank you for your implementation. The version below doesn't require looping through all the columns so it may be faster. It takes advantage of the HDM_GETITEM/HDM_SETITEM messages.

Code: Select all

LV_SortArrow(LV, Column) {
   static HDI_FORMAT    := 0x0004
   static HDF_SORTDOWN  := 0x0200
   static HDF_SORTUP    := 0x0400
   static LVM_FIRST     := 0x1000
   static HDM_FIRST     := 0x1200
   static HDM_GETITEM   := HDM_FIRST + 11
   static HDM_SETITEM   := HDM_FIRST + 12
   static LVM_GETHEADER := LVM_FIRST + 31
   static HDITEM_SIZE   := 6 * 4 + 6 * A_PtrSize
   static FormatOffset  := 3 * 4 + 2 * A_PtrSize
   static PrevColumn    := 0
   
   HDITEM := Buffer(HDITEM_SIZE)
   NumPut 'UInt', HDI_FORMAT, HDITEM, 0
   HDR := SendMessage(LVM_GETHEADER, 0, 0, LV)
   if PrevColumn && Column != PrevColumn {
      SendMessage(HDM_GETITEM, PrevColumn - 1, HDITEM, HDR)
      Format := NumGet(HDITEM, FormatOffset, 'Int')
      NumPut 'Int', Format & ~(HDF_SORTDOWN | HDF_SORTUP), HDITEM, FormatOffset
      SendMessage(HDM_SETITEM, PrevColumn - 1, HDITEM, HDR)
   }
   PrevColumn := Column
   SendMessage(HDM_GETITEM, Column - 1, HDITEM, HDR)
   Format := NumGet(HDITEM, FormatOffset, 'Int')
   if Format & HDF_SORTDOWN
      NumPut 'Int', (Format & ~HDF_SORTDOWN) | HDF_SORTUP, HDITEM, FormatOffset
   else if Format & HDF_SORTUP
      NumPut 'Int', (Format & ~HDF_SORTUP) | HDF_SORTDOWN, HDITEM, FormatOffset
   else
      NumPut 'Int', Format | HDF_SORTUP, HDITEM, FormatOffset
   return SendMessage(HDM_SETITEM, Column - 1, HDITEM, HDR)
}
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

iPhilip
Posts: 791
Joined: 02 Oct 2013, 12:21

Re: [ListView] Add Column Header SortArrow Icon

Post by iPhilip » 08 May 2023, 13:17

An alternate approach to the built-in method is to add a character to the header text indicating the direction of the sort. In the implementation below, the character is placed to the left of the text if the column is right-aligned and to the right of the text otherwise.

Code: Select all

LV_SortArrow(LV, Column) {
   static SORTUP         := Chr(0x25B2)  ; (▲) Black Up-Pointing Triangle
   static SORTDOWN       := Chr(0x25BC)  ; (▼) Black Down-Pointing Triangle
   static HDI_TEXT       := 0x0002
   static HDI_FORMAT     := 0x0004
   static HDF_RIGHT      := 0x0001
   static LVM_FIRST      := 0x1000
   static HDM_FIRST      := 0x1200
   static HDM_GETITEM    := HDM_FIRST + 11
   static HDM_SETITEM    := HDM_FIRST + 12
   static LVM_GETHEADER  := LVM_FIRST + 31
   static HDITEM_SIZE    := 6 * 4 + 6 * A_PtrSize
   static TextPtrOffset  := 2 * 4
   static TextSizeOffset := 2 * 4 + 2 * A_PtrSize
   static FormatOffset   := 3 * 4 + 2 * A_PtrSize
   static MaxChars       := 260
   static PrevColumn     := 0
   
   Text := Buffer(2 * MaxChars)
   HDITEM := Buffer(HDITEM_SIZE)
   NumPut 'UInt', HDI_TEXT | HDI_FORMAT, HDITEM, 0
   NumPut 'Ptr', Text.Ptr, HDITEM, TextPtrOffset
   NumPut 'Int', MaxChars, HDITEM, TextSizeOffset
   HDR := SendMessage(LVM_GETHEADER, 0, 0, LV)
   if PrevColumn && Column != PrevColumn {
      SendMessage(HDM_GETITEM, PrevColumn - 1, HDITEM, HDR)
      Format := NumGet(HDITEM, FormatOffset, 'Int'), String := StrGet(Text)
      StrPut Format & HDF_RIGHT ? SubStr(String, 3) : SubStr(String, 1, -2), Text
      SendMessage(HDM_SETITEM, PrevColumn - 1, HDITEM, HDR)
   }
   PrevColumn := Column
   SendMessage(HDM_GETITEM, Column - 1, HDITEM, HDR)
   Format := NumGet(HDITEM, FormatOffset, 'Int'), String := StrGet(Text)
   if Format & HDF_RIGHT
      String := RegExMatch(String, '^(' SORTUP '|' SORTDOWN ')(.*)', &Match) ? (Match[1] == SORTDOWN ? SORTUP : SORTDOWN) Match[2] : SORTUP A_Space String
   else
      String := RegExMatch(String, '(.*)(' SORTUP '|' SORTDOWN ')$', &Match) ? Match[1] (Match[2] == SORTDOWN ? SORTUP : SORTDOWN) : String A_Space SORTUP
   StrPut String, Text, MaxChars - 1
   return SendMessage(HDM_SETITEM, Column - 1, HDITEM, HDR)
}

LV_SortArrow.png
LV_SortArrow.png (7.06 KiB) Viewed 1933 times
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: [ListView] Add Column Header SortArrow Icon

Post by kczx3 » 08 May 2023, 17:24

The character approach is probably quite beneficial because I don’t think you can combine a bitmap and use the built-in sort arrows.

iPhilip
Posts: 791
Joined: 02 Oct 2013, 12:21

Re: [ListView] Add Column Header SortArrow Icon

Post by iPhilip » 09 May 2023, 04:40

kczx3 wrote:
08 May 2023, 17:24
The character approach is probably quite beneficial because I don’t think you can combine a bitmap and use the built-in sort arrows.
That's correct.
https://learn.microsoft.com/en-us/windows/win32/api/commctrl/ns-commctrl-hditemw wrote: This flag (HDF_SORTDOWN or HDF_SORTUP) cannot be combined with HDF_IMAGE or HDF_BITMAP.
You can display a bitmap to the left (HDF_BITMAP) or right (HDF_BITMAP | HDF_BITMAP_ON_RIGHT) of the header text to indicate the direction of the sort. The disadvantage is that it takes up a bit more space in the header. See below.

Code: Select all

LV_SortArrow(LV, Column) {
   static OBM_UPARROWD        := 32743
   static OBM_DNARROWD        := 32742
   static LR_SHARED           := 0x8000
   static HDI_FORMAT          := 0x0004
   static HDI_BITMAP          := 0x0010
   static HDF_RIGHT           := 0x0001
   static HDF_BITMAP_ON_RIGHT := 0x1000
   static HDF_BITMAP          := 0x2000
   static LVM_FIRST           := 0x1000
   static HDM_FIRST           := 0x1200
   static HDM_GETITEM         := HDM_FIRST + 11
   static HDM_SETITEM         := HDM_FIRST + 12
   static LVM_GETHEADER       := LVM_FIRST + 31
   static HDITEM_SIZE         := 6 * 4 + 6 * A_PtrSize
   static BitmapOffset        := 2 * 4 + 1 * A_PtrSize
   static FormatOffset        := 3 * 4 + 2 * A_PtrSize
   static ColumnState         := false
   static PrevColumn          := 0
   
   HDITEM := Buffer(HDITEM_SIZE)
   NumPut 'UInt', HDI_FORMAT | HDI_BITMAP, HDITEM, 0
   HDR := SendMessage(LVM_GETHEADER, 0, 0, LV)
   if PrevColumn && Column != PrevColumn {
      SendMessage(HDM_GETITEM, PrevColumn - 1, HDITEM, HDR)
      Format := NumGet(HDITEM, FormatOffset, 'Int')
      NumPut 'Int', Format & ~HDF_BITMAP, HDITEM, FormatOffset
      SendMessage(HDM_SETITEM, PrevColumn - 1, HDITEM, HDR)
      ColumnState := false
   }
   PrevColumn := Column
   ColumnState := !ColumnState
   SendMessage(HDM_GETITEM, Column - 1, HDITEM, HDR)
   Format := NumGet(HDITEM, FormatOffset, 'Int')
   NumPut 'Ptr', DllCall('LoadImageW', 'Ptr', 0, 'Ptr', ColumnState ? OBM_UPARROWD : OBM_DNARROWD, 'UInt', 0, 'Int', 0, 'Int', 0, 'UInt', LR_SHARED, 'Ptr'), HDITEM, BitmapOffset
   NumPut 'Int', Format | HDF_BITMAP | (Format & HDF_RIGHT ? 0 : HDF_BITMAP_ON_RIGHT), HDITEM, FormatOffset
   return SendMessage(HDM_SETITEM, Column - 1, HDITEM, HDR)
}

LV_SortArrow3.png
LV_SortArrow3.png (7.32 KiB) Viewed 1873 times
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

Post Reply

Return to “Tips and Tricks”