Listview collapsable groups with checkboxes?

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
andymbody
Posts: 993
Joined: 02 Jul 2017, 23:47

Listview collapsable groups with checkboxes?

01 Sep 2023, 07:01

I would like to create a listview that has the same design as the screenshot below.

I can do these
1. Each row has a checkbox
2. Each row is part of a collapsible group

I DONT know how to do these
3. Each group "header" also has a checkbox that can generate an event (which can check/uncheck all rows within group)
4. Each group "header" is highlighted in a different color than the normal rows within the group (as shown in screenshot)
5. Each group can include the "dups" value (as shown in screenshot).
6. Rows within a group are shifted (tabbed) to right slightly (like screenshot)
7. (bonus) Group " header" to have collapsible toggle on left, instead of right.

I'm not sure how much work is involved to make 3-7 happen. As far as I can tell, most (if not all) of this has not been included in the native AHK ListView capabilities. Which means that I will need to create custom functions to do it. @just me's LV_EX function library provides lots of extended capabilities, but I have not found these yet. I have been looking thru it (and online searches) for clues as to how to build functions that can provide the things in 3-7. But if anyone knows of related posts, libraries, or tutorials that might help in this search, I would appreciate the tips.

Thanks!
Andy


Example screenshot:
UPDATE: After further investigation, it looks like the example "view" is most likely a 3rd party ActiveX called TList8, which must be licensed. I would still like to "simulate" some or all of the features mentioned, but at least I now know that this is not a SysListView321.
GroupCheckboxListviewExample.png
GroupCheckboxListviewExample.png (6.17 KiB) Viewed 1904 times
My current code (example)

Code: Select all

#SingleInstance Force
SetBatchLines -1

Gui, Add, Listview, checked xm w600 r20 Grid hwndHLV vVLV, Group - FileName|Path|Dups|Size

size := 1
Loop, 9 {
	size += (Mod(A_index, 3)==0) ? 1 : 0
	LV_Add("", "File" . A_Index, "Path" . A_Index, , size . "kb")
}

Loop, 4
	LV_ModifyCol(A_Index, 100)

LV_EX_GroupInsert(HLV, 1, "Group 1"), LV_EX_GroupSetState(HLV, 1, "Collapsible")
LV_EX_GroupInsert(HLV, 2, "Group 2"), LV_EX_GroupSetState(HLV, 2, "Collapsible")
LV_EX_GroupInsert(HLV, 3, "Group 3"), LV_EX_GroupSetState(HLV, 3, "Collapsible")

gNum := 1
Loop, 9
{
	gNum += (Mod(A_index, 3)==0) ? 1 : 0
	LV_EX_SetGroup(HLV, A_Index, gNum)
}
LV_EX_EnableGroupView(HLV)


Gui, Show, , Example

return

; ======================================================================================================================
; 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_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_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_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
}
User avatar
Hellbent
Posts: 2114
Joined: 23 Sep 2017, 13:34

Re: Listview collapsable groups with checkboxes?

07 Sep 2023, 19:54

If you have decent math and logic skills you can create your own control.

I have never used a normal listview control before let alone designed my own but it looks like what you basically have is a window inside a window inside another window ( The control requires up to two windows. One is nested in your main window and the other is nested inside the other one ).
Your items can be done using a "panel" concept, where every item is its own panel object with a variable height. The idea is that you create your panel whenever it changes ( expands for example ) and then you redraw it and the panels that are below it with the new positions.

.
20230907202247.png
20230907202247.png (86.57 KiB) Viewed 1765 times
.

Panels:
.
20230907202315.png
20230907202315.png (3.91 KiB) Viewed 1765 times
.

This script here uses a somewhat similar panel concept but it doesn't need to redraw because there is no changes to the panel sizes.

Image

viewtopic.php?f=18&t=89417&p=502038#p484882

The example above uses actual windows for the panels but a simple drawing routine with some math and logic can handle the panels as one object / image like in this example ( everything is redrawn when something changes )

.
resize redraw.gif
resize redraw.gif (1.05 MiB) Viewed 1765 times
.

viewtopic.php?f=6&t=109733
User avatar
andymbody
Posts: 993
Joined: 02 Jul 2017, 23:47

Re: Listview collapsable groups with checkboxes?

07 Sep 2023, 20:33

Hellbent wrote:
07 Sep 2023, 19:54
If you have decent math and logic skills you can create your own control.
Interesting idea. Hadn't thought of that.

I've studied your designs and code over the years, and must say, you went from AHK beginner to master very quickly (based on the impression and timeline of your tutorials and videos). I appreciate the feedback and all your efforts/tools to assist the rest of us with GUI and graphics projects. I have not used any of the tools yet, but they seem very nice.

Andy
User avatar
Hellbent
Posts: 2114
Joined: 23 Sep 2017, 13:34

Re: Listview collapsable groups with checkboxes?

08 Sep 2023, 13:49

andymbody wrote:
07 Sep 2023, 20:33
I've studied your designs and code over the years, and must say, you went from AHK beginner to master very quickly
I'm far from a master of anything but point taken. Thank you.

Just for fun I put some thought into what would be needed to create a listview control and I think that the main components are a main housing / frame , scroll bars, and panels.

I started prototyping out the frame and have more or less worked out a few of the details. My next step would be to prototype a panel class and from there work out how to combine it with the frame.

.
listview 1.gif
listview 1.gif (631.83 KiB) Viewed 1702 times
.

********************************
*** Requires Window 8+ ***
********************************

Code: Select all

;****************************************************************************************************************************************************************************
#Include <My Altered GDIP lib> ;GDIP:  https://www.autohotkey.com/boards/viewtopic.php?f=6&t=6517
;~ #Include <PopUpWindow_V2> ; Paste at the bottom of the script 
;~ #Include <HB Vectors v2>  ; Paste at the bottom of the script 
;****************************************************************************************************************************************************************************
#SingleInstance, Force
SetBatchLines, -1
Gdip_Startup()

;***********************************
Gui1 := {}
Gui1.W := 500
Gui1.H := 420
Gui1.X := A_ScreenWidth - Gui1.W - 40
Gui1.Y := 150
Gui1.Scale := 1
;***********************************
Gui, New, +AlwaysOnTop +hwndhwnd -DPIScale
Gui1.Hwnd := hwnd
;***********************************

;***********************************
cc := Gui1
Gui, Show, % "x" cc.X " y" cc.Y " w" cc.W * cc.Scale " h" cc.H * cc.Scale
;***********************************
x := 10 ;at x *margin
y := 10 ;at y *margin 
w := 400
h := 400
cc := Gui1.Listview1 := New PopUpWindow( { AutoShow: 1 , X: x , Y: y , W: w * Gui1.Scale , H: h * Gui1.Scale , Options: " -DPIScale +Parent" Gui1.Hwnd } ) 

cc.Sections := []

x := 0
w := 120
cc.Sections[ 1 ] := { Header: "Section 1" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 2 ] := { Header: "Section 2" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 3 ] := { Header: "Section 3" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 4 ] := { Header: "Section 4" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 5 ] := { Header: "Section 5" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 


OnMessage( 0x201 , Func( "ClickEvent" ).Bind( Gui1 ) )
DrawListControl( Gui1 )
return
GuiClose:
GuiContextMenu:
*ESC::ExitApp

RALT::PopUpWindow.Helper()

ClickEvent( Gui1 ){
	CoordMode, Mouse, Client
	MouseGetPos, x , y
	MouseVector := New Vector( ( x / Gui1.Scale ) - Gui1.Listview1.X , ( y / Gui1.Scale ) - Gui1.Listview1.Y )
	closest := 1000
	cIndex := ""
	Loop, % Gui1.Listview1.Sections.Length()	{
		cc := Gui1.Listview1.Sections[ A_Index ].MoveVector
		if( ( nDist := MouseVector.Dist( cc ) ) <= 10 ){
			if( nDist < closest ){
				closest := nDist
				cIndex := A_Index
			}
		}
	}
	if( cIndex ){
		cc := Gui1.Listview1.Sections[ cIndex ]
		lx := ""
		ly := ""
		While( GetKeyState( "LButton" , "P" ) ){
			MouseGetPos, x , y
			x /= Gui1.Scale
			y /= Gui1.Scale
			x -= Gui1.Listview1.X
			y -= Gui1.Listview1.Y
			if( cIndex = 1 ){
				if( x < 1 && lx != x ){
					vc := lx := cc.MoveVector.X := 1
					cc.W := 1
					ox := Gui1.Listview1.Sections[ cIndex + 1 ].X
					nx := Gui1.Listview1.Sections[ cIndex + 1 ].X := cc.MoveVector.X + 1
					Loop, % Gui1.Listview1.Sections.Length() - 1	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}else if( lx != x ){
					vc := lx := cc.MoveVector.X := x
					cc.W := x
					Loop, % Gui1.Listview1.Sections.Length() - 1	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}
			}else{
				if( x <= Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X && lx != x ){
					vc := lx := cc.MoveVector.X := Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X + 1
					cc.W := lx - Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X
					Loop, % Gui1.Listview1.Sections.Length() - cIndex 	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}else if( lx != x ){
					vc := lx := cc.MoveVector.X := x
					cc.W := x - Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X
					Loop, % Gui1.Listview1.Sections.Length() - cIndex 	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}
				ToolTip, % cIndex
			}
			DrawListControl( Gui1 )
		}
		ToolTip
	}
}

DrawListControl( Gui1 ){
	if( Gui1.Busy )
		return 
	Gui1.Busy := 1
	cc := Gui1.Listview1
	cc.ClearWindow()
	cc.DrawBitmap( HB_BITMAP_MAKER( Gui1 , Gui1.Scale ) , { X: 0 , Y: 0 , W: cc.W , H: cc.H } , dispose := 1 , AutoUpdate := 1 )
	sleep, 30
	Gui1.Busy := 0
}

HB_BITMAP_MAKER( Gui1 , ScaleFactor := 1 ){
	;Bitmap Created Using: HB Bitmap Maker
	pBitmap := Gdip_CreateBitmap( Gui1.Listview1.W , Gui1.Listview1.H ) , G := Gdip_GraphicsFromImage( pBitmap ) , Gdip_SetSmoothingMode( G , 2 )
	;background
	x := -2 
	y := -2
	w := ( Gui1.Listview1.W / Gui1.Scale ) + 4
	h := ( Gui1.Listview1.H / Gui1.Scale ) + 4
	Brush := Gdip_BrushCreateSolid( "0xFFFFFFFF" ) 
	, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
	, Gdip_DeleteBrush( Brush )
	;header background
	x := -2
	y := -2
	w := ( Gui1.Listview1.W / Gui1.Scale ) + 4
	h := 26
	Brush := Gdip_BrushCreateSolid( "0xFFD6DBE9" ) 
	, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
	, Gdip_DeleteBrush( Brush )
	;section headers
	Loop, % Gui1.Listview1.Sections.Length()	{
		cc := Gui1.Listview1.Sections[ A_Index ]
		if( cc.W > 5 ){	
			Brush := Gdip_BrushCreateSolid( "0xFFFF0000" ) 
			, Gdip_TextToGraphics( G , cc.Header , "s" 12 * ScaleFactor " vCenter NoWrap c" Brush " x" cc.X * ScaleFactor " y" cc.Y * ScaleFactor  , "Segoe ui" , cc.W * ScaleFactor , cc.H * ScaleFactor ) 
			, Gdip_DeleteBrush( Brush )
		}
		x := cc.MoveVector.X
		y := cc.MoveVector.Y
		w := 1
		h := Gui1.H 
		Gdip_SetSmoothingMode( G , 1 )
		Brush := Gdip_BrushCreateSolid( "0xFFFF00FF" ) 
		, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
		, Gdip_DeleteBrush( Brush )
		Gdip_SetSmoothingMode( G , 2 )
	}
	Gdip_DeleteGraphics( G )
	return pBitmap
}


***Required classes***

Code: Select all

;************
;Vector Class
;**************************************************************************************************************************************************************************
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;**************************************************************************************************************************************************************************
Class Vector	{
	;Written By: HB
	;Date: Sept 23rd, 2022
	;Last Edit: Sept 24th, 2022
	;Purpose: Vector math class 
	;Credit: Rohwedder 
	;Resources: 
		;Line intercept concepts and code: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=37175
		;Create an Arrow: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=92039&p=479129#p478944
		;Getting an angle: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=108760&p=483661#p483678
		;Setting an Angle: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=108760&p=483786#p483811
		;
		
	static RadToDeg := 45 / ATan( 1 ) 
		, DegToRad := ATan( 1 ) / 45 
		
	__New( x := 0 , y := 0 , rotate := 0 ){ 
		if( IsObject( x ) ){
			if( rotate = 3 ){
				This.X := x.X * -1
				,This.Y := x.Y * -1
			}else if( rotate = 2 ){
				This.X := x.Y 
				,This.Y := x.X * -1
			}else if( rotate = 1 ){
				This.X := x.Y * -1
				,This.Y := x.X 
			}else{
				This.X := x.X
				,This.Y := x.Y
			}
		}else{
			if( rotate = 3 ){
				This.X := X * -1
				,This.Y := Y * -1
			}else if( rotate = 2 ){
				This.X := Y 
				,This.Y := X * -1
			}else if( rotate = 1 ){
				This.X := Y * -1
				,This.Y := X 
			}else{
				This.X := X
				,This.Y := Y
			}
		}
	}
	Add( x , y := "" ){
		if( IsObject( x ) ){
			This.X += x.X
			,This.Y += x.Y
		}else if( y = "" ){
			This.X += x 
			,This.Y += x
		}else{
			This.X += x 
			,This.Y += y 
		}
	}
	Sub( x , y := "" ){
		if( IsObject( x ) ){
			This.X -= x.X
			,This.Y -= x.Y
		}else if( y = "" ){
			This.X -= X
			,This.Y -= X
		}else{
			This.X -= X
			,This.Y -= Y
		}
	}
	Div( x , y := "" ){
		if( IsObject( x ) ){
			This.X /= x.X
			,This.Y /= x.Y
		}else if( x && y = "" ){
			This.X /= x 
			,This.Y /= x 
		}else{
			This.X /= X
			,This.Y /= Y
		}
	}
	Mult( x , y := "" ){
		if( IsObject( x ) ){
			This.X *= x.X
			,This.Y *= x.Y
		}else if( x && y = "" ){
			This.X *= x 
			,This.Y *= x 
		}else{
			This.X *= X
			,This.Y *= Y
		}
	}
	Dist( x , y := "" ){
		if( IsObject( x ) )
			return Sqrt( ( ( This.X - x.X ) **2 ) + ( ( This.Y - x.Y ) **2 ) )
		else 
			return Sqrt( ( ( This.X - X ) **2 ) + ( ( This.Y - Y ) **2 ) )
	}
	GetMag(){
		return Sqrt( This.X * This.X + This.Y * This.Y )
	}
	SetMag( magnitude ){
		local m := This.GetMag()
		This.X := This.X * magnitude / m
		,This.Y := This.Y * magnitude / m
	}
	MagSq(){
		return This.GetMag()**2
	}	
	Dot( x , y := "" ){
		if( IsObject( x ) )
			return ( This.X * x.X ) + ( This.Y * x.Y )
		else
			return ( This.X * X ) + ( This.Y * Y )
	}
	Cross( x , y := "" ){
		if( IsObject( x ) )
			return This.X * x.Y - This.Y * x.X
		else
			return This.X * Y - This.Y * X
		
	}
	Norm(){
		local m := This.GetMag()
		This.X /= m
		This.Y /= m
	}
	GetAngle(){ 
		local angle 
		( (  angle := Vector.RadToDeg * DllCall( "msvcrt\atan2" , "Double" , This.Y , "Double" , This.X , "CDECL Double" ) ) < 0 ) ? ( angle += 360 )
		return angle
	}
	SetAngle( newAngle := 0 , NewVector := 0 ){
		local Angle := This.GetAngle()
		, ChangeAngle := newAngle - Angle 
		, Co := Cos( Vector.DegToRad * ChangeAngle )
		, Si := Sin( Vector.DegToRad * ChangeAngle )
		, X := This.X 
		, Y := This.Y
		, X2 := X * Co - Y * Si 
		, Y2 := X * Si + Y * Co 
		
		if( !NewVector )
			This.X := X2 , This.Y := Y2
		else 
			return New Vector( X2 , Y2 )
	}
	RotateAngle( rotationAmount := 90 , NewVector := 0 ){
		local Co := Cos( Vector.DegToRad * rotationAmount )
		, Si := Sin( Vector.DegToRad * rotationAmount )
		, X := This.X 
		, Y := This.Y
		, X2 := X * Co - Y * Si 
		, Y2 := X * Si + Y * Co 
		
		if( !NewVector )
			This.X := X2 , This.Y := Y2
		else 
			return New Vector( X2 , Y2 )
	}
	;********************************************
	;class methods
	TestLineInterceptPoint( interceptPoint , Line1 , Line2 ){ ; Line = { Start: { X: , Y: } , End: { X: , Y: } } , interceptPoint = { X: , Y: }
		local
		for k , v in [ "X" , "Y" ]	
			M%v%_Min := min( Line1.Start[ v ] , Line1.End[ v ] )
			,M%v%_Max := max( Line1.Start[ v ] , Line1.End[ v ] )
			,L%v%_Min := min( Line2.Start[ v ] , Line2.End[ v ] )
			,L%v%_Max := max( Line2.Start[ v ] , Line2.End[ v ] )
		if( !( interceptPoint.X < Mx_Min || interceptPoint.X > Mx_Max || interceptPoint.X < Lx_Min || interceptPoint.X > Lx_Max ) && !( interceptPoint.Y < My_Min || interceptPoint.Y > My_Max || interceptPoint.Y < Ly_Min || interceptPoint.Y > Ly_Max ) )
			return 1
		return 0
	}
	GetLineInterceptPoint( Line1 , Line2 ){ ; Line = { Start: { X: , Y: } , End: { X: , Y: } }
		local A1 := Line1.End.Y - Line1.Start.Y
		,B1 := Line1.Start.X - Line1.End.X
		,C1 := A1 * Line1.Start.X + B1 * Line1.Start.Y
		,A2 := Line2.End.Y - Line2.Start.Y
		,B2 := Line2.Start.X - Line2.End.X
		,C2 := A2 * Line2.Start.X + B2 * Line2.Start.Y
		,Denominator := A1 * B2 - A2 * B1 
		return New Vector( { X: ( ( B2 * C1 - B1 * C2 ) / Denominator )  , Y: ( ( A1 * C2 - A2 * C1 ) / Denominator ) } )
	}
	;********************************************
}
;**************************************************************************************************************************************************************************
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 00000 <<<>>> 00000 
;**************************************************************************************************************************************************************************
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
;####################################################################################################################################################################################
class PopUpWindow	{
;PopUpWindow v2.2
;Date Written: Oct 28th, 2021
;Last Edit: Feb 7th, 2022 :Changed the trigger method.
;Written By: Hellbent aka CivReborn
;SpcThanks: teadrinker , malcev 
	static Index := 0 , Windows := [] , Handles := [] , EditHwnd , HelperHwnd
	__New( obj := "" ){
		This._SetDefaults()
		This.UpdateSettings( obj )
		This._CreateWindow()
		This._CreateWindowGraphics()
		if( This.AutoShow )
			This.ShowWindow( This.Title )
	}
	_SetDefaults(){
		This.X := 10
		This.Y := 10
		This.W := 10
		This.H := 10
		This.Smoothing := 2
		This.Options := " -DPIScale +AlwaysOnTop "
		This.AutoShow := 0
		This.GdipStartUp := 0
		This.Title := ""
		
		This.Controls := []
		This.Handles := []
		This.Index := 0 
	}
	AddTrigger( obj ){
		local k , v , cc , bd
		
		This.Controls[ ++This.Index ] := { 	X:		10
										,	Y:		10
										,	W:		10
										,	H:		10	}
		for k, v in obj
			This.Controls[ This.Index ][ k ] := obj[ k ] 
		cc := This.Controls[ This.Index ]
		Gui, % This.Hwnd ":Add", Text, % "x" cc.X " y" cc.Y " w" cc.W " h" cc.H " hwndhwnd"
		This.Handles[ hwnd ] := This.Index
		This.Controls[ This.Index ].Hwnd := hwnd
		
		if( IsObject( cc.Label ) ){
			bd := cc.Label
			GuiControl, % This.Hwnd ":+G" , % hwnd , % bd
		}else{
			bd := This._TriggerCall.Bind( This )
			GuiControl, % This.Hwnd ":+G" , % hwnd , % bd
		}
		return hwnd
		
	}
	_TriggerCall(){
		MouseGetPos,,,, ctrl, 2
		Try
			;~ SetTimer, % This.Controls[ This.Handles[ ctrl ] ].Label, -0
			gosub, % This.Controls[ This.Handles[ ctrl ] ].Label
		
				
	}
	DrawTriggers( color := "0xFFFF0000" , AutoUpdate := 0 ){
		local brush , cc 
		Brush := Gdip_BrushCreateSolid( color ) 
		Gdip_SetSmoothingMode( This.G , 3 )
		loop, % This.Controls.Length()	{
			cc := This.Controls[ A_Index ]
			Gdip_FillRectangle( This.G , Brush , cc.x , cc.y , cc.w , cc.h )
		
		}
		Gdip_DeleteBrush( Brush )
		Gdip_SetSmoothingMode( This.G , This.Smoothing )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	UpdateSettings( obj := "" , UpdateGraphics := 0 ){
		local k , v
		if( IsObject( obj ) )
			for k, v in obj
				This[ k ] := obj[ k ]
		( This.X = "Center" ) ? ( This.X := ( A_ScreenWidth - This.W ) / 2 ) 	
		( This.Y = "Center" ) ? ( This.Y := ( A_ScreenHeight - This.H ) / 2 ) 	
		if( UpdateGraphics ){
			This._DestroyWindowsGraphics()
			This._CreateWindowGraphics()
		}
	}
	_CreateWindow(){
		local hwnd
		Gui , New, % " +LastFound +E0x80000 hwndhwnd -Caption  " This.Options
		PopUpWindow.Index++
		This.Index := PopUpWindow.Index
		PopUpWindow.Windows[ PopUpWindow.Index ] := This
		This.Hwnd := hwnd
		PopUpWindow.Handles[ hwnd ] := PopUpWindow.Index
		if( This.GdipStartUp && !PopUpWindow.pToken )
			PopUpWindow.pToken := GDIP_STARTUP()
	}
	_DestroyWindowsGraphics(){
		Gdip_DeleteGraphics( This.G )
		SelectObject( This.hdc , This.obm )
		DeleteObject( This.hbm )
		DeleteDC( This.hdc )
	}
	_CreateWindowGraphics(){
		This.hbm := CreateDIBSection( This.W , This.H )
		This.hdc := CreateCompatibleDC()
		This.obm := SelectObject( This.hdc , This.hbm )
		This.G := Gdip_GraphicsFromHDC( This.hdc )
		Gdip_SetSmoothingMode( This.G , This.Smoothing )
	}
	ShowWindow( Title := "" ){
		Gui , % This.Hwnd ":Show", % "x" This.X " y" This.Y " w" This.W " h" This.H " NA", % Title
	}
	HideWindow(){
		Gui , % This.Hwnd ":Hide",
	}
	UpdateWindow( alpha := 255 ){
		UpdateLayeredWindow( This.hwnd , This.hdc , This.X , This.Y , This.W , This.H , alpha )
	}
	ClearWindow( AutoUpdate := 0 , Color := "" ){
		if( color != "" )
			Gdip_GraphicsClear( This.G , color )
		else
			Gdip_GraphicsClear( This.G )
		if( Autoupdate )
			This.UpdateWindow()
	}
	DrawBitmap( pBitmap , obj , dispose := 1 , AutoUpdate := 0 ){
		Gdip_DrawImage( This.G , pBitmap , obj.X , obj.Y , obj.W , obj.H )
		if( dispose )
			Gdip_DisposeImage( pBitmap )
		if( Autoupdate )
			This.UpdateWindow()
	}
	PaintBackground( color := "0xFF000000" , AutoUpdate := 0 ){
		if( isObject( color ) ){
			Brush := Gdip_BrushCreateSolid( ( color.HasKey( "Color" ) ) ? ( color.Color ) : ( "0xFF000000" ) ) 
			if( color.Haskey( "Round" ) )
				Gdip_FillRoundedRectangle( This.G , Brush , color.X , color.Y , color.W , color.H , color.Round )
			else
				Gdip_FillRectangle( This.G , Brush , color.X , color.Y , color.W , color.H ) 
		}else{
			Brush := Gdip_BrushCreateSolid( color ) 
			Gdip_FillRectangle( This.G , Brush , -1 , -1 , This.W + 2 , This.H + 2 ) 
		}
		Gdip_DeleteBrush( Brush )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	DeleteWindow( GDIPShutdown := 0 ){
		Gui, % This.Hwnd ":Destroy"
		SelectObject( This.hdc , This.obm )
		DeleteObject( This.hbm )
		DeleteDC( This.hdc )
		Gdip_DeleteGraphics( This.G )
		hwnd := This.Hwnd
		for k, v in PopUpWindow.Windows[ Hwnd ]
			This[k] := ""
		PopUpWindow.Windows[ Hwnd ] := ""
		if( GDIPShutdown ){
			Gdip_Shutdown( PopUpWindow.pToken )
			PopUpWindow.pToken := ""
		}
	}
	_OnClose( wParam ){
		if( wParam = 0xF060 ){	;SC_CLOSE ;[ clicking on the gui close button ]
			Try{
				Gui, % PopUpWindow.HelperHwnd ":Destroy"
				SoundBeep, 555
			}
		}
	}
	CreateCachedBitmap( pBitmap , Dispose := 0 ){
		local pCachedBitmap
		if( This.CachedBitmap )
			This.DisposeCachedbitmap()
		DllCall( "gdiplus\GdipCreateCachedBitmap" , "Ptr" , pBitmap , "Ptr" , this.G , "PtrP" , pCachedBitmap )
		This.CachedBitmap := pCachedBitmap
		if( Dispose )
			Gdip_DisposeImage( pBitmap )
	}
	DrawCachedBitmap( AutoUpdate := 0 ){
		DllCall( "gdiplus\GdipDrawCachedBitmap" , "Ptr" , this.G , "Ptr" , This.CachedBitmap , "Int" , 0 , "Int" , 0 )
		if( AutoUpdate )
			This.UpdateWindow()
	}
	DisposeCachedbitmap(){
		DllCall( "gdiplus\GdipDeleteCachedBitmap" , "Ptr" , This.CachedBitmap )
	}
	Helper(){
		local hwnd , MethodList := ["__New","UpdateSettings","ShowWindow","HideWindow","UpdateWindow","ClearWindow","DrawBitmap","PaintBackground","DeleteWindow" , "AddTrigger" , "DrawTriggers", "CreateCachedBitmap" , "DrawCachedBitmap" , "DisposeCachedbitmap" ]
		Gui, New, +AlwaysOnTop +ToolWindow +HwndHwnd
		PopUpWindow.HelperHwnd := hwnd
		Gui, Add, Edit, xm ym w250 r1 Center hwndhwnd, Gui1
		PopUpWindow.EditHwnd := hwnd
		loop, % MethodList.Length()	
			Gui, Add, Button, xm y+1 w250 r1 gPopUpWindow._HelperClip, % MethodList[ A_Index ]
		Gui, Show,,
		OnMessage( 0x112 , This._OnClose.Bind( hwnd ) )
	}
	_HelperClip(){
		local ClipList 
		
		GuiControlGet, out, % PopUpWindow.HelperHwnd ":", % PopUpWindow.EditHwnd	
		
		ClipList := 		{ 	__New: 					" := New PopUpWindow( { AutoShow: 1 , X: 0 , Y: 0 , W: A_ScreenWidth , H: A_ScreenHeight , Options: "" -DPIScale +AlwaysOnTop "" } )"
							,	UpdateSettings:			".UpdateSettings( { X: """" , Y: """" , W: """" , H: """" } , UpdateGraphics := 0 )"
							,	ShowWindow:				".ShowWindow( Title := """" )"
							,	HideWindow:				".HideWindow()"
							,	UpdateWindow:			".UpdateWindow()"
							,	ClearWindow:			".ClearWindow( AutoUpdate := 0 )"
							,	DrawBitmap:				".DrawBitmap( pBitmap := """" , { X: 0 , Y: 0 , W: " Out ".W , H: " Out ".H } , dispose := 1 , AutoUpdate := 0 )"
							,	PaintBackground:		".PaintBackground( color := ""0xFF000000"" , AutoUpdate := 0 )  "  ";{ Color: ""0xFF000000"" , X: 2 , Y: 2 , W: " Out ".W - 4 , H: " Out ".H - 4 , Round: 10 }"
							,	DeleteWindow:			".DeleteWindow( GDIPShutdown := 0 )"
							,	AddTrigger:				".AddTrigger( { X: """" , Y: """" , W: """" , H: """" , Value: """" , Label: """" } )"	
							,	DrawTriggers:			".DrawTriggers( color := ""0xFFFF0000"" , AutoUpdate := 0 )"	
							,	CreateCachedBitmap:		".CreateCachedBitmap( pBitmap , Dispose := 0 )"	
							,	DrawCachedBitmap: 		".DrawCachedBitmap( AutoUpdate := 0 )"	
							,	DisposeCachedbitmap:	".DisposeCachedbitmap()"	}
							
		clipboard := Out ClipList[ A_GuiControl ]
		
	}
}


User avatar
andymbody
Posts: 993
Joined: 02 Jul 2017, 23:47

Re: Listview collapsable groups with checkboxes?

08 Sep 2023, 19:39

Hellbent wrote:
08 Sep 2023, 13:49
I started prototyping out the frame and have more or less worked out a few of the details. My next step would be to prototype a panel class and from there work out how to combine it with the frame.
Wow! you are lightning fast! This is great. I will take a closer look over the weekend. I look forward to tinkering and learning. Thanks R!

Andy
User avatar
Hellbent
Posts: 2114
Joined: 23 Sep 2017, 13:34

Re: Listview collapsable groups with checkboxes?

10 Sep 2023, 03:09

I added a panel class to my earlier prototype.

The way it works is that each row ( rows can have children which are also rows visually ) is an instance of a panel and the panels contain a number of features. You create a panel by giving it an array with the different section values and if the current item has children, you give it an array for each child.
Depending on the default options and your updates to them, a bitmap that is sized based on the settings is created and then can be drawn in sequence with the other panels to the frame.
I haven't added it yet, but sooner or later I would need to add in something to track which panel goes in the top position ( scroll ).

.
listview 3.gif
listview 3.gif (965.91 KiB) Viewed 1616 times
.

This requires the "Required Classes" from my previous post. Just paste them at the bottom of this script and change the path to your copy of the gdi+ library. ( use the full path if you don't know what "<" ">" means ).

Code: Select all

;****************************************************************************************************************************************************************************
#Include <My Altered GDIP lib> ;GDIP:  https://www.autohotkey.com/boards/viewtopic.php?f=6&t=6517
;#Include <PopUpWindow_V2> ;  Paste at the bottom of the script 
;#Include <HB Vectors v2>  ; Paste at the bottom of the script 
;****************************************************************************************************************************************************************************
#SingleInstance, Force
SetBatchLines, -1
Gdip_Startup()


Gui1 := {}
Gui1.W := 500
Gui1.H := 420
Gui1.X := A_ScreenWidth - Gui1.W - 40
Gui1.Y := 150
Gui1.Scale := 1
;***********************************
Gui, New, +AlwaysOnTop +hwndhwnd -DPIScale
Gui1.Hwnd := hwnd
;***********************************

;***********************************
cc := Gui1
Gui, Show, % "x" cc.X " y" cc.Y " w" cc.W * cc.Scale " h" cc.H * cc.Scale
;***********************************
x := 10 ;at x *margin
y := 10 ;at y *margin 
w := 400
h := 400
cc := Gui1.Listview1 := New PopUpWindow( { AutoShow: 1 , X: x , Y: y , W: w * Gui1.Scale , H: h * Gui1.Scale , Options: " -DPIScale +Parent" Gui1.Hwnd } ) 

cc.Sections := []

x := 0
w := 120
cc.Sections[ 1 ] := { Header: "Section 1" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 2 ] := { Header: "Section 2" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 3 ] := { Header: "Section 3" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 4 ] := { Header: "Section 4" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 5 ] := { Header: "Section 5" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 

cc.Rows := []
DrawListControl( Gui1 )


obj := {}
obj.Values := [ "Item 1" , "Item 1 Value 1" , "Item 1 Value 2" , "Item 1 Value 3" ]
;~ obj.Children := []
;~ obj.Children[ 1 ] := [ "Item 1" , "Item 1 Value 1" , "Item 1 Value 2" , "Item 1 Value 3" ]
;~ obj.Children[ 2 ] := [ "Header" , "Blah" , "Blah Blah" , "Blah Blah, Blah" ]
;~ obj.Children[ 3 ] := [ "Section 1" , "Section 2" , "Section 3" , "Section 4" ]
Gui1.bob := New ListViewPanels( obj , Gui1.Listview1 )
;***********************************
obj := {}
obj.Values := [ "Main Header" , "1223" , "ABCD" , "Boop" ]
obj.Children := []
obj.Children[ 1 ] := [ "Item 1" , "Item 1 Value 1" , "Item 1 Value 2" , "Item 1 Value 3" ]
obj.Children[ 2 ] := [ "Header" , "Blah" , "Blah Blah" , "Blah Blah, Blah" ]
obj.Children[ 3 ] := [ "Section 1" , "Section 2" , "Section 3" , "Section 4" ]
obj.Children[ 4 ] := [ "Section 1" , "Section 2" , "Section 3" , "Section 4" ]
obj.Children[ 5 ] := [ "The next cell is empty" , "" , "Section 3" , "Section 4" ]
obj.Children[ 6 ] := [ "" , "Section 2" , "Section 3" , "Section 4" ]
obj.Children[ 7 ] := [ "Section 1" , "Section 2" , "Section 3" , "Section 4" ]
obj.FontSize := 22
obj.ChildFontSize := 18
Gui1.sam := New ListViewPanels( obj , Gui1.Listview1 )
;***********************************
obj := {}
obj.Values := [ "Example 3" , "4467" , "ABCD" , "Beep" ]
obj.Children := []
obj.Children[ 1 ] := [ "Item 1" , "Item 1 Value 1" , "Item 1 Value 2" , "Item 1 Value 3" ]
obj.Children[ 2 ] := [ "Header" , "Blah" , "Blah Blah" , "Blah Blah, Blah" ]
obj.FontSize := 12
obj.ChildFontSize := 12
Gui1.joe := New ListViewPanels( obj , Gui1.Listview1 )
;***********************************




;~ Gui1.Listview1.Sections[ 2 ].X := 66

;~ ToolTip, % "Tip:`n" bob.RowHeight "`n" bob.Rows  "`n: " bob.Sections[ 2 ].x

OnMessage( 0x201 , Func( "ClickEvent" ).Bind( Gui1 ) )
DrawListControl( Gui1 )
return
GuiClose:
GuiContextMenu:
*ESC::ExitApp

class ListViewPanels	{
	static Index := 0 , Panels := [] 
	
	
	__New( obj := "" , parent := "" ){
		
		This._SetDefaults()
		This._UpdateDefaults( obj , parent )
		
		
		;~ ToolTip, % "Tip:`n" This.RowHeight "`n" This.Rows  "`n: " This.Sections[ 2 ].x
	}
	_SetDefaults(){
		This.Index := ++ListViewPanels.Index
		This.FontType := "Arial"
		This.FontSize := 12
		This.FontOptions := "vCenter Bold NoWrap"
		This.FontColor := "0xFF000000"
		This.FontColorBottom := "0x00000000"
		This.ChildFontType := "Arial"
		This.ChildFontSize := 12
		This.ChildFontOptions := "vCenter NoWrap"
		This.ChildFontColor := "0xFF000000"
		This.Values := []
		This.Children := []
		This.RowHeight := ""
		This.ToggleState := 1
		This.PaddingX := 2
		This.PaddingY := 2
		This.TabSpacing := 30
		
	}
	_UpdateDefaults( obj := "" , parent := "" ){
		if( isObject( obj ) ){
			for k , v in obj	{
				if( k != "Values" && k != "Children" )
					This[ k ] := obj[ k ]
			}
			if( obj.Haskey( "Values" ) ){
				for k , v in obj.Values	{
					This.Values[ A_Index ] := v
				}
				if( obj.Haskey( "Children" ) ){
					Loop, % Obj.Children.Length(){
						This.Children[ Index := A_Index ] := []
						Loop, % Obj.Children[ Index ].Length()	{
							This.Children[ Index ][ A_Index ] := Obj.Children[ Index ][ A_Index ]
						}
					}
					
				}
			}
			if( parent != "" ){
				This.Sections := parent.Sections
				This.TotalWidth := 2
				Loop, % This.Sections.Length()	{
					This.TotalWidth += This.Sections[ A_Index ].W + 2
				}
			}
		}
		This.RowHeight := This._GetRowHeight( "Height" )
		This.Rows := 1 + This.Children.Length()
		This.Height1 := This.RowHeight + 2 * This.PaddingY
		This.Height2 := ( This.RowHeight * This.Rows ) + ( This.PaddingY * ( This.Rows + 1 ) )
		This.Width1 := This.TotalWidth
		This.Bitmap := This._CreatePanelBitmap()
	}
	_GetRowHeight( InputType := "Width" , Text := "Sample" ){
		local pBitmap := Gdip_CreateBitmap( 10 , 10 )
		local Graphics := Gdip_GraphicsFromImage( pBitmap )
		local Brush := Gdip_BrushCreateSolid( "0xFF000000")
		local OutputArray := ""
		local Output := 0
		local cc := This
		Gdip_SetSmoothingMode( Graphics , 2 )
		outputArray := StrSplit( Gdip_TextToGraphics( Graphics , Text , " s" cc.Fontsize " c" Brush " " cc.FontOptions " x" 0 " y" 0 , cc.FontType , 10000 , 10000  ) , "|" , "|" )
		if( InputType = "Width" ){
			Output := outputArray[ 3 ]
		}else if( InputType = "Height" ){
			Output := outputArray[ 4 ]
		}
		Gdip_DeleteBrush( Brush )
		Gdip_DeleteGraphics( Graphics )
		Gdip_DisposeImage( pBitmap )
		return ceil( Output )
	}
	_CreatePanelBitmap(){
		ScaleFactor := 1
		if( This.ToggleState )
			pBitmap := Gdip_CreateBitmap( This.Width1 , This.Height2 ) 
		else
			pBitmap := Gdip_CreateBitmap( This.Width1 , This.Height1 ) 
		G := Gdip_GraphicsFromImage( pBitmap ) 
		Gdip_SetSmoothingMode( G , 2 )
		
		;draw the background of the panel
		x := -2
		y := -2
		w := This.Width1 + 4
		if( This.ToggleState )
			This.H := h := This.Height2 
		else
			This.H := h := This.Height1
		Brush := Gdip_BrushCreateSolid( "0xFFD6DBE9" ) 
		, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
		, Gdip_DeleteBrush( Brush )
		
		
		Loop, % This.Sections.Length()	{
			
			;~ x := This.Sections[ A_Index ].X 
			x := This.Sections[ A_Index ].X 
			y := 0
			w := This.Sections[ A_Index ].W
			h := This.Height1
			;~ MsgBox, % x
			Brush := Gdip_BrushCreateSolid( "0xFF3399ff" ) 
			, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
			, Gdip_DeleteBrush( Brush )
			
			if( A_Index = 1 ){
				x += This.TabSpacing
				w -= This.TabSpacing
			}
			
			Brush := Gdip_BrushCreateSolid( This.FontColor ) 
			, Gdip_TextToGraphics( G , This.Values[ A_Index ] , "s" This.FontSize * ScaleFactor " " This.FontOptions " c" Brush " x" x * ScaleFactor " y" y * ScaleFactor  , This.FontType , w * ScaleFactor , h * ScaleFactor ) 
			, Gdip_DeleteBrush( Brush )
			
			
		}
		if( This.Children.Length() ){
				
				;~ SoundBeep
				colors := [ "0xFFFF0000" , "0xFFFFFF00" , "0xFFFF8000" , "0xFF80FFFF" , "0xFF00FF80" , "0xFF00FF00" ]
				;~ x += This.TabSpacing
				;~ w -= This.TabSpacing
				
				Loop, % This.Children.Length()	{
					
					y += This.Height1 - This.PaddingY
					;~ Brush := Gdip_BrushCreateSolid( "0xFFFF0000" ) 
					Index := A_Index
					Loop, % This.Sections.Length()	{
						if( A_Index = 1 ){
							x := This.Sections[ A_Index ].X + 2 * This.TabSpacing
							w := This.Sections[ A_Index ].W - 2 * This.TabSpacing
						}else{
							x := This.Sections[ A_Index ].X 
							w := This.Sections[ A_Index ].W
						}
						;~ y := 0
						;~ w := This.Sections[ A_Index ].W
						h := This.Height1
						Brush := Gdip_BrushCreateSolid( colors[ A_Index ] ) 
						, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor ,  w * ScaleFactor  , h * ScaleFactor ) 
						, Gdip_DeleteBrush( Brush )
					
						Brush := Gdip_BrushCreateSolid( This.ChildFontColor ) 
						, Gdip_TextToGraphics( G , This.Children[ Index ][ A_Index ] , "s" This.ChildFontSize * ScaleFactor " " This.ChildFontOptions " c" Brush " x" ( x + 2 ) * ScaleFactor " y" y * ScaleFactor  , This.ChildFontType , w * ScaleFactor , h * ScaleFactor ) 
						, Gdip_DeleteBrush( Brush )
						
					}
				}
		}
		
		Gdip_DeleteGraphics( G )
		return pBitmap
	}
}



ClickEvent( Gui1 ){
	CoordMode, Mouse, Client
	MouseGetPos, x , y
	MouseVector := New Vector( ( x / Gui1.Scale ) - Gui1.Listview1.X , ( y / Gui1.Scale ) - Gui1.Listview1.Y )
	closest := 1000
	cIndex := ""
	Loop, % Gui1.Listview1.Sections.Length()	{
		cc := Gui1.Listview1.Sections[ A_Index ].MoveVector
		if( ( nDist := MouseVector.Dist( cc ) ) <= 10 ){
			if( nDist < closest ){
				closest := nDist
				cIndex := A_Index
			}
		}
	}
	if( cIndex ){
		cc := Gui1.Listview1.Sections[ cIndex ]
		lx := ""
		ly := ""
		While( GetKeyState( "LButton" , "P" ) ){
			MouseGetPos, x , y
			x /= Gui1.Scale
			y /= Gui1.Scale
			x -= Gui1.Listview1.X
			y -= Gui1.Listview1.Y
			if( cIndex = 1 ){
				if( x < 1 && lx != x ){
					vc := lx := cc.MoveVector.X := 1
					cc.W := 1
					ox := Gui1.Listview1.Sections[ cIndex + 1 ].X
					nx := Gui1.Listview1.Sections[ cIndex + 1 ].X := cc.MoveVector.X + 1
					Loop, % Gui1.Listview1.Sections.Length() - 1	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}else if( lx != x ){
					vc := lx := cc.MoveVector.X := x
					cc.W := x
					Loop, % Gui1.Listview1.Sections.Length() - 1	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}
			}else{
				if( x <= Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X && lx != x ){
					vc := lx := cc.MoveVector.X := Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X + 1
					cc.W := lx - Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X
					Loop, % Gui1.Listview1.Sections.Length() - cIndex 	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}else if( lx != x ){
					vc := lx := cc.MoveVector.X := x
					cc.W := x - Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X
					Loop, % Gui1.Listview1.Sections.Length() - cIndex 	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}
				ToolTip, % cIndex
			}
			DrawListControl( Gui1 )
		}
		ToolTip
	}
}

DrawListControl( Gui1 ){
	if( Gui1.Busy )
		return 
	Gui1.Busy := 1
	cc := Gui1.Listview1
	cc.ClearWindow()
	y := 26
	;~ _UpdateDefaults()
	cc.DrawBitmap( Gui1.bob._CreatePanelBitmap() , { X: 0 , Y: y , W: Gui1.bob.Width1 , H: Gui1.bob.H } , dispose := 1 , AutoUpdate := 0 )
	
	y += Gui1.bob.H
	cc.DrawBitmap( Gui1.sam._CreatePanelBitmap() , { X: 0 , Y: y , W: Gui1.sam.Width1 , H: Gui1.sam.H } , dispose := 1 , AutoUpdate := 0 )
	y += Gui1.sam.H
	cc.DrawBitmap( Gui1.joe._CreatePanelBitmap() , { X: 0 , Y: y , W: Gui1.joe.Width1 , H: Gui1.joe.H } , dispose := 1 , AutoUpdate := 0 )
	
	cc.DrawBitmap( HB_BITMAP_MAKER( Gui1 , Gui1.Scale ) , { X: 0 , Y: 0 , W: cc.W , H: cc.H } , dispose := 1 , AutoUpdate := 1 )
	
	sleep, 30
	Gui1.Busy := 0
}

HB_BITMAP_MAKER( Gui1 , ScaleFactor := 1 ){
	;Bitmap Created Using: HB Bitmap Maker
	pBitmap := Gdip_CreateBitmap( Gui1.Listview1.W , Gui1.Listview1.H ) , G := Gdip_GraphicsFromImage( pBitmap ) , Gdip_SetSmoothingMode( G , 2 )
	;background
	;~ SoundBeep
	;~ x := -2 
	;~ y := -2
	;~ w := ( Gui1.Listview1.W / Gui1.Scale ) + 4
	;~ h := ( Gui1.Listview1.H / Gui1.Scale ) + 4
	;~ Brush := Gdip_BrushCreateSolid( "0xFFFFFFFF" ) 
	;~ , Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
	;~ , Gdip_DeleteBrush( Brush )
	;header background
	x := -2
	y := -2
	w := ( Gui1.Listview1.W / Gui1.Scale ) + 4
	h := 26
	Brush := Gdip_BrushCreateSolid( "0xFFD6DBE9" ) 
	, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
	, Gdip_DeleteBrush( Brush )
	;section headers
	Loop, % Gui1.Listview1.Sections.Length()	{
		cc := Gui1.Listview1.Sections[ A_Index ]
		if( cc.W > 5 ){	
			Brush := Gdip_BrushCreateSolid( "0xFF000000" ) 
			, Gdip_TextToGraphics( G , cc.Header , "s" 12 * ScaleFactor " vCenter NoWrap Bold c" Brush " x" ( cc.X + 2 ) * ScaleFactor " y" cc.Y * ScaleFactor  , "Segoe ui" , cc.W * ScaleFactor , cc.H * ScaleFactor ) 
			, Gdip_DeleteBrush( Brush )
		}
		x := cc.MoveVector.X
		y := cc.MoveVector.Y
		w := 3
		h := Gui1.H 
		Gdip_SetSmoothingMode( G , 1 )
		Brush := Gdip_BrushCreateSolid( "0xFF22262a" ) 
		, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
		, Gdip_DeleteBrush( Brush )
		w := 2
		Brush := Gdip_BrushCreateSolid( "0xFFD6DBE9" ) 
		, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
		, Gdip_DeleteBrush( Brush )
		Gdip_SetSmoothingMode( G , 2 )
	}
	Gdip_DeleteGraphics( G )
	return pBitmap
}

I also remembered that I had played around with prototyping out some of the details of a custom listview before.

.
listview 2.gif
listview 2.gif (502.33 KiB) Viewed 1616 times
.

This one is completely created using gui text and progress controls lol.

.
listview 4.gif
listview 4.gif (431.69 KiB) Viewed 1616 times
.
User avatar
andymbody
Posts: 993
Joined: 02 Jul 2017, 23:47

Re: Listview collapsable groups with checkboxes?

10 Sep 2023, 08:58

Ok... I'm blown away... you have taken the concept of Listview to a whole new level! Let me rephrase my previous statement... you have not become a master... but a Grand Master!

You designed this in a single day? It takes me longer just to write the .ahk file name! ;)

I had not understood the panel concept you mentioned until I saw this example... now I get it. And the dynamically scalable LV is very cool and a practical addition. The colors really make everything pop, and I can envision a user-friendly property editor panel for changing the control's internal properties on the fly. I would have to say that this is your signature. Most of your projects have this ability (even if just during design/debug stage). You have made the Listview your own here for sure. And brought life to an otherwise dull tool.

Thank you for sharing the progress of your project.

Andy
User avatar
Hellbent
Posts: 2114
Joined: 23 Sep 2017, 13:34

Re: Listview collapsable groups with checkboxes?

10 Sep 2023, 22:32

andymbody wrote:
10 Sep 2023, 08:58
Ok... I'm blown away... you have taken the concept of Listview to a whole new level! Let me rephrase my previous statement... you have not become a master... but a Grand Master!
Thank you for your kind words.
You designed this in a single day? It takes me longer just to write the .ahk file name! ;)
I see what you did there ;)
I had not understood the panel concept you mentioned until I saw this example... now I get it.
The "panel" is more or less just a concept that is used to create a structure.
In its most basic iteration you end up with a rectangle of space that can be created from a template ( create copies of a template structure )
.
Image
.

With that idea of what a "panel" is you can start adding other controls and features to it.
Take these examples.
These are more or less the same thing as your listview with checkboxes, it just uses a different assortment of sub controls.
.
Image
.
Image
.
For comparison here is your "panel".
.
Image
.


And the dynamically scalable LV is very cool and a practical addition. The colors really make everything pop, and I can envision a user-friendly property editor panel for changing the control's internal properties on the fly. I would have to say that this is your signature. Most of your projects have this ability (even if just during design/debug stage).
lol, I'm actually in the middle of creating something like that.

.
insta 1.gif
insta 1.gif (603.07 KiB) Viewed 1515 times
.



You have made the Listview your own here for sure. And brought life to an otherwise dull tool.

Thank you for sharing the progress of your project.

Andy
I honestly don't know what a listview is supposed to do. My idea of it doesn't go much further than the name "listView", meaning view a list.
User avatar
andymbody
Posts: 993
Joined: 02 Jul 2017, 23:47

Re: Listview collapsable groups with checkboxes?

10 Sep 2023, 23:46

Hellbent wrote:
10 Sep 2023, 22:32
I honestly don't know what a listview is supposed to do. My idea of it doesn't go much further than the name "listView", meaning view a list.
Yes, Listview="view a list", and vehicle="carry people and their stuff somewhere".

But you are well on the way to morphing a Horse&Carriage into a Rolls-Royce. I could see your design ideas becoming a benchmark. What you are able to do with a very high level, non-typed, scripting language is impressive.

Of course AHK does make accessing the lower level calls so much easier than C++ or even direct API. But it can also be very frustrating because it remains quiet when you make a coding mistake that other typed languages would alert you to.

Unfortunately I was unable to spend very much time looking at your code this weekend, but I plan to. Thank you for you efforts and the tutelage!

Andy
User avatar
Hellbent
Posts: 2114
Joined: 23 Sep 2017, 13:34

Re: Listview collapsable groups with checkboxes?

11 Sep 2023, 05:44

I added the graphical side of the checkboxes to the prototype listview control.

.
listvew 1.gif
listvew 1.gif (548.22 KiB) Viewed 1460 times
.

Code: Select all

;****************************************************************************************************************************************************************************
#Include <My Altered GDIP lib> ;GDIP:  https://www.autohotkey.com/boards/viewtopic.php?f=6&t=6517
#Include <PopUpWindow_V2> ;  Paste at the bottom of the script 
#Include <HB Vectors v2>  ; Paste at the bottom of the script 
;****************************************************************************************************************************************************************************
#SingleInstance, Force
SetBatchLines, -1
Gdip_Startup()


Gui1 := {}
Gui1.W := 800
Gui1.H := 420
Gui1.X := A_ScreenWidth - Gui1.W - 40
Gui1.Y := 150
Gui1.Scale := 1
;***********************************
Gui, New, +AlwaysOnTop +hwndhwnd -DPIScale
Gui1.Hwnd := hwnd
;***********************************

;***********************************
cc := Gui1
Gui, Show, % "x" cc.X " y" cc.Y " w" cc.W * cc.Scale " h" cc.H * cc.Scale
;***********************************
x := 10 ;at x *margin
y := 10 ;at y *margin 
w := 700
h := 400
cc := Gui1.Listview1 := New PopUpWindow( { AutoShow: 1 , X: x , Y: y , W: w * Gui1.Scale , H: h * Gui1.Scale , Options: " -DPIScale +Parent" Gui1.Hwnd } ) 

cc.Sections := []

x := 0
w := 200
cc.Sections[ 1 ] := { Header: "Section 1" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 2 ] := { Header: "Section 2" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 3 ] := { Header: "Section 3" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 4 ] := { Header: "Section 4" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 
x += w + 3 
cc.Sections[ 5 ] := { Header: "Section 5" , X: x , Y: 0 , W: w , H: 24  , MoveVector: New Vector( x + w + 1 , 0 ) } 

cc.Rows := []
DrawListControl( Gui1 )


obj := {}
obj.Values := [ "Item 1" , "Item 1 Value 1" , "Item 1 Value 2" , "Item 1 Value 3" ]
obj.Children := []
obj.Children[ 1 ] := [ "Item 1" , "Item 1 Value 1" , "Item 1 Value 2" , "Item 1 Value 3" ]
obj.Children[ 2 ] := [ "Header" , "Blah" , "Blah Blah" , "Blah Blah, Blah" ]
obj.Children[ 3 ] := [ "Section 1" , "Section 2" , "Section 3" , "Section 4" ]
obj.State := 0
Gui1.bob := New ListViewPanels( obj , Gui1.Listview1 )
;***********************************
obj := {}
obj.Values := [ "Main Header" , "1223" , "ABCD" , "Boop" ]
obj.Children := []
obj.Children[ 1 ] := [ "Item 1" , "Item 1 Value 1" , "Item 1 Value 2" , "Item 1 Value 3" ]
obj.Children[ 2 ] := [ "Header" , "Blah" , "Blah Blah" , "Blah Blah, Blah" ]
obj.Children[ 3 ] := [ "Section 1" , "Section 2" , "Section 3" , "Section 4" ]
obj.Children[ 4 ] := [ "Section 1" , "Section 2" , "Section 3" , "Section 4" ]
obj.Children[ 5 ] := [ "The next cell is empty" , "" , "Section 3" , "Section 4" ]
obj.Children[ 6 ] := [ "" , "Section 2" , "Section 3" , "Section 4" ]
obj.Children[ 7 ] := [ "Section 1" , "Section 2" , "Section 3" , "Section 4" ]
obj.ChildStates := [ 1 , 1 , 0 , 1 , "" , 1 , 0 ]
obj.FontSize := 12
obj.ChildFontSize := 12
Gui1.sam := New ListViewPanels( obj , Gui1.Listview1 )
;***********************************
obj := {}
obj.Values := [ "Example 3" , "4467" , "ABCD" , "Beep" ]
obj.Children := []
obj.Children[ 1 ] := [ "Item 1" , "Item 1 Value 1" , "Item 1 Value 2" , "Item 1 Value 3" ]
obj.Children[ 2 ] := [ "Header" , "Blah" , "Blah Blah" , "Blah Blah, Blah" ]
obj.FontSize := 12
obj.ChildFontSize := 12
obj.State := ""
obj.ChildStates := [ 0 , 1 ]
Gui1.joe := New ListViewPanels( obj , Gui1.Listview1 )
;***********************************




;~ Gui1.Listview1.Sections[ 2 ].X := 66

;~ ToolTip, % "Tip:`n" bob.RowHeight "`n" bob.Rows  "`n: " bob.Sections[ 2 ].x

OnMessage( 0x201 , Func( "ClickEvent" ).Bind( Gui1 ) )
DrawListControl( Gui1 )
return
GuiClose:
GuiContextMenu:
*ESC::ExitApp

class ListViewPanels	{
	static Index := 0 , Panels := [] 
	
	
	__New( obj := "" , parent := "" ){
		
		This._SetDefaults()
		This._UpdateDefaults( obj , parent )
		
		
		;~ ToolTip, % "Tip:`n" This.RowHeight "`n" This.Rows  "`n: " This.Sections[ 2 ].x
	}
	_SetDefaults(){
		This.Index := ++ListViewPanels.Index
		This.FontType := "Arial"
		This.FontSize := 12
		This.FontOptions := "vCenter Bold NoWrap"
		This.FontColor := "0xFF000000"
		This.FontColorBottom := "0x00000000"
		This.ChildFontType := "Arial"
		This.ChildFontSize := 12
		This.ChildFontOptions := "vCenter NoWrap"
		This.ChildFontColor := "0xFF000000"
		This.Values := []
		This.Children := []
		This.RowHeight := ""
		This.ToggleState := 1
		This.PaddingX := 2
		This.PaddingY := 2
		This.TabSpacing := 15
		This.State := 1
		
	}
	_UpdateDefaults( obj := "" , parent := "" ){
		if( isObject( obj ) ){
			for k , v in obj	{
				if( k != "Values" && k != "Children" && k != "ChildStates" )
					This[ k ] := obj[ k ]
			}
			if( obj.Haskey( "Values" ) ){
				for k , v in obj.Values	{
					This.Values[ A_Index ] := v
				}
				if( obj.Haskey( "Children" ) ){
					Loop, % Obj.Children.Length(){
						This.Children[ Index := A_Index ] := []
						Loop, % Obj.Children[ Index ].Length()	{
							This.Children[ Index ][ A_Index ] := Obj.Children[ Index ][ A_Index ]
						}
					}
					if( obj.Haskey( "ChildStates" ) ){
						This.ChildStates := []
						This.HasChildStates := 1
						loop, % obj.ChildStates.Length()	{
							if( obj.ChildStates[ A_Index ] = "" )
								This.ChildStates[ A_Index ] := ""
							else
								This.ChildStates[ A_Index ] := obj.ChildStates[ A_Index ]
							
						}
					}
					
				}
			}
			if( parent != "" ){
				This.Parent := parent
				This.Sections := parent.Sections
				This.TotalWidth := 2
				Loop, % This.Sections.Length()	{
					This.TotalWidth += This.Sections[ A_Index ].W + 2
				}
			}
		}
		This.RowHeight := This._GetRowHeight( "Height" )
		This.Rows := 1 + This.Children.Length()
		This.Height1 := This.RowHeight + 2 * This.PaddingY
		This.Height2 := ( This.RowHeight * This.Rows ) + ( This.PaddingY * ( This.Rows + 1 ) )
		;~ This.Width1 := This.TotalWidth
		This.Width1 := This.TotalWidth
		This.Bitmap := This._CreatePanelBitmap()
	}
	_GetRowHeight( InputType := "Width" , Text := "Sample" ){
		local pBitmap := Gdip_CreateBitmap( 10 , 10 )
		local Graphics := Gdip_GraphicsFromImage( pBitmap )
		local Brush := Gdip_BrushCreateSolid( "0xFF000000")
		local OutputArray := ""
		local Output := 0
		local cc := This
		Gdip_SetSmoothingMode( Graphics , 2 )
		outputArray := StrSplit( Gdip_TextToGraphics( Graphics , Text , " s" cc.Fontsize " c" Brush " " cc.FontOptions " x" 0 " y" 0 , cc.FontType , 10000 , 10000  ) , "|" , "|" )
		if( InputType = "Width" ){
			Output := outputArray[ 3 ]
		}else if( InputType = "Height" ){
			Output := outputArray[ 4 ]
		}
		Gdip_DeleteBrush( Brush )
		Gdip_DeleteGraphics( Graphics )
		Gdip_DisposeImage( pBitmap )
		return ceil( Output )
	}
	_CreatePanelBitmap(){
		ScaleFactor := 1
		if( This.ToggleState )
			;~ pBitmap := Gdip_CreateBitmap( This.Width1 , This.Height2 ) 
			pBitmap := Gdip_CreateBitmap( This.Parent.W , This.Height2 ) 
		else
			;~ pBitmap := Gdip_CreateBitmap( This.Width1 , This.Height1 ) 
			pBitmap := Gdip_CreateBitmap( This.Parent.W , This.Height1 ) 
		G := Gdip_GraphicsFromImage( pBitmap ) 
		Gdip_SetSmoothingMode( G , 2 )
		
		;draw the background of the panel
		x := -2
		y := -2
		w := This.Width1 + 4
		if( This.ToggleState )
			This.H := h := This.Height2 
		else
			This.H := h := This.Height1
		;~ Brush := Gdip_BrushCreateSolid( "0xFFD6DBE9" ) 
		Brush := Gdip_BrushCreateSolid( "0xFFFFFFFF" ) 
		, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
		, Gdip_DeleteBrush( Brush )
		
		
		Loop, % This.Sections.Length()	{
			
			;~ x := This.Sections[ A_Index ].X 
			x := This.Sections[ A_Index ].X 
			y := 0
			w := This.Sections[ A_Index ].W
			h := This.Height1
			;~ MsgBox, % x
			Brush := Gdip_BrushCreateSolid( "0xFFE4F8F8" ) 
			, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
			, Gdip_DeleteBrush( Brush )
			
			if( A_Index = 1 ){
				x += This.TabSpacing
				w -= This.TabSpacing
				if( This.Children.Length() >= 1 ){
					Brush := Gdip_BrushCreateSolid( "0xFF000000" ) 
					, Gdip_TextToGraphics( G , ( This.ToggleState ) ? ( "5" ) : ( "6" ) , "s" 16 * ScaleFactor " Center vCenter c" Brush " x" ( x + 2 ) * ScaleFactor " y"( y + 4 ) * ScaleFactor  , "WebDings" , ( This.RowHeight - 2 ) * ScaleFactor , ( This.RowHeight - 2 ) * ScaleFactor ) 
					, Gdip_DeleteBrush( Brush )
					x += This.RowHeight + 2
					w -= This.RowHeight + 2
				}
				if( This.State != "" ){
						Gdip_SetSmoothingMode( G , 1 )
						Brush := Gdip_BrushCreateSolid( "0xFF000000" ) 
						, Gdip_FillRectangle( G , Brush , x * ScaleFactor , ( y + 2 ) * ScaleFactor , This.RowHeight * ScaleFactor , This.RowHeight * ScaleFactor ) 
						, Gdip_DeleteBrush( Brush )
						
						Brush := Gdip_BrushCreateSolid( "0xFFFFFFFF" ) 
						, Gdip_FillRectangle( G , Brush , ( x + 1 ) * ScaleFactor , ( y + 3 ) * ScaleFactor , ( This.RowHeight - 2 ) * ScaleFactor , ( This.RowHeight - 2 ) * ScaleFactor ) 
						, Gdip_DeleteBrush( Brush )
						
						if( This.State ){
							
							Brush := Gdip_BrushCreateSolid( "0xFF000000" ) 
							, Gdip_TextToGraphics( G , "a" , "s" 20 * ScaleFactor " Center vCenter c" Brush " x" ( x + 2 ) * ScaleFactor " y"( y + 4 ) * ScaleFactor  , "WebDings" , ( This.RowHeight - 2 ) * ScaleFactor , ( This.RowHeight - 2 ) * ScaleFactor ) 
							, Gdip_DeleteBrush( Brush )
						}
						
						x += This.RowHeight + 2
						w -= This.RowHeight + 2
					}
				
			}
			
			Gdip_SetSmoothingMode( G , 2 )
			
			
			Brush := Gdip_BrushCreateSolid( This.FontColor ) 
			, Gdip_TextToGraphics( G , This.Values[ A_Index ] , "s" This.FontSize * ScaleFactor " " This.FontOptions " c" Brush " x" x * ScaleFactor " y" y * ScaleFactor  , This.FontType , w * ScaleFactor , h * ScaleFactor ) 
			, Gdip_DeleteBrush( Brush )
			
			
		}
		if( This.Children.Length() ){
				
				;~ SoundBeep
				;~ colors := [ "0xFFFF0000" , "0xFFFFFF00" , "0xFFFF8000" , "0xFF80FFFF" , "0xFF00FF80" , "0xFF00FF00" ]
				;~ colors := [ "0xFFfcfcfc" , "0xFFdbdddf" , "0xFFc0c3c7" , "0xFFa8abb2" , "0xFF90949c" ]
				;~ colors := [ "0xFFfefefe" , "0xFFf7f8f8" , "0xFFf0f0f1" , "0xFFe5e6e8" , "0xFFdadcde" ]
				colors := [ "0xFFFFFFFF" , "0xFFFFFFFF" , "0xFFFFFFFF" , "0xFFFFFFFF" , "0xFFFFFFFF" ]
				;~ x += This.TabSpacing
				;~ w -= This.TabSpacing
				
				Loop, % This.Children.Length()	{
					
					y += This.Height1 - This.PaddingY
					;~ Brush := Gdip_BrushCreateSolid( "0xFFFF0000" ) 
					Index := A_Index
					Loop, % This.Sections.Length()	{
						if( A_Index = 1 ){
							;~ x := This.Sections[ A_Index ].X + 2 * This.TabSpacing
							x := This.Sections[ A_Index ].X + 2 * ( This.RowHeight + 2) + This.TabSpacing
							w := This.Sections[ A_Index ].W - 2 * ( This.RowHeight + 2) - This.TabSpacing
							
							if( This.HasChildStates ){
								;~ SoundBeep
								if( This.ChildStates[ Index ] != "" ){
									Gdip_SetSmoothingMode( G , 1 )
									Brush := Gdip_BrushCreateSolid( "0xFF000000" ) 
									, Gdip_FillRectangle( G , Brush , x * ScaleFactor , ( y + 2 ) * ScaleFactor , This.RowHeight * ScaleFactor , This.RowHeight * ScaleFactor ) 
									, Gdip_DeleteBrush( Brush )
									
									Brush := Gdip_BrushCreateSolid( "0xFFFFFFFF" ) 
									, Gdip_FillRectangle( G , Brush , ( x + 1 ) * ScaleFactor , ( y + 3 ) * ScaleFactor , ( This.RowHeight - 2 ) * ScaleFactor , ( This.RowHeight - 2 ) * ScaleFactor ) 
									, Gdip_DeleteBrush( Brush )
									
									if( This.ChildStates[ Index ] ){
						
										Brush := Gdip_BrushCreateSolid( "0xFF000000" ) 
										, Gdip_TextToGraphics( G , "a" , "s" 16 * ScaleFactor " Center vCenter c" Brush " x" ( x + 2 ) * ScaleFactor " y"( y + 4 ) * ScaleFactor  , "WebDings" , ( This.RowHeight - 2 ) * ScaleFactor , ( This.RowHeight - 2 ) * ScaleFactor ) 
										, Gdip_DeleteBrush( Brush )
									}
									
								}
								x += This.RowHeight + 2
								w -= This.RowHeight - 2
							}
							
						}else{
							x := This.Sections[ A_Index ].X 
							w := This.Sections[ A_Index ].W
						}
						;~ y := 0
						;~ w := This.Sections[ A_Index ].W
						h := This.Height1
						Brush := Gdip_BrushCreateSolid( colors[ A_Index ] ) 
						, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor ,  w * ScaleFactor  , h * ScaleFactor ) 
						, Gdip_DeleteBrush( Brush )
					
						Brush := Gdip_BrushCreateSolid( This.ChildFontColor ) 
						, Gdip_TextToGraphics( G , This.Children[ Index ][ A_Index ] , "s" This.ChildFontSize * ScaleFactor " " This.ChildFontOptions " c" Brush " x" ( x + 2 ) * ScaleFactor " y" y * ScaleFactor  , This.ChildFontType , w * ScaleFactor , h * ScaleFactor ) 
						, Gdip_DeleteBrush( Brush )
						
					}
				}
		}
		
		Gdip_DeleteGraphics( G )
		return pBitmap
	}
}



ClickEvent( Gui1 ){
	CoordMode, Mouse, Client
	MouseGetPos, x , y
	MouseVector := New Vector( ( x / Gui1.Scale ) - Gui1.Listview1.X , ( y / Gui1.Scale ) - Gui1.Listview1.Y )
	closest := 1000
	cIndex := ""
	Loop, % Gui1.Listview1.Sections.Length()	{
		cc := Gui1.Listview1.Sections[ A_Index ].MoveVector
		if( ( nDist := MouseVector.Dist( cc ) ) <= 10 ){
			if( nDist < closest ){
				closest := nDist
				cIndex := A_Index
			}
		}
	}
	if( cIndex ){
		cc := Gui1.Listview1.Sections[ cIndex ]
		lx := ""
		ly := ""
		While( GetKeyState( "LButton" , "P" ) ){
			MouseGetPos, x , y
			x /= Gui1.Scale
			y /= Gui1.Scale
			x -= Gui1.Listview1.X
			y -= Gui1.Listview1.Y
			if( cIndex = 1 ){
				if( x < 1 && lx != x ){
					vc := lx := cc.MoveVector.X := 1
					cc.W := 1
					ox := Gui1.Listview1.Sections[ cIndex + 1 ].X
					nx := Gui1.Listview1.Sections[ cIndex + 1 ].X := cc.MoveVector.X + 1
					Loop, % Gui1.Listview1.Sections.Length() - 1	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}else if( lx != x ){
					vc := lx := cc.MoveVector.X := x
					cc.W := x
					Loop, % Gui1.Listview1.Sections.Length() - 1	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}
			}else{
				if( x <= Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X && lx != x ){
					vc := lx := cc.MoveVector.X := Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X + 1
					cc.W := lx - Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X
					Loop, % Gui1.Listview1.Sections.Length() - cIndex 	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}else if( lx != x ){
					vc := lx := cc.MoveVector.X := x
					cc.W := x - Gui1.Listview1.Sections[ cIndex - 1 ].MoveVector.X
					Loop, % Gui1.Listview1.Sections.Length() - cIndex 	{
						ox := Gui1.Listview1.Sections[ cIndex + A_Index ].X
						nx := Gui1.Listview1.Sections[ cIndex + A_Index ].X := vc + 1
						vc := Gui1.Listview1.Sections[ cIndex + A_Index ].MoveVector.X := nx + Gui1.Listview1.Sections[ cIndex + A_Index ].W
					}
				}
				ToolTip, % cIndex
			}
			DrawListControl( Gui1 )
		}
		ToolTip
	}
}

DrawListControl( Gui1 ){
	if( Gui1.Busy )
		return 
	Gui1.Busy := 1
	cc := Gui1.Listview1
	cc.ClearWindow()
	y := 26
	;~ _UpdateDefaults()
	;~ cc.DrawBitmap( Gui1.bob._CreatePanelBitmap() , { X: 0 , Y: y , W: Gui1.bob.Width1 , H: Gui1.bob.H } , dispose := 1 , AutoUpdate := 0 )
	cc.DrawBitmap( Gui1.bob._CreatePanelBitmap() , { X: 0 , Y: y , W: Gui1.bob.Parent.W , H: Gui1.bob.H } , dispose := 1 , AutoUpdate := 0 )
	
	y += Gui1.bob.H
	cc.DrawBitmap( Gui1.sam._CreatePanelBitmap() , { X: 0 , Y: y , W: Gui1.sam.Parent.W , H: Gui1.sam.H } , dispose := 1 , AutoUpdate := 0 )
	y += Gui1.sam.H
	cc.DrawBitmap( Gui1.joe._CreatePanelBitmap() , { X: 0 , Y: y , W: Gui1.joe.Parent.W , H: Gui1.joe.H } , dispose := 1 , AutoUpdate := 0 )
	
	cc.DrawBitmap( HB_BITMAP_MAKER( Gui1 , Gui1.Scale ) , { X: 0 , Y: 0 , W: cc.W , H: cc.H } , dispose := 1 , AutoUpdate := 1 )
	
	sleep, 30
	Gui1.Busy := 0
}

HB_BITMAP_MAKER( Gui1 , ScaleFactor := 1 ){
	;Bitmap Created Using: HB Bitmap Maker
	pBitmap := Gdip_CreateBitmap( Gui1.Listview1.W , Gui1.Listview1.H ) , G := Gdip_GraphicsFromImage( pBitmap ) , Gdip_SetSmoothingMode( G , 2 )
	;background
	;~ SoundBeep
	;~ x := -2 
	;~ y := -2
	;~ w := ( Gui1.Listview1.W / Gui1.Scale ) + 4
	;~ h := ( Gui1.Listview1.H / Gui1.Scale ) + 4
	;~ Brush := Gdip_BrushCreateSolid( "0xFFFFFFFF" ) 
	;~ , Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
	;~ , Gdip_DeleteBrush( Brush )
	;header background
	x := -2
	y := -2
	w := ( Gui1.Listview1.W / Gui1.Scale ) + 4
	h := 26
	Brush := Gdip_BrushCreateSolid( "0xFFD6DBE9" ) 
	, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
	, Gdip_DeleteBrush( Brush )
	;section headers
	Loop, % Gui1.Listview1.Sections.Length()	{
		cc := Gui1.Listview1.Sections[ A_Index ]
		if( cc.W > 5 ){	
			Brush := Gdip_BrushCreateSolid( "0xFF000000" ) 
			, Gdip_TextToGraphics( G , cc.Header , "s" 12 * ScaleFactor " vCenter NoWrap Bold c" Brush " x" ( cc.X + 2 ) * ScaleFactor " y" cc.Y * ScaleFactor  , "Segoe ui" , cc.W * ScaleFactor , cc.H * ScaleFactor ) 
			, Gdip_DeleteBrush( Brush )
		}
		x := cc.MoveVector.X
		y := cc.MoveVector.Y
		w := 3
		h := Gui1.H 
		Gdip_SetSmoothingMode( G , 1 )
		Brush := Gdip_BrushCreateSolid( "0xFF22262a" ) 
		, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
		, Gdip_DeleteBrush( Brush )
		w := 2
		Brush := Gdip_BrushCreateSolid( "0xFFD6DBE9" ) 
		, Gdip_FillRectangle( G , Brush , x * ScaleFactor , y * ScaleFactor , w * ScaleFactor , h * ScaleFactor ) 
		, Gdip_DeleteBrush( Brush )
		Gdip_SetSmoothingMode( G , 2 )
	}
	Gdip_DeleteGraphics( G )
	return pBitmap
}
User avatar
andymbody
Posts: 993
Joined: 02 Jul 2017, 23:47

Re: Listview collapsable groups with checkboxes?

11 Sep 2023, 21:10

Hey... that looks familiar! :clap:

I'm looking thru your code now. Trying to get a grip of the layout of the graphics (details). Whether the components are drawn in rows from top to bottom, or vertically from left to right. I think logically, I would expect rows of rectangles, but since the column widths can be easily resized, it would suggest vertical rectangles instead.

I personally like to think is terms of classes/objects for nearly everything. Although I make exceptions to this a lot with AHK. But, I feel a project such as building a Listview would be most efficient using OOP approach. You have used some in this project.

When I had though about how to design a listview (as you suggested), my logic started with what I felt was the smallest element and expanded from there. At first I thought about individual cells (a cell class), but then realized that a cell begins as a rectangle (rectangle class) and adds attribute/properties and sub elements that it might enclose, such as text, checkbox, bitmap, etc. And each of those can become a class on it's own. Also the cell has a border (which is essentially a rectangle with properties such as thickness, color, etc. - border class). The idea was to create all the sub-classes of elements, then place objects within objects to build large pieces, and expand out to eventually find that a Listview emerged (the graphics portion anyway).

Based on what I see in your code so far, this is not the approach that you chose. You have used a couple classes, and some "generic" objects for row data etc. I am interested in your opinion regarding the approach mentioned above. Do you feel (based on your experience with other projects) that it would be beneficial, or too costly (screen refresh lag)?

I have used the GDIP library calls and they are great. And I would plan to use them in the design I mentioned. I don't think I would want to make lower level calls than found there. But having the individual classes makes sense to me logically, rather than relying on "hard-coding" a particular implementation (flavor) for each project. These classes also provide customization and scalability opportunities for future projects.

The generic objects that you have used may be for simplicity purposes during design mode, I'm mot sure. The fact that you have created the Vector, PopupWindow, and Listview classes (and others I think I have seen in your other projects) suggests that you support this approach in your own projects.

How do you feel about a Cell class? Do you feel it would provide benefit to the project/coding, or do you feel that any benefit would be offset by it becoming a drain on resources (during refresh)? The cell objects could be sub elements to a "Row" class/object which might improve refresh times with custom "draw row" method, etc. But I'm trying to figure out how that would work with the column panels (which is a great idea)... the "row" and "column" class/objects should not have to depend on each other, and should certainly avoid stepping on each other's toes when refreshing the screen. So there would need to be a refresh controller to manage this relationship.

Does this all sound overly complicated to you? Cause it is certainly becoming complicated to write about here. :lol:

Anyway, in a nutshell... I am asking for your opinion to the idea of the smaller class/object building blocks, which would provide layer-upon-layer to create the larger Listview object/control. Especially the individual Cell class/objects. You have much more experience than me with the graphics and screen-update efficiency for a project like this. I could certainly play and see what the outcome is, but would rather know what your experience has taught you first.

Andy
User avatar
Hellbent
Posts: 2114
Joined: 23 Sep 2017, 13:34

Re: Listview collapsable groups with checkboxes?

12 Sep 2023, 01:50

andymbody wrote:
11 Sep 2023, 21:10
Based on what I see in your code so far, this is not the approach that you chose. You have used a couple classes, and some "generic" objects for row data etc. I am interested in your opinion regarding the approach mentioned above. Do you feel (based on your experience with other projects) that it would be beneficial, or too costly (screen refresh lag)?
My first step is normally to just make functioning code that approximates what I want. From there I get a better picture of how things fit together.
This is more specifically my approach to novel tasks.
If you look at the click function you will notice that a lot of that can be reduced, but it is there because the idea I started with changed along the way and copy and paste are dear friends of mine lol.
I see it and I make the assumption that you will pick up on it too, and if you don't there is no harm.
Once I have a clearer picture I then start over with the new knowledge in hand.
In cases like this listview, my long term goal would be to create a class for it and as you pointed out, there is the ground work with the use of generic objects.

I have created too many things that I had no idea how to create when I started out to count and this approach of just making the first idea function and restart from there is in my preferred method. The idea is, cram, cram, cram in as many ideas I can into a functioning prototype and then reassess for a real first draft.

That of course assumes that I don't already have a plan that I know will work.
The alternative more often than not is bunch of really nice classes that never get used. In my own experience, my best code is never used, but TBH I don't like to stick to something for too long. The call of the new is too great for me.


I have used the GDIP library calls and they are great. And I would plan to use them in the design I mentioned. I don't think I would want to make lower level calls than found there.
You could also use normal text and progress controls to create this particular control, I think that with double buffering the flickering shouldn't be an issue ( not suggesting, just pointing out options ). Another option is to use an activeX control and css and html, I have played around a bit with them but I am missing the entry level stuff that is required to really do anything with it so I can't provide examples, I just know that it would be very similar to using gdi+ ( draw a rectangle here, draw an ellipse there, etc... )



But having the individual classes makes sense to me logically, rather than relying on "hard-coding" a particular implementation (flavor) for each project. These classes also provide customization and scalability opportunities for future projects.
This is what I normally aim to do. But I'm also not afraid to take a hybrid approach if it means getting a quick result.
Generally I do want to create some sort of logical structure and I don't have much issue with nesting my objects in other objects and working with the resulting long object names.

The generic objects that you have used may be for simplicity purposes during design mode, I'm mot sure.
They are. But I almost always take a hybrid approach because I am a product of AHK, or more specifically have thrown a lot of AHK spaghetti against the wall and my coding habits are the results.


The fact that you have created the Vector, PopupWindow, and Listview classes (and others I think I have seen in your other projects) suggests that you support this approach in your own projects.
For sure. I had even thought about what it would take to use a ddl class I have in one of the cells. I didn't understand what the point of a ddl would be so I didn't think on it long lol.

How do you feel about a Cell class? Do you feel it would provide benefit to the project/coding, or do you feel that any benefit would be offset by it becoming a drain on resources (during refresh)?
It is a good idea. I would likely end up with something similar, but I'm guessing that I would directly incorporate the cell in the panel and the panel into the main listview class.

I don't think that you will have to worry about recourses in the worst case in this scenario let alone a slightly optimized one.
I like your column idea and here is how you can go about doing. First you create a bitmap that only contains your text ( or whatever else ),
keeping the bitmap in memory until you are done with it.
Next, figure out which of the bitmaps is the widest and create a new "Column" bitmap with every item drawn to it.
If you change the order such as with "Sort", you delete the old column bitmap and create a new one in your new order.
Lastly, draw the column bitmap on the main bitmap with all the other columns using cropping to set the correct section of the column bitmap.
( The main bitmap in my prototype is tied to the PopUpWindow class [ obj.ClearWindow() / obj.DrawBitmap() ] ).

Your cropping will be based on which row is in the top position and how many rows or what height you want to display.
From making my prototype I can tell you that you are either going to have to use a fixed height ( could actually be dynamic but its not a good choice ) if you want to have dynamic text font sizes / row heights ( variation in height from one cell to the next above or below ) .

Or your other option is that you can get the height for a specific fontsize and then use that height to calculate a height based on n rows. I.e. you can set it to display 10 rows and the actual size is based on font / font size.
In addition to that though, you can also add "Max" and "Mins" for any number of things. For example, lets say that I want to sometimes use icons and I want to make sure that no matter what the height was going to be based on font, it is instead going to use my minimum height for icon instances.

BTW here is the function I use to get the size of text in gdi+. You just need to alter the function a bit to make work the way you want. I have personally altered this function close to a dozen times as it is simple to use and does exactly what I need it to do. That is, getting the width and height of a text string. Take that, add a bit of padding here, a margin or two there...

Code: Select all

_GetRowHeight( InputType := "Width" , Text := "Sample" ){
		local pBitmap := Gdip_CreateBitmap( 10 , 10 )
		local Graphics := Gdip_GraphicsFromImage( pBitmap )
		local Brush := Gdip_BrushCreateSolid( "0xFF000000")
		local OutputArray := ""
		local Output := 0
		local cc := This
		Gdip_SetSmoothingMode( Graphics , 2 )
		outputArray := StrSplit( Gdip_TextToGraphics( Graphics , Text , " s" cc.Fontsize " c" Brush " " cc.FontOptions " x" 0 " y" 0 , cc.FontType , 10000 , 10000  ) , "|" , "|" )
		if( InputType = "Width" ){
			Output := outputArray[ 3 ]
		}else if( InputType = "Height" ){
			Output := outputArray[ 4 ]
		}
		Gdip_DeleteBrush( Brush )
		Gdip_DeleteGraphics( Graphics )
		Gdip_DisposeImage( pBitmap )
		return ceil( Output )
	}
The equivalent for a normal gui control is to create a dummy gui, and control. Then see what it gives you back via guicontrol, , POS before destroying the dummy.

The cell objects could be sub elements to a "Row" class/object which might improve refresh times with custom "draw row" method, etc. But I'm trying to figure out how that would work with the column panels (which is a great idea)... the "row" and "column" class/objects should not have to depend on each other, and should certainly avoid stepping on each other's toes when refreshing the screen. So there would need to be a refresh controller to manage this relationship.
The "Row" is the panel with a case specific name. Basically what you are creating is a rubber stamp that can create slightly dynamic copies.
You use variables to create and run logic that creates your unique instance of the row object.
For example. Want a checkbox? Test if a key called "State" was passed in the construction of the row ( non null value )
Basically you create a rule set for how the panel is created based on different values and logic operations ( If This, Do That ).
In that you are creating your cell objects and the bitmaps for them. Then once you are done creating your rows, you create your column bitmaps and draw them on the bitmap. It is your most outer object ( the listview ) that controls which part of the column gets drawn and handles the user events. It has access to everything including positions and offsets that you may have built into your cells and rows.
For example. You are likely going to want to know where a checkbox is positioned in a cell and where a cell is positioned in a row and where a row is positioned in the greater control. With the index for all these things you can check to see if you're clicking on a checkbox or not.
If you are clicking on a checkbox, swap the state and redraw the whole column ( this scenario is a bit of a toss up of going with really tall bitmaps that only need to be updated for sort and checkbox changes or creating a smaller bitmap much more frequently, personally I'd likely go with the tall bitmap due to frequency that you are likely to press a checkbox (i.e. a small amount) ).
Anyway, in a nutshell... I am asking for your opinion to the idea of the smaller class/object building blocks, which would provide layer-upon-layer to create the larger Listview object/control. Especially the individual Cell class/objects. You have much more experience than me with the graphics and screen-update efficiency for a project like this. I could certainly play and see what the outcome is, but would rather know what your experience has taught you first.

Andy
I like the smaller class idea and recommend it. Just don't get too bogged down with formality on your first draft because you simply don't have the experience to come close to answering "What don't I know".

Also don't overthink it too much.
What you have is a picture on a gui.
The picture is created with a few element types ( border , section headers , columns )

To get the best results I would go with using a layered window for the basis of your control. This does create a branch in dev for before windows 8 and after windows 8 though. One example is that with windows 8+ you can use a layered window as a child of your normal gui (as is the case in my prototype).

If you have any other questions feel free to ask. Also please keep me posted on your dev work on this. It's not to often that I get to see someone else's methods for creating these types of things.
User avatar
andymbody
Posts: 993
Joined: 02 Jul 2017, 23:47

Re: Listview collapsable groups with checkboxes?

12 Sep 2023, 06:48

Thank you for all the details!

After reading, I suppose I do the same thing with throwing spaghetti, then retracing to make things more formal once it is working. Because much of the time I am entering new territory and must see what is possible within the bounds of the language. AHK is surprisingly versatile, especially with the "libraries" of code that are freely shared across the net. I have some code that I should probably formalize and share (a wrapper class that expands Imagesearch, for instance). But (like you?), I get bored or loose interest frequently and move onto other things. This results in bits of code laying around, but never fully implemented in a completed project. I do get bogged down with trying to create classes that have bells and whistles that I might want to use in the future, but then get abandoned before they find a real home. I need to change that behavior. What you shared here points out my error. There is no benefit to the idea of universal classes if they never get finished or used in the end.

I mostly write utilities to complete mundane tasks while I sleep (I can't stand repetitive tasks). I tell people... I will invest a month to write a utility that saves me 10 minutes of repetitiion. :lol: The Imagesearch wrapper forms the backbone of many of my projects. I wake up to finished output that would normally take an estimated year to complete, but only a few hours using AHK.

Anyway, I will post my progress and questions here, but be warned... I am slower than you are at progress. ;)

Thanks for all your help!
Andy
User avatar
andymbody
Posts: 993
Joined: 02 Jul 2017, 23:47

Re: Listview collapsable groups with checkboxes?

13 Sep 2023, 08:31

@Hellbent

I'm moving through your code and breaking it apart, and see that the GDIP calls are basically painting a canvas to simulate the look of controls.

And at some point functionality will need to be added, of course. This functionality can be added manually as you have done with the custom column drag event. But now it makes me wonder... is it possible to create a brand new custom "control" object and register that "control" with WinOS, so that functionality might be added by utilizing windows messaging? Basically allowing subscription to the newly created control events (like the normal GUI controls have). Events such as mouseover, click, doubleclick, focus, etc.

I will have to look into this to see what is possible in that respect. I have not searched online yet... just "talking out loud" at this point...

But in the mean time, if you (or anyone else) know anything about this and would be willing to share the info or point me in the right direction, I would appreciate it.

Update: (Or is it the main window that receives win messages, and then you bind functions to the events to handle them. I think that sounds correct?) Is there a way to attach a handle to custom controls? I will look into all this.


Thanks!
Andy
User avatar
Hellbent
Posts: 2114
Joined: 23 Sep 2017, 13:34

Re: Listview collapsable groups with checkboxes?

14 Sep 2023, 00:44

andymbody wrote:
13 Sep 2023, 08:31

And at some point functionality will need to be added, of course. This functionality can be added manually as you have done with the custom column drag event. But now it makes me wonder... is it possible to create a brand new custom "control" object and register that "control" with WinOS, so that functionality might be added by utilizing windows messaging? Basically allowing subscription to the newly created control events (like the normal GUI controls have). Events such as mouseover, click, doubleclick, focus, etc.
If you are running windows 8 or higher you can use a simple gui text control.

Here is an example of how you go about doing it.

Code: Select all

MyControlObjects := {}
MyControlHandlesArray := []

MyControlObjects.Button1 := { X: 0 , Y: 0 , W: 100 , H: 100 }

for k, v in MyControlObjects	{
	cc := MyControlObjects[ k ]
	Gui, % GuiNameOrHwnd ":Add" , Text, % "x" cc.X " y" cc.Y " w" cc.W " h" cc.H " hwndhwnd "
	cc.Hwnd := hwnd
	cc.Index := A_Index
	MyControlHandlesArray[ hwnd ] := cc
}


With that you can do this.

Code: Select all


MouseGetPos,,,, ctrlHwnd, 2
if( ( Tip := MyControlHandlesArray[ ctrlHwnd ].Index ) )
	ToolTip, % "The Index of this control is: " Tip 

Once you feel that you understand how that works you can add a bit more complexity to it.

Code: Select all


MyWindowObject := {}

MyWindowObject.ControlObjects := {}
MyWindowObject.ControlHandles := []

MyWindowObject.ControlObjects.Button1 := { X: 0 , Y: 0 , W: 100 , H: 100 , Label: "" , Function: "TestFunction" , Tip: "You pressed the test button" }

for k , v in MyWindowObject.ControlObjects	{
	cc := MyWindowObject.ControlObjects[ k ]
	if( cc.Label ){
		Gui, % MyWindowObject.Hwnd ":Add" , Text, % "x" cc.X " y" cc.Y " w" cc.W " h" cc.H " hwndhwnd g" cc.Label
	}else{
		Gui, % MyWindowObject.Hwnd ":Add" , Text, % "x" cc.X " y" cc.Y " w" cc.W " h" cc.H " hwndhwnd "
	}
	cc.Hwnd := hwnd
	cc.Index := A_Index
	MyWindowObject.ControlHandles[ hwnd ] := cc
	if( cc.Function ){
		fn := Func( cc.Function ).Bind( MyWindowObject , hwnd )
		GuiControl, % MyWindowObject.Hwnd ":+g" , % hwnd , % fn
	}
}

;...
;..
;.

OnMessage( 0x200 , Func( "MsgHandler" ).Bind( MyWindowObject ) )
OnMessage( 0x201 , Func( "MsgHandler" ).Bind( MyWindowObject ) )
;etc...
;*************************************

TestFunction( winObj , controlHwnd  ){
	ToolTip, % "Tip: " WinObj.ControlHandles[ controlHwnd  ].Tip
}

MsgHandler( WinObj , param1 , param2 , msg , hwnd ){

	if( msg = 0x200 ){
	
	}
}

I don't think doing it that way is the right option in this case though.
In the case of this control ( listview ), most of the controls should be handled spatially using rectangles and point vectors.

Code: Select all

Loop, % ControlRectangles
	if( PointInRect( rectObj , MouseX , MouseY ) )
		Msgbox, is in the rectangle
;...
;..
;.
;****************************************	

PointInRect( obj , x , y ){
	if( x >= obj.X && x <= obj.X + obj.W && y >= obj.Y && y <= obj.Y + obj.H )
		return 1
	return 0
}

I also put a bit more thought into columns and rows and how to draw things and every time I went down any path I kept coming back to panels being the correct structure but I can't say for sure yet. I would be interested to see it if you end up going in a different direction.

The panels just need to be connected to a common section object that can update the spacing and position of the sections in each of the panels.
I want to say that you might call it feeding forward or some sort of cascade.

Also, if you are writing for win 8+ you can split the control into a few child windows within the control window. The scrollbars, the headerbar, etc. can all be subwindows of the control window (if it's not clear, the control window is the layered window not the main gui for which the layered window is a child of ).
User avatar
andymbody
Posts: 993
Joined: 02 Jul 2017, 23:47

Re: Listview collapsable groups with checkboxes?

14 Sep 2023, 07:10

Thank you for the info!

One thing I did not mention about this project is that each "group" will contain a dynamic number of rows at any given time. This adds a little more complexity to the tracking of rows in relation to the viewport of the Listview control (behind the scenes).

To set the context for this project... The purpose of the main project is to browse a list of duplicate files (based on certain criteria), decide which files to keep, and which to throw away. This will be accomplished by "chipping down" the list a little at a time. I normally use AllDup (which is GREAT, and where the screenshot came from), but it doesn't provide functionality to cross reference dup files with dup folders (or other custom comparisons that I need). It's also very slow to find duplicates when compared to EverythingApp (which is nearly instantaneous).

The plan is to create a list of dups with EverythingApp (since it's lighting fast), and export the list. Then have an interface that will import the list, and provide custom filtering and comparisons, to help me narrow down my choice of keepers.

If I'm going to build such a tool, I figured it might be advantageous if the UI "looks" and "behaves" in a familiar way (AllDup - unique capabilities within the group header). Obviously this "familiar" design is not the most important aspect of this project, and if it bogs down the overall project, I will probably just use the grouping feature found in the native AHK ListView control, and provide an alternate way to accomplish similar behaviors. My initial post was to see just how complex this part of the project would be.

You have provide valuable information in this regard and given me a lot to ponder. I may pursue this custom listView as a side project in the future, but should probably get the main project completed first.

Thank you for all of your help, and as I make progress on the "custom listview" idea over time, I will post it here.

Andy
User avatar
andymbody
Posts: 993
Joined: 02 Jul 2017, 23:47

Re: Listview collapsable groups with checkboxes?

17 Sep 2023, 05:11

FYI...

There seems to be support for extending GUI (and other built-in classes/objects) in V2. This might include controls. I am looking into this now. Being able to extend the built-in Listview control would be a blessing, and prevent the need to build a custom control from scratch.

Andy

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Bing [Bot], bobstoner289, Google [Bot], Ralf_Reddings200244 and 295 guests