Is it possible to change a Group Header in a grouped listview
Posted: 27 Nov 2021, 14:22
by dd900
I have a ListView with around ~5000 items backed by an array of objects. I use a DropDownList to control grouping. All is working fine. One of my grouping options is to group the items by their path. And one of my listview contextmenu options is to move an entire folder. All works fine there as well. I am able to move the folder and update the listview accordingly. The only thing that I haven't figured out is how to update the Header of the group. I need to change it because the Header is the name of the path of the items in the group. I also have the count of items in the group header. So if I delete an item from a group I would also need to update the count in the header.
Would LV_EX_GroupInsert overwrite the header if I use the same GroupId? And would I have to re-add the items to the group if I went this route?
Would LV_EX_GroupInsert overwrite the header if I use the same GroupId? And would I have to re-add the items to the group if I went this route?
MainScript
Code: Select all
#NoTrayIcon
#NoEnv
#SingleInstance Force
#KeyHistory, 0
#MaxMem 1024
Process, Priority,, A
SetBatchLines, -1
ListLines, Off
SetWorkingDir, % A_ScriptDir
if (!A_IsAdmin)
Run, *RunAs "%A_ScriptFullPath%"
#Include lib\functions.ahk
#Include lib\LV_Functions.ahk
#Include lib\classCharacter.ahk
testing := 1
CharactersArray := []
Mugen := { Folder: "", Exe: "", CharsFolder: "" }
SelectedRow := ""
SelectedIndex := ""
SelectedCharacter := ""
Columns := { name: 1, displayname: 2, author: 3, versiondate: 4, mugenversion: 5, path: 6, deffile: 7, index: 8 }
if (testing)
{
Mugen.Folder := "R:\roms\mugen\LEGACY"
Mugen.Folder := "R:\roms\mugen\Megaman Robot Master Mayhem"
Mugen.CharsFolder := Mugen.Folder "\chars"
}
else
{
if (A_Args.Length() > 0 && FileExist(A_Args[1]))
Mugen.Folder := A_Args[1]
else if (InStr(FileExist(A_ScriptDir "\chars"), "D"))
Mugen.Folder := A_ScriptDir
else
{
FileSelectFolder, mugenFolder,, 0, Select M.U.G.E.N. Folder
Mugen.Folder := mugenFolder
}
if (ErrorLevel || Mugen.Folder = "")
ExitApp
Mugen.CharsFolder := Mugen.Folder "\chars"
}
Loop, Files, % Mugen.CharsFolder "\*", DR
{
if (A_LoopFileName = "backup")
FileRemoveDir, % A_LoopFilePath, 1
}
Loop, Files, % Mugen.CharsFolder "\*.def", R
{
IniRead, sections, % A_LoopFilePath
if (sections = "" || sections = "ERROR" || !InStr(sections, "Info") || InStr(sections, "SceneDef") || InStr(sections, "StageInfo"))
continue
CharactersArray.Push(new Character(A_LoopFilePath))
}
GoSub, CreateListviewContextMenu
GoSub, CreateCharactersGui
EmptyMem()
return
#Include Character_ContexMenu.ahk
#Include DEF-CNS_Gui.ahk
#Include AddKey_Gui.ahk
CreateCharactersGui:
Gui, Characters:New, -DPIScale +HwndhCharacters +Resize MinSize400x400 +Label_Characters, M.U.G.E.N. Characters Manager
Gui, Characters:Margin, 20, 20
Gui, Characters:Font, s10 q5
Gui, Characters:Add, DropDownList, +HwndhCharsDDL vCharactersDDL g_CharactersDDL, No Grouping||Name|DisplayName|Author|VersionDate|MugenVersion|Path
Gui, Characters:Add, ListView, w600 h440 -Multi +ReadOnly +Report +AltSubmit +HwndhCharsLV vCharactersLV g_CharactersLV, Name|DisplayName|Author|Version|Mugen Version|Folder|Def File|Index
Gosub, FillListView
Gui, Characters:Show, center
return
FillListView:
for i, char in CharactersArray
LV_Add(""
, UnQuote(char.DEF.Info["name"])
, UnQuote(char.DEF.Info["displayname"])
, UnQuote(char.DEF.Info["author"])
, char.DEF.Info["versiondate"]
, char.DEF.Info["mugenversion"]
, char.Path
, char.DefName
, i)
LV_ModifyCol(8, "50 Integer")
return
_CharactersLV:
if (A_GuiEvent != "Normal")
return
_CharactersContextMenu:
if (A_GuiControl = "CharactersLV")
{
SelectedRow := LV_GetNext(0, "F")
if (!SelectedRow)
{
MsgBox, No Character Selected
return
}
WinGetPos, cX, cY, cW, cH, M.U.G.E.N. Characters Manager
nX := cX + 16, nY := cY + 26
, LV_GetText(SelectedIndex, SelectedRow, 8)
, SelectedCharacter := CharactersArray[SelectedIndex]
if (A_GuiEvent != "Normal")
Menu, CharacterContex, Show, % A_GuiX, % A_GuiY
}
return
_CharactersDDL:
Gui, Characters:Submit, NoHide
propName := Format("{:L}", CharactersDDL)
if (propName == prevPropName)
return
LV_EX_GroupRemoveAll(hCharsLV)
if (propName == "no grouping")
LV_EX_EnableGroupView(hCharsLV, false)
else
{
obj := {}
if (!LV_EX_IsGroupViewEnabled(hCharsLV))
LV_EX_EnableGroupView(hCharsLV)
for i, char in CharactersArray
{
key := propName == "path" ? char.Path : char.DEF.Info[propName]
if (key = "ERROR")
continue
if (!obj.HasKey(key))
obj[key] := []
obj[key].Push(i)
}
groupId := 10
, groupIndex := 1
for k, arr in obj
{
arrCount := arr.Count()
if (arrCount > 1)
{
LV_EX_GroupInsert(hCharsLV, groupId, k " - Count(" arrCount ")", 1, groupIndex)
, row := 1
Loop, % LV_GetCount()
{
LV_GetText(text, row, Columns[propName])
if (propName == "author" || propName == "name" || propName == "displayname")
text := Quoted(text)
if (text = k)
LV_EX_SetGroup(hCharsLV, row, groupId)
row += 1
}
LV_EX_GroupSetState(hCharsLV, groupId, "Collapsed", "Collapsible")
groupId += 10
, groupIndex += 1
}
}
}
prevPropName := propName
return
_CharactersSize:
If (A_EventInfo = 1)
Return
AutoXYWH("*wh", "CharactersLV")
return
_CharactersClose:
ExitApp
ContextMenu Script
Code: Select all
CreateListviewContextMenu:
Menu, RemoveCharcterMenu, Add, Rename DEF, RemoveChar_Rename
Menu, RemoveCharcterMenu, Add, Delete DEF, RemoveChar_Delete
Menu, RemoveCharcterMenu, Add, Delete Folder, RemoveChar_DeleteFolder
Menu, CharacterContex, Add, Open Char DEF, CreateCharDEFGui
Menu, CharacterContex, Add, Open Char CNS, CreateCharCNSGui
Menu, CharacterContex, Add, Open Char Folder, OpenCharacterFolder
Menu, CharacterContex, Add
Menu, CharacterContex, Add, Remove Charcter, :RemoveCharcterMenu
Menu, CharacterContex, Add
Menu, CharacterContex, Add, Move Char Folder, MoveCharacterFolder
return
RemoveChar_Rename:
Gui, Characters: +OwnDialogs
MsgBox, 0x40024, Confirm, % "Are you sure?`n`nThis will rename """ SelectedCharacter.DefName """ to """ SelectedCharacter.DefName ".bak"" and remove the character from the list."
IfMsgBox Yes
{
outFile := SelectedCharacter.DefFile ".bak"
while (FileExist(outFile))
outFile := SelectedCharacter.DefFile "(" A_Index ").bak"
FileMove, % SelectedCharacter.DefFile, % outFile
Gosub, DeleteListViewChar
}
return
RemoveChar_Delete:
Gui, Characters: +OwnDialogs
MsgBox, 0x40024, Confirm, % "Are you sure?`n`nThis will Delete """ SelectedCharacter.DefName """ and remove the character from the list."
IfMsgBox Yes
{
FileRecycle, % SelectedCharacter.DefFile
Gosub, DeleteListViewChar
}
return
RemoveChar_DeleteFolder:
Gui, Characters: +OwnDialogs
Gui, Characters:Default
path := SelectedCharacter.Path
, rows := LV_FindAllRows(path, Columns["path"], "path")
if (rows.Count() > 1)
{
charText := ""
, indexArray := []
for i, row in rows
{
LV_GetText(defName, row, 7)
, LV_GetText(index, row, 8)
, charText .= """" defName """`n"
, indexArray.Push({ Row: row, Index: index })
}
charText := RTrim(charText, "`n")
MsgBox, 0x40024, Confirm, % "Are you sure?`nThere is more than one def file in this folder.`n`nThis will Delete`n" charText "`nand remove the characters from the list."
IfMsgBox Yes
{
FileRemoveDir, % path, 1
deletedCount := 0
for i, obj in indexArray
{
CharactersArray.Delete(obj.Index)
if (A_Index = 1)
LV_Delete(obj.Row)
else
{
row := obj.Row - deletedCount
LV_Delete(row)
}
deletedCount += 1
}
}
}
else
{
MsgBox, 0x40024, Confirm, % "Are you sure?`n`nThis will Delete """ path """ and remove the character from the list."
IfMsgBox Yes
{
FileRemoveDir, % path, 1
Gosub, DeleteListViewChar
}
}
EmptyMem()
return
OpenCharacterFolder:
explorerWinTitle := SelectedCharacter.DirName
Run, % "explorer /e," SelectedCharacter.Path
return
MoveCharacterFolder:
Gui, Characters: +OwnDialogs
Gui, Characters:Default
FileSelectFolder, newFolder, % charsFolder, 1, Select New Folder
oldPath := SelectedCharacter.Path
, newPath := newFolder "\" SelectedCharacter.DirName
, rows := LV_FindAllRows(oldPath, Columns["path"], "path")
while (InStr(FileExist(newPath), "D"))
newPath := newFolder "\" SelectedCharacter.DirName "(" A_Index ")"
FileMoveDir, % oldPath, % newPath
if (rows.Count() > 1)
{
for i, row in rows
{
LV_GetText(defName, row, 7)
, LV_GetText(index, row, 8)
, newDefFile := newPath "\" defName
, CharactersArray[index] := new Character(newDefFile)
, LV_Modify(row,, UnQuote(CharactersArray[index].DEF.Info["name"]), UnQuote(CharactersArray[index].DEF.Info["displayname"]), UnQuote(CharactersArray[index].DEF.Info["author"]), CharactersArray[index].DEF.Info["versiondate"], CharactersArray[index].DEF.Info["mugenversion"], CharactersArray[index].Path, CharactersArray[index].DefName, index)
}
}
else
{
newDefFile := newPath "\" SelectedCharacter.DefName
, CharactersArray[SelectedIndex] := new Character(newDefFile)
, LV_Modify(SelectedRow,, UnQuote(CharactersArray[SelectedIndex].DEF.Info["name"]), UnQuote(CharactersArray[SelectedIndex].DEF.Info["displayname"]), UnQuote(CharactersArray[SelectedIndex].DEF.Info["author"]), CharactersArray[SelectedIndex].DEF.Info["versiondate"], CharactersArray[SelectedIndex].DEF.Info["mugenversion"], CharactersArray[SelectedIndex].Path, CharactersArray[SelectedIndex].DefName, SelectedIndex)
}
return
DeleteListViewChar:
Gui, Characters:Default
CharactersArray.Delete(SelectedIndex)
, LV_Delete(SelectedRow)
return
LV_Functions
there are more files to the script but this is all the relevant codeCode: Select all
LV_FindFirstRow(str, column, propName)
{
out := 0
Loop, % LV_GetCount()
{
row := A_Index
, LV_GetText(text, row, column)
if (propName == "author" || propName == "name" || propName == "displayname")
text := Quoted(text)
if (text = str)
{
out := row
break
}
}
return out
}
LV_FindAllRows(str, column, propName)
{
out := []
Loop, % LV_GetCount()
{
row := A_Index
, LV_GetText(text, row, column)
if (propName == "author" || propName == "name" || propName == "displayname")
text := Quoted(text)
if (text = str)
out.Push(row)
}
return out
}
; ======================================================================================================================
; LV_EX_GroupInsert - Inserts a group into a list-view control.
; ======================================================================================================================
LV_EX_GroupInsert(HLV, GroupID, Header, Align := "", Index := -1) {
; LVM_INSERTGROUP = 0x1091 -> msdn.microsoft.com/en-us/library/bb761103(v=vs.85).aspx
Static Alignment := {1: 1, 2: 2, 4: 4, C: 2, L: 1, R: 4}
Static SizeOfLVGROUP := (4 * 6) + (A_PtrSize * 4)
Static OffHeader := 8
Static OffGroupID := OffHeader + (A_PtrSize * 3) + 4
Static OffAlign := OffGroupID + 12
Static LVGF := 0x11 ; LVGF_GROUPID | LVGF_HEADER | LVGF_STATE
Static LVGF_ALIGN := 0x00000008
Align := (A := Alignment[SubStr(Align, 1, 1)]) ? A : 0
Mask := LVGF | (Align ? LVGF_ALIGN : 0)
PHeader := A_IsUnicode ? &Header : LV_EX_PWSTR(Header, WHeader)
VarSetCapacity(LVGROUP, SizeOfLVGROUP, 0)
NumPut(SizeOfLVGROUP, LVGROUP, 0, "UInt")
NumPut(Mask, LVGROUP, 4, "UInt")
NumPut(PHeader, LVGROUP, OffHeader, "Ptr")
NumPut(GroupID, LVGROUP, OffGroupID, "Int")
NumPut(Align, LVGROUP, OffAlign, "UInt")
SendMessage, 0x1091, %Index%, % &LVGROUP, , % "ahk_id " . HLV
Return ErrorLevel
}
; ======================================================================================================================
; LV_EX_GroupRemove - Removes a group from a list-view control.
; ======================================================================================================================
LV_EX_GroupRemove(HLV, GroupID) {
; LVM_REMOVEGROUP = 0x1096 -> msdn.microsoft.com/en-us/library/bb761149(v=vs.85).aspx
SendMessage, 0x10A0, %GroupID%, 0, , % "ahk_id " . HLV
Return ErrorLevel
}
; ======================================================================================================================
; LV_EX_GroupRemoveAll - Removes all groups from a list-view control.
; ======================================================================================================================
LV_EX_GroupRemoveAll(HLV) {
; LVM_REMOVEALLGROUPS = 0x10A0 -> msdn.microsoft.com/en-us/library/bb761147(v=vs.85).aspx
SendMessage, 0x10A0, 0, 0, , % "ahk_id " . HLV
Return ErrorLevel
}
; ======================================================================================================================
; LV_EX_GroupSetState - Set group state (requires Win Vista+ for most states).
; ======================================================================================================================
LV_EX_GroupSetState(HLV, GroupID, States*) {
; LVM_SETGROUPINFO = 0x1093 -> msdn.microsoft.com/en-us/library/bb761167(v=vs.85).aspx
Static LVGS := {Collapsed: 0x01, Collapsible: 0x08, Focused: 0x10, Hidden: 0x02, NoHeader: 0x04, Normal: 0x00
, Selected: 0x20, SubSeted: 0x40, SubSetedLinkFocused: 0x80, 0: 0, 1: 1, 2: 2, 4: 4, 8: 8, 16: 16
, 32: 32, 64: 64, 128: 128}
Static LVGF := 0x04 ; LVGF_STATE
Static SizeOfLVGROUP := (4 * 6) + (A_PtrSize * 4)
Static OffHeader := 8
Static OffStateMask := 8 + (A_PtrSize * 3) + 8
Static OffState := OffStateMask + 4
SetStates := 0
For Each, State In States {
If !LVGS.HasKey(State)
Return False
SetStates |= LVGS[State]
}
VarSetCapacity(LVGROUP, SizeOfLVGROUP, 0)
NumPut(SizeOfLVGROUP, LVGROUP, 0, "UInt")
NumPut(LVGF, LVGROUP, 4, "UInt")
NumPut(SetStates, LVGROUP, OffStateMask, "UInt")
NumPut(SetStates, LVGROUP, OffState, "UInt")
SendMessage, 0x1093, %GroupID%, % &LVGROUP, , % "ahk_id " . HLV
Return ErrorLevel
}
; ======================================================================================================================
; LV_EX_GetGroup - Gets the ID of the group the list-view item belongs to.
; ======================================================================================================================
LV_EX_GetGroup(HLV, Row) {
; LVM_GETITEMA = 0x1005 -> http://msdn.microsoft.com/en-us/library/bb774953(v=vs.85).aspx
Static LVITEMSize := 48 + (A_PtrSize * 3)
Static OffGroupID := 28 + (A_PtrSize * 3)
VarSetCapacity(LVITEM, LVITEMSize, 0)
NumPut(0x00000100, LVITEM, 0, "UInt") ; LVIF_GROUPID
NumPut(Row - 1, LVITEM, 4, "Int")
SendMessage, 0x1005, 0, % &LVITEM, , % "ahk_id " . HLV
Return NumGet(LVITEM, OffGroupID, "UPtr")
}
; ======================================================================================================================
; LV_EX_SetGroup - Assigns a list-view item to an existing group.
; ======================================================================================================================
LV_EX_SetGroup(HLV, Row, GroupID) {
; LVM_SETITEMA = 0x1006 -> http://msdn.microsoft.com/en-us/library/bb761186(v=vs.85).aspx
Static LVITEMSize := 48 + (A_PtrSize * 3)
Static OffGroupID := 28 + (A_PtrSize * 3)
VarSetCapacity(LVITEM, LVITEMSize, 0)
NumPut(0x00000100, LVITEM, 0, "UInt") ; LVIF_GROUPID
NumPut(GroupID, LVITEM, OffGroupID, "UPtr")
NumPut(Row - 1, LVITEM, 4, "Int")
SendMessage, 0x1006, 0, % &LVITEM, , % "ahk_id " . HLV
Return ErrorLevel
}
; ======================================================================================================================
; LV_EX_EnableGroupView - Enables or disables whether the items in a list-view control display as a group.
; ======================================================================================================================
LV_EX_EnableGroupView(HLV, Enable := True) {
; LVM_ENABLEGROUPVIEW = 0x109D -> msdn.microsoft.com/en-us/library/bb774900(v=vs.85).aspx
SendMessage, 0x109D, % (!!Enable), 0, , % "ahk_id " . HLV
Return (ErrorLevel >> 31) ? 0 : 1
}
; ======================================================================================================================
; LV_EX_HasGroup - Determines whether the list-view control has a specified group.
; ======================================================================================================================
LV_EX_HasGroup(HLV, GroupID) {
; LVM_HASGROUP = 0x10A1 -> msdn.microsoft.com/en-us/library/bb761097(v=vs.85).aspx
SendMessage, 0x10A1, %GroupID%, 0, , % "ahk_id " . HLV
Return ErrorLevel
}
; ======================================================================================================================
; LV_EX_IsGroupViewEnabled - Checks whether the list-view control has group view enabled.
; ======================================================================================================================
LV_EX_IsGroupViewEnabled(HLV) {
; LVM_ISGROUPVIEWENABLED = 0x10AF -> msdn.microsoft.com/en-us/library/bb761133(v=vs.85).aspx
SendMessage, 0x10AF, 0, 0, , % "ahk_id " . HLV
Return ErrorLevel
}
; ======================================================================================================================
; LV_EX_PWSTR - Internal function needed for ANSI builts, returns a pointer to a Unicode string.
; ======================================================================================================================
LV_EX_PWSTR(Str, ByRef WSTR) {
VarSetCapacity(WSTR, StrPut(Str, "UTF-16") * 2, 0)
StrPut(Str, &WSTR, "UTF-16")
Return &WSTR
}