Class LV_Colors v2.0 - 2023-05-06

Post your working scripts, libraries and tools.
just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Class LV_Colors v2.0 - 2023-05-06

Post by just me » 19 Aug 2021, 08:41

This is the AHK 2.0 release of my [Class] LV_Colors. I still miss the flexibility of the v1 object design, but I came to terms with it. The new version 2.0 supports all features of the v1.1 script.

Important changes:
  • The values of the optional parameters NoSort and NoSizing default to False.
  • The Critical property has been removed because changes in AHK v2.0.1 solved the 'freezing' problem. The notificationhandler is now set to Critical -1.
  • The script requires AHK v2.0.1+
Change History

Class_LV_Colors.ahk:

Code: Select all

#Requires AutoHotkey v2.0.1
; ======================================================================================================================
; Namespace:      LV_Colors
; Function:       Individual row and cell coloring for AHK ListView controls.
; Tested with:    AHK 2.0.2 (U32/U64)
; Tested on:      Win 10 (x64)
; Changelog:      2023-01-04/2.0.0/just me   Initial release of the AHK v2 version
; ======================================================================================================================
; CLASS LV_Colors
;
; The class provides methods to set individual colors for rows and/or cells, to clear all colors, to prevent/allow
; sorting and rezising of columns dynamically, and to deactivate/activate the notification handler for NM_CUSTOMDRAW
; notifications (see below).
;
; A message handler for NM_CUSTOMDRAW notifications will be activated for the specified ListView whenever a new
; instance is created. If you want to temporarily disable coloring call MyInstance.ShowColors(False). This must
; be done also before you try to destroy the instance. To enable it again, call MyInstance.ShowColors().
;
; To avoid the loss of Gui events and messages the message handler is set 'critical'. To prevent 'freezing' of the
; list-view or the whole GUI this script requires AHK v2.0.1+.
; ======================================================================================================================
Class LV_Colors {
   ; ===================================================================================================================
   ; __New()         Constructor - Create a new LV_Colors instance for the given ListView
   ; Parameters:     HWND        -  ListView's HWND.
   ;                 Optional ------------------------------------------------------------------------------------------
   ;                 StaticMode  -  Static color assignment, i.e. the colors will be assigned permanently to the row
   ;                                contents rather than to the row number.
   ;                                Values:  True/False
   ;                                Default: False
   ;                 NoSort      -  Prevent sorting by click on a header item.
   ;                                Values:  True/False
   ;                                Default: False
   ;                 NoSizing    -  Prevent resizing of columns.
   ;                                Values:  True/False
   ;                                Default: False
   ; ===================================================================================================================
   __New(LV, StaticMode := False, NoSort := False, NoSizing := False) {
      If (LV.Type != "ListView")
         Throw TypeError("LV_Colors requires a ListView control!", -1, LV.Type)
      ; ----------------------------------------------------------------------------------------------------------------
      ; Set LVS_EX_DOUBLEBUFFER (0x010000) style to avoid drawing issues.
      LV.Opt("+LV0x010000")
      ; Get the default colors
      BkClr := SendMessage(0x1025, 0, 0, LV) ; LVM_GETTEXTBKCOLOR
      TxClr := SendMessage(0x1023, 0, 0, LV) ; LVM_GETTEXTCOLOR
      ; Get the header control
      Header := SendMessage(0x101F, 0, 0, LV) ; LVM_GETHEADER
      ; Set other properties
      This.LV := LV
      This.HWND := LV.HWND
      This.Header := Header
      This.BkClr := BkCLr
      This.TxClr := Txclr
      This.IsStatic := !!StaticMode
      This.AltCols := False
      This.AltRows := False
      This.SelColors := False
      This.NoSort(!!NoSort)
      This.NoSizing(!!NoSizing)
      This.ShowColors()
      This.RowCount := LV.GetCount()
      This.ColCount := LV.GetCount("Col")
      This.Rows := Map()
      This.Rows.Capacity := This.RowCount
      This.Cells := Map()
      This.Cells.Capacity := This.RowCount
   }
   ; ===================================================================================================================
   ; __Delete()      Destructor
   ; ===================================================================================================================
   __Delete() {
      This.ShowColors(False)
      If WinExist(This.HWND)
         WinRedraw(This.HWND)
   }
   ; ===================================================================================================================
   ; Clear()         Clears all row and cell colors.
   ; Parameters:     AltRows     -  Reset alternate row coloring (True / False)
   ;                                Default: False
   ;                 AltCols     -  Reset alternate column coloring (True / False)
   ;                                Default: False
   ; Return Value:   Always True.
   ; ===================================================================================================================
   Clear(AltRows := False, AltCols := False) {
      If (AltCols)
         This.AltCols := False
      If (AltRows)
         This.AltRows := False
      This.Rows.Clear()
      This.Rows.Capacity := This.RowCount
      This.Cells.Clear()
      This.Cells.Capacity := This.RowCount
      Return True
   }
   ; ===================================================================================================================
   ; UpdateProps()   Updates the RowCount, ColCount, BkClr, and TxClr properties.
   ; Return Value:   True on success, otherwise false.
   ; ===================================================================================================================
   UpdateProps() {
      If !(This.HWND)
         Return False
      This.BkClr := SendMessage(0x1025, 0, 0, This.LV) ; LVM_GETTEXTBKCOLOR
      This.TxClr := SendMessage(0x1023, 0, 0, This.LV) ; LVM_GETTEXTCOLOR
      This.RowCount := This.LV.GetCount()
      This.Colcount := This.LV.GetCount("Col")
      If WinExist(This.HWND)
         WinRedraw(This.HWND)
      Return True
   }
   ; ===================================================================================================================
   ; AlternateRows() Sets background and/or text color for even row numbers.
   ; Parameters:     BkColor     -  Background color as RGB color integer (e.g. 0xFF0000 = red) or HTML color name.
   ;                                Default: Empty -> default background color
   ;                 TxColor     -  Text color as RGB color integer (e.g. 0xFF0000 = red) or HTML color name.
   ;                                Default: Empty -> default text color
   ; Return Value:   True on success, otherwise false.
   ; ===================================================================================================================
   AlternateRows(BkColor := "", TxColor := "") {
      If !(This.HWND)
         Return False
      This.AltRows := False
      If (BkColor = "") && (TxColor = "")
         Return True
      BkBGR := This.BGR(BkColor)
      TxBGR := This.BGR(TxColor)
      If (BkBGR = "") && (TxBGR = "")
         Return False
      This.ARB := (BkBGR != "") ? BkBGR : This.BkClr
      This.ART := (TxBGR != "") ? TxBGR : This.TxClr
      This.AltRows := True
      Return True
   }
   ; ===================================================================================================================
   ; AlternateCols() Sets background and/or text color for even column numbers.
   ; Parameters:     BkColor     -  Background color as RGB color integer (e.g. 0xFF0000 = red) or HTML color name.
   ;                                Default: Empty -> default background color
   ;                 TxColor     -  Text color as RGB color integer (e.g. 0xFF0000 = red) or HTML color name.
   ;                                Default: Empty -> default text color
   ; Return Value:   True on success, otherwise false.
   ; ===================================================================================================================
   AlternateCols(BkColor := "", TxColor := "") {
      If !(This.HWND)
         Return False
      This.AltCols := False
      If (BkColor = "") && (TxColor = "")
         Return True
      BkBGR := This.BGR(BkColor)
      TxBGR := This.BGR(TxColor)
      If (BkBGR = "") && (TxBGR = "")
         Return False
      This.ACB := (BkBGR != "") ? BkBGR : This.BkClr
      This.ACT := (TxBGR != "") ? TxBGR : This.TxClr
      This.AltCols := True
      Return True
   }
   ; ===================================================================================================================
   ; SelectionColors() Sets background and/or text color for selected rows.
   ; Parameters:     BkColor     -  Background color as RGB color integer (e.g. 0xFF0000 = red) or HTML color name.
   ;                                Default: Empty -> default selected background color
   ;                 TxColor     -  Text color as RGB color integer (e.g. 0xFF0000 = red) or HTML color name.
   ;                                Default: Empty -> default selected text color
   ; Return Value:   True on success, otherwise false.
   ; ===================================================================================================================
   SelectionColors(BkColor := "", TxColor := "") {
      If !(This.HWND)
         Return False
      This.SelColors := False
      If (BkColor = "") && (TxColor = "")
         Return True
      BkBGR := This.BGR(BkColor)
      TxBGR := This.BGR(TxColor)
      If (BkBGR = "") && (TxBGR = "")
         Return False
      This.SELB := BkBGR
      This.SELT := TxBGR
      This.SelColors := True
      Return True
   }
   ; ===================================================================================================================
   ; Row()           Sets background and/or text color for the specified row.
   ; Parameters:     Row         -  Row number
   ;                 Optional ------------------------------------------------------------------------------------------
   ;                 BkColor     -  Background color as RGB color integer (e.g. 0xFF0000 = red) or HTML color name.
   ;                                Default: Empty -> default background color
   ;                 TxColor     -  Text color as RGB color integer (e.g. 0xFF0000 = red) or HTML color name.
   ;                                Default: Empty -> default text color
   ; Return Value:   True on success, otherwise false.
   ; ===================================================================================================================
   Row(Row, BkColor := "", TxColor := "") {
      If !(This.HWND)
         Return False
      If (Row >This.RowCount)
         Return False
      If This.IsStatic
         Row := This.MapIndexToID(Row)
      If This.Rows.Has(Row)
         This.Rows.Delete(Row)
      If (BkColor = "") && (TxColor = "")
         Return True
      BkBGR := This.BGR(BkColor)
      TxBGR := This.BGR(TxColor)
      If (BkBGR = "") && (TxBGR = "")
         Return False
      ; Colors := {B: (BkBGR != "") ? BkBGR : This.BkClr, T: (TxBGR != "") ? TxBGR : This.TxClr}
      This.Rows[Row] := Map("B", (BkBGR != "") ? BkBGR : This.BkClr, "T", (TxBGR != "") ? TxBGR : This.TxClr)
      Return True
   }
   ; ===================================================================================================================
   ; Cell()          Sets background and/or text color for the specified cell.
   ; Parameters:     Row         -  Row number
   ;                 Col         -  Column number
   ;                 Optional ------------------------------------------------------------------------------------------
   ;                 BkColor     -  Background color as RGB color integer (e.g. 0xFF0000 = red) or HTML color name.
   ;                                Default: Empty -> row's background color
   ;                 TxColor     -  Text color as RGB color integer (e.g. 0xFF0000 = red) or HTML color name.
   ;                                Default: Empty -> row's text color
   ; Return Value:   True on success, otherwise false.
   ; ===================================================================================================================
   Cell(Row, Col, BkColor := "", TxColor := "") {
      If !(This.HWND)
         Return False
      If (Row > This.RowCount) || (Col > This.ColCount)
         Return False
      If This.IsStatic
         Row := This.MapIndexToID(Row)
      If This.Cells.Has(Row) && This.Cells[Row].Has(Col)
         This.Cells[Row].Delete(Col)
      If (BkColor = "") && (TxColor = "")
         Return True
      BkBGR := This.BGR(BkColor)
      TxBGR := This.BGR(TxColor)
      If (BkBGR = "") && (TxBGR = "")
         Return False
      If !This.Cells.Has(Row)
         This.Cells[Row] := [], This.Cells[Row].Capacity := This.ColCount
      If (Col > This.Cells[Row].Length)
         This.Cells[Row].Length := Col
      This.Cells[Row][Col] := Map("B", (BkBGR != "") ? BkBGR : This.BkClr, "T", (TxBGR != "") ? TxBGR : This.TxClr)
      Return True
   }
   ; ===================================================================================================================
   ; NoSort()        Prevents/allows sorting by click on a header item for this ListView.
   ; Parameters:     Apply       -  True/False
   ; Return Value:   True on success, otherwise false.
   ; ===================================================================================================================
   NoSort(Apply := True) {
      If !(This.HWND)
         Return False
      This.LV.Opt((Apply ? "+" : "-") . "NoSort")
      Return True
   }
   ; ===================================================================================================================
   ; NoSizing()      Prevents/allows resizing of columns for this ListView.
   ; Parameters:     Apply       -  True/False
   ; Return Value:   True on success, otherwise false.
   ; ===================================================================================================================
   NoSizing(Apply := True) {
      If !(This.Header)
         Return False
      ControlSetStyle((Apply ? "+" : "-") . "0x0800", This.Header) ; HDS_NOSIZING = 0x0800
      Return True
   }
   ; ===================================================================================================================
   ; ShowColors()    Adds/removes a message handler for NM_CUSTOMDRAW notifications of this ListView.
   ; Parameters:     Apply       -  True/False
   ; Return Value:   Always True
   ; ===================================================================================================================
   ShowColors(Apply := True) {
      If (Apply) && !This.HasOwnProp("OnNotifyFunc") {
         This.OnNotifyFunc := ObjBindMethod(This, "NM_CUSTOMDRAW")
         This.LV.OnNotify(-12, This.OnNotifyFunc)
         WinRedraw(This.HWND)
      }
      Else If !(Apply) && This.HasOwnProp("OnNotifyFunc") {
         This.LV.OnNotify(-12, This.OnNotifyFunc, 0)
         This.OnNotifyFunc := ""
         This.DeleteProp("OnNotifyFunc")
         WinRedraw(This.HWND)
      }
      Return True
   }
   ; ===================================================================================================================
   ; Internally used/called Methods
   ; ===================================================================================================================
   NM_CUSTOMDRAW(LV, L) {
      ; Return values: 0x00 (CDRF_DODEFAULT), 0x20 (CDRF_NOTIFYITEMDRAW / CDRF_NOTIFYSUBITEMDRAW)
      Static SizeNMHDR := A_PtrSize * 3                  ; Size of NMHDR structure
      Static SizeNCD := SizeNMHDR + 16 + (A_PtrSize * 5) ; Size of NMCUSTOMDRAW structure
      Static OffItem := SizeNMHDR + 16 + (A_PtrSize * 2) ; Offset of dwItemSpec (NMCUSTOMDRAW)
      Static OffItemState := OffItem + A_PtrSize         ; Offset of uItemState  (NMCUSTOMDRAW)
      Static OffCT :=  SizeNCD                           ; Offset of clrText (NMLVCUSTOMDRAW)
      Static OffCB := OffCT + 4                          ; Offset of clrTextBk (NMLVCUSTOMDRAW)
      Static OffSubItem := OffCB + 4                     ; Offset of iSubItem (NMLVCUSTOMDRAW)
      Critical -1
      If !(This.HWND) || (NumGet(L, "UPtr") != This.HWND)
         Return
      ; ----------------------------------------------------------------------------------------------------------------
      DrawStage := NumGet(L + SizeNMHDR, "UInt"),
      Row := NumGet(L + OffItem, "UPtr") + 1,
      Col := NumGet(L + OffSubItem, "Int") + 1,
      Item := Row - 1
      If This.IsStatic
         Row := This.MapIndexToID(Row)
      ; CDDS_SUBITEMPREPAINT = 0x030001 --------------------------------------------------------------------------------
      If (DrawStage = 0x030001) {
         UseAltCol := (This.AltCols) && !(Col & 1),
         ColColors := (This.Cells.Has(Row) && This.Cells[Row].Has(Col)) ? This.Cells[Row][Col] : Map("B", "", "T", ""),
         ColB := (ColColors["B"] != "") ? ColColors["B"] : UseAltCol ? This.ACB : This.RowB,
         ColT := (ColColors["T"] != "") ? ColColors["T"] : UseAltCol ? This.ACT : This.RowT,
         NumPut("UInt", ColT, L + OffCT), NumPut("UInt", ColB, L + OffCB)
         Return (!This.AltCols && (Col > This.Cells[Row].Length)) ? 0x00 : 0x020
      }
      ; CDDS_ITEMPREPAINT = 0x010001 -----------------------------------------------------------------------------------
      If (DrawStage = 0x010001) {
         ; LVM_GETITEMSTATE = 0x102C, LVIS_SELECTED = 0x0002
         If (This.SelColors) && SendMessage(0x102C, Item, 0x0002, This.HWND) {
            ; Remove the CDIS_SELECTED (0x0001) and CDIS_FOCUS (0x0010) states from uItemState and set the colors.
            NumPut("UInt", NumGet(L + OffItemState, "UInt") & ~0x0011, L + OffItemState)
            If (This.SELB != "")
               NumPut("UInt", This.SELB, L + OffCB)
            If (This.SELT != "")
               NumPut("UInt", This.SELT, L + OffCT)
            Return 0x02 ; CDRF_NEWFONT
         }
         UseAltRow := This.AltRows && (Item & 1),
         RowColors := This.Rows.Has(Row) ? This.Rows[Row] : "",
         This.RowB := RowColors ? RowColors["B"] : UseAltRow ? This.ARB : This.BkClr,
         This.RowT := RowColors ? RowColors["T"] : UseAltRow ? This.ART : This.TxClr
         If (This.AltCols || This.Cells.Has(Row))
            Return 0x20
         NumPut("UInt", This.RowT, L + OffCT), NumPut("UInt", This.RowB, L + OffCB)
         Return 0x00
      }
      ; CDDS_PREPAINT = 0x000001 ---------------------------------------------------------------------------------------
      Return (DrawStage = 0x000001) ? 0x20 : 0x00
   }
   ; -------------------------------------------------------------------------------------------------------------------
   MapIndexToID(Row) { ; provides the unique internal ID of the given row number
      Return SendMessage(0x10B4, Row - 1, 0, This.HWND) ; LVM_MAPINDEXTOID
   }
   ; -------------------------------------------------------------------------------------------------------------------
   BGR(Color, Default := "") { ; converts colors to BGR
      ; HTML Colors (BGR)
      Static HTML := {AQUA: 0xFFFF00, BLACK: 0x000000, BLUE: 0xFF0000, FUCHSIA: 0xFF00FF, GRAY: 0x808080, GREEN: 0x008000
                    , LIME: 0x00FF00, MAROON: 0x000080, NAVY: 0x800000, OLIVE: 0x008080, PURPLE: 0x800080, RED: 0x0000FF
                    , SILVER: 0xC0C0C0, TEAL: 0x808000, WHITE: 0xFFFFFF, YELLOW: 0x00FFFF}
      If IsInteger(Color)
         Return ((Color >> 16) & 0xFF) | (Color & 0x00FF00) | ((Color & 0xFF) << 16)
      Return (HTML.HasOwnProp(Color) ? HTML.%Color% : Default)
   }
}

LV_Colors_sample.ahk:

Code: Select all

#Requires AutoHotkey v2.0.1
AHK :=  "AutoHotkey v" . A_AhkVersion . " (" . (A_PtrSize = 8 ? "64" : "32") . "-bit)"
Main := Gui("", "ListView & Colors - " . AHK)
Main.MarginX := 20
Main.MarginY := 20
Header := ["Column 1", "Column 2", "Column 3", "Column 4", "Column 5", "Column 6", "Column 7", "Column 8"]
MainLV := Main.AddListView("w800 r30 cBlue Grid -ReadOnly", Header)
Loop 256
   MainLV.Add("", "Value " . A_Index, "Value " . A_Index, "Value " . A_Index, "Value " . A_Index, "Value " . A_Index,
              "Value " . A_Index, "Value " . A_Index, "Value " . A_Index)
Loop MainLV.GetCount("Column")
   MainLV.ModifyCol(A_Index, 95)
; Create a new instance of LV_Colors
CLV := LV_Colors(MainLV)
If !IsObject(CLV) {
   MsgBox("Couldn't create a new LV_Colors object!", "ERROR", 16)
   ExitApp
}
; Set the colors for selected rows
CLV.SelectionColors(0xF0F0F0)
Main.AddCheckBox("w120 vColorsOn Checked", "Colors On").OnEvent("Click", ShowColors)
Main.AddRadio("x+0 yp wp vNone", "No Colors").OnEvent("Click", WhichColors)
Main.AddRadio("x+0 yp wp vColors Checked", "Colors").OnEvent("Click", WhichColors)
Main.AddRadio("x+0 yp wp vAltRows", "Alternate Rows").OnEvent("Click", WhichColors)
Main.AddRadio("x+0 yp wp vAltCols", "Alternate Columns").OnEvent("Click", WhichColors)
Main.OnEvent("Close", MainClose)
Main.OnEvent("Escape", MainClose)
Main.Show()
WhichColors({Name: "Colors"})
; ----------------------------------------------------------------------------------------------------------------------
MainClose(*) {
   Main.Destroy()
   ExitApp
}
; ----------------------------------------------------------------------------------------------------------------------
ShowColors(Ctl, *) {
   CLV.ShowColors(Ctl.Value)
   MainLV.Focus()
}
; ----------------------------------------------------------------------------------------------------------------------
WhichColors(Ctl, *) {
   MainLV.Opt("-Redraw")
   CLV.Clear(1, 1)
   Switch Ctl.Name {
      Case "Colors":
         SetColors(CLV)
      Case "AltRows":
         CLV.AlternateRows(0x808080, 0xFFFFFF)
      Case "AltCols":
         CLV.AlternateCols(0x808080, 0xFFFFFF)
   }
   MainLV.Opt("+Redraw")
   MainLV.Focus()
}
; ----------------------------------------------------------------------------------------------------------------------
SetColors(CLV) {
   Loop CLV.LV.GetCount() {
      If (A_Index & 1) {
         If (Mod(A_Index, 3) = 0)
            CLV.Row(A_Index, 0xFF0000, 0xFFFF00)
         CLV.Cell(A_Index, 1, 0x00FF00, 0x000080)
         CLV.Cell(A_Index, 3, 0x00FF00, 0x000080)
         CLV.Cell(A_Index, 5, 0x00FF00, 0x000080)
      }
      Else {
         CLV.Row(A_Index, 0x000080, 0x00FF00)
      }
   }
}
; ----------------------------------------------------------------------------------------------------------------------
#Include Class_LV_Colors.ahk

:arrow: Github

Class LV_Colors - alpha.1
Last edited by just me on 01 Feb 2024, 05:13, edited 7 times in total.

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

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by kczx3 » 19 Aug 2021, 11:24

Can you elaborate on what the issues are that the v2 OnNotify concept is causing in comparison to v1's onMessage? Just so I know what to look out for when testing the class.

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

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by kczx3 » 19 Aug 2021, 11:30

In the sample, change radio button to "Colors" and then uncheck the "Colors On" box and then close the GUI. The following error appears.

Code: Select all

---------------------------
LV_Colors_sample.ahk
---------------------------
Error in #include file "...\Lib\LV_Colors.ahk":
     The control is destroyed.

	Line#
	069: This.Rows := Array()
	070: This.Rows.Capacity := This.RowCount
	071: This.Cells := Array()
	072: This.Cells.Capacity := This.RowCount
	073: }
	077: {
	078: This.ShowColors(False)
--->	079: If WinExist(This.LV)
	080: WinRedraw(This.LV)
	081: }
	086: {
	087: This.Rows := Array()
	088: This.Rows.Capacity := This.RowCount
	089: This.Cells := Array()
	090: This.Cells.Capacity := This.RowCount

Try to continue anyway?
---------------------------
Yes   No   
---------------------------


just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by just me » 20 Aug 2021, 03:19

@kczx3,

the issues are the same as in v1: drawing issues and frozen Guis when sorting or resizing columns or even when scrolling a large number of visible rows and columns very fast. I hoped that GuiCtrl.OnNotify() might decrease the frequency, but imo it doesn't.

I saw the error during testing and found a way to avoid it, but I obviously made some last-minute change.

Code: Select all

If WinExist(This.HWND)
   WinRedraw(This.HWND)
should work.

User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by jNizM » 24 Nov 2021, 06:25

hey just me

edit: whole post, because I cannot reproduce the error with a dummy table

But I got this error with this Table info: Table has 91 Rows and 42 Cols and not all cells has an input

Code: Select all

loop LV.GetCount()
	if (LV.GetText(A_Index, 22))
		CLV.Cell(A_Index, 22, 0xFFCFD4)
Image

Right before the error comes MsgBox A_Index "`n" LV.GetText(A_Index, 22) gives me Index -> 1 and GetText -> the value of the cell
Whole Col 11 for example has no problems at all.

edit: small example. breaks after 2nd time using the button

Code: Select all

Main := Gui()
Main.MarginX := 10
Main.MarginY := 10
Main.SetFont("s10", "Segoe UI")
MainLV := Main.AddListView("xm ym w500 r15 Grid -LV0x10 LV0x10000", ["Col-1", "Col-2", "Col-3"])
Main.AddButton("xm y+5", "Color").OnEvent("Click", ColorTable)
Main.Show()


ColorTable(*)
{
	MainLV.Opt("-Redraw")
	MainLV.Delete()
	MainLV.Add("", 1 * 10, "", 1 * 30)
	loop 12
		MainLV.Add("", (A_Index + 1) * 10, A_Index * 20, (A_Index + 1) * 30)
	MainLV.Add("", 14 * 10, 14 * 20,"")
	LV_ShowTable(MainLV)
	MainLV.Opt("+Redraw")
}


LV_ShowTable(LV)
{
	CLV := LV_Colors(LV, 1, 0, 0)
	CLV.Critical := 100
	CLV.Clear()

	loop LV.GetCount()
	{
		if (LV.GetText(A_Index, 1)) && (LV.GetText(A_Index, 1) = 120)
			CLV.Cell(A_Index, 1, 0xFFCFD4)
		if (LV.GetText(A_Index, 2)) && (LV.GetText(A_Index, 2) = 120)
			CLV.Cell(A_Index, 2, 0xFFCFD4)
		if (LV.GetText(A_Index, 3)) && (LV.GetText(A_Index, 3) = 120)
			CLV.Cell(A_Index, 3, 0xFFCFD4)
	}
}


#Include ..\_lib\LV_Colors.ahk
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by just me » 25 Nov 2021, 04:27

Hi @jNizM,

thanks for reporting the issue. It's caused by using the 'static' mode of LV_Colors().

Code: Select all

Row := This.MapIndexToID(Row)
can return values of zero or - after you delete and refill the ListView - greater than the number of rows.

I think I'll replace the arrays with maps.

just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by just me » 26 Nov 2021, 10:20

@jNizM, the bug might be fixed now.

User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by jNizM » 29 Nov 2021, 02:42

In this test script above it does not shows colors at all on the 2nd run. No errors, but also no colors. First run works.
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by just me » 29 Nov 2021, 06:19

That's caused by the use of a local CLV object in LV_ShowTable().

User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by jNizM » 29 Nov 2021, 07:46

I'm at a loss. I have to call CLV := LV_Colors() after the table is created (with entries).

Code: Select all

Main := Gui()
Main.MarginX := 10
Main.MarginY := 10
Main.SetFont("s10", "Segoe UI")
MainLV := Main.AddListView("xm ym w500 r15 Grid -LV0x10 LV0x10000", ["Col-1", "Col-2", "Col-3"])
Main.AddButton("xm y+5", "Color").OnEvent("Click", ColorTable)
Main.Show()


ColorTable(*)
{
	MainLV.Opt("-Redraw")

	; delete old table
	MainLV.Delete()

	; create table with random entries
	loop Random(4, 14)
		MainLV.Add("", A_Index - 1, A_Index - 1, A_Index - 1)

	; init class
	CLV := LV_Colors(MainLV, 1, 0, 0)
	CLV.Critical := 100
	CLV.Clear()

	; loop throw table and...
	loop MainLV.GetCount()
	{
		; color 1 in first col
		if (MainLV.GetText(A_Index, 1) = 1)
			CLV.Cell(A_Index, 1, 0xFFCFD4)
		; color 2 in 2nd col
		if (MainLV.GetText(A_Index, 2) = 2)
			CLV.Cell(A_Index, 2, 0xFFCFD4)
		; color 3 in 3rd col
		if (MainLV.GetText(A_Index, 3) = 3)
			CLV.Cell(A_Index, 3, 0xFFCFD4)
	}

	MainLV.Opt("+Redraw")
}
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by just me » 29 Nov 2021, 08:13

Moin, my suggestion:

Code: Select all

Main := Gui()
Main.MarginX := 10
Main.MarginY := 10
Main.SetFont("s10", "Segoe UI")
MainLV := Main.AddListView("xm ym w500 r15 Grid -LV0x10 LV0x10000", ["Col-1", "Col-2", "Col-3"])

CLV := LV_Colors(MainLV, 1, 0, 0)
CLV.Critical := 100
CLV.ShowColors(False)

Main.AddButton("xm y+5", "Color").OnEvent("Click", ColorTable)
Main.Show()



ColorTable(*)
{
	MainLV.Opt("-Redraw")
	CLV.ShowColors(False)
	; create random table
	MainLV.Delete()

	Loop Random(4, 14)
		MainLV.Add("", A_Index - 1, A_Index - 1, A_Index - 1)

	; color 1 in first col, 2 in 2nd col, 3 in 3rd col in a table with random rows
	CLV.UpDateProps()
	CLV.Clear()

	Loop MainLV.GetCount()
	{
		if (MainLV.GetText(A_Index, 1) = 1)
			CLV.Cell(A_Index, 1, 0xFFCFD4)
		if (MainLV.GetText(A_Index, 2) = 2)
			CLV.Cell(A_Index, 2, 0xFFCFD4)
		if (MainLV.GetText(A_Index, 3) = 3)
			CLV.Cell(A_Index, 3, 0xFFCFD4)
	}

	CLV.ShowColors()

	MainLV.Opt("+Redraw")
}

#Include Class_LV_Colors.ahk

User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by jNizM » 29 Nov 2021, 08:44

That's how it works, thank you.

Too bad that larger tables still have problems with coloring, scrolling and resizing. But it was like that under v1 as well.
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

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

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by kczx3 » 29 Nov 2021, 09:14

I still don't understand what AHK is doing internally that causes this. I've had a test script with AutoIt that does all kinds of custom drawing with a listview and images and colored/wrapped drawn text and never have any painting or freezing issues.

just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by just me » 29 Nov 2021, 09:25

@kczx3, would you please show the AutoIt code?

User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by jNizM » 29 Nov 2021, 09:32

[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

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

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by kczx3 » 30 Nov 2021, 08:09

I don't have the post that I found it on but here is a zip with a bunch of AutoIt samples for ListViews and custom draw. In particular, check out "E) Large images in first column.au3" which features images in the first column, tabular text in the second column, and similar in the third column with one piece of text being wrappable.
Attachments
ListViewStuff.zip
(767.97 KiB) Downloaded 147 times

just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by just me » 01 Dec 2021, 11:50

@jNizM & @kczx3, thanks for the links/code. After looking into the AU3 code I think that many of the custom drawing in the AU3 scripts is even 'more expensive' than my script. E.g. in case of custom colors _GUIListViewEx_WM_NOTIFY_Handler is allways called for each subitem. If it doesn't cause the same issues as AHK, the issue must be caused by the internal AHK message handling or AU3 must be significantly faster than AHK. I don't see anything that I could use to improve my code.

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

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by kczx3 » 01 Dec 2021, 19:55

Right, that’s my conclusion as well. However AHK handles the messages seems to perform poorer than AU3 in these GUI message scenarios.

User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by jNizM » 02 Dec 2021, 02:31

So maybe lets invite @lexikos into this. Hopefully he can explain what is going wrong or different here.
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: 2.0-beta.1: Class LV_Colors - alpha.1

Post by lexikos » 02 Dec 2021, 04:40

The main cause of deadlock in relation to ListView is Critical. I wrote in detail about it in Critical thread gets interuped and enters in a deadlock, and summarized as below.
So in short, the problem is a design flaw of AutoHotkey in combination with the ListView, and only applies to Critical threads.
...
The problem arises because the program is required to process some messages but leave others in the queue, while the ListView (or other control or message loop) probably requires some of those messages which the program is designed to keep queued. I see it as somewhat contradictory using Critical and then also expecting the GUI to remain interactive/responsive.

Most single-threaded programs or languages do not have these issues because they generally do not "multiplex" the message loop into the running thread (i.e. checking for messages in between execution of lines of code). Messages would be checked only at specific times controlled by whatever code is running, like when Application.DoEvents() is called in a .NET program.
To put it another way, the design flaw is that the window message loop is used as the only mechanism for queuing events that should start new threads, and problems occur when AutoHotkey does not have full control over the window message loop.

AutoIt defaults to "MessageLoop" mode, in which the script itself must poll for GUI messages in a tight loop, and failing to do this makes the GUI unresponsive. In other words, the program does no checking for messages on its own, least of all while a message handling function is already running. The documentation doesn't say when such checks might occur in OnEvent mode, but I suspect it is only during Sleep() and similar functions.

According to @just me, custom drawing will not work without Critical. I assume this is because a check for messages during handling of the custom draw message triggers some kind of problem. So why allow a check for messages after 100ms? That's what effect the setting of 100 has, as used in LV_Colors_sample.ahk.

I could not produce any problem with LV_Colors_sample.ahk, even with CLV.Critical := 5 or "Off". I timed the bulk of the NM_CUSTOMDRAW method and found that each call is taking around 0.020ms, which is not long enough to trigger a check for messages.


I intend to implement a dedicated queue for thread-starting events as part of restructuring AutoHotkey to be usable as a library, but that is not going to happen soon.

Post Reply

Return to “Scripts and Functions (v2)”