Well, but how to do it?MSDN wrote:Owner-Drawn List Boxes
Of course we have to set the LBS_OWNERDRAWVARIABLE (0x0020) and LBS_HASSTRINGS (0x0040) styles, and then?
Just two messages, seems easy.MSDN wrote:
- The owner of an owner-drawn list box can process a WM_MEASUREITEM message to specify the dimensions of list items.
- The owner of an owner-drawn list box must process the WM_DRAWITEM message.
WM_MEASUREITEM:
Actually it isn't as difficult as it seems on the first look. In contrast to list boxes with the LBS_OWNERDRAWFIX (0x0010) style, WM_MEASUREITEM is called once for each item, and the font of the control and the text of the items are already assigned. You can even send a LB_GETITEMRECT message to get width of the item. So the height can be determined 'easily' using the DRAWTEXT() function with the DT_WORDBREAK and DT_CALCRECT flags.
When WM_MEASUREITEM is sent the first time, the control cannot know, whether it has to show a vertical scroll bar. To ensure that all items are calculated using the same width, set the LBS_DISABLENOSCROLL (0x1000) and WS_VSCROLL (0x200000) styles to make the vertical scroll bar permanently visible, if it might/will be shown.
WM_DRAWITEM:
If WM_MEASUREITEM has been processed successfully, it's also 'easy' to draw the text. You can retrieve the assigned text sendingLB_GETTEXTLEN and LB_GETTEXT messages. The DRAWITEMSTRUCT contains the item's rectangle. So DRAWTEXT() with DT_WORDBREAK will do the job.
Some additional work is needed to draw the selected items, but it's feasible. That's it!
The following example does the whole job. It contains four LBODVAR_ functions. The purpose of LBODVAR_MeasureItem() and LBODVAR_DrawItem() should be clear. LBODVAR_Init() activates the message handling of WM_MEASUREITEM and WM_DRAWITEM at load-time. LBODVAR_GetHeightByRows() can by used to determing the control's height appropriate to the passed number of items like the r option of Gui, Add, ....
Code: Select all
#NoEnv
SetBatchLines, -1
WM_MEASUREITEM := 0x002C
; ----------------------------------------------------------------------------------------------------------------------
; ListBox styles
; LBS_OWNERDRAWVARIABLE = 0x0020, LBS_HASSTRINGS = 0x0040, LBS_DISABLENOSCROLL := 0x1000, WS_VSCROLL := 0x200000
LBODStyles := "+0x201060"
; List
Items := "
(Join|
Lorem ipsum dolor sit amet consectetuer.
Rutrum laoreet ligula sem adipiscing adipiscing mattis Cras Curabitur lacus dignissim.
Nibh vitae auctor Quisque orci ut a est eros consequat.
Sed tincidunt augue eget Vestibulum rutrum Nulla tincidunt orci odio nibh.
Et id sit pharetra Praesent sodales odio interdum eleifend velit Maecenas.
Id turpis senectus Curabitur wisi eleifend libero Pellentesque leo sagittis Quisque.
Eu pretium volutpat Vivamus pellentesque tortor sem massa convallis lacus consectetuer.
Volutpat odio vitae odio Suspendisse risus congue eu Curabitur dapibus justo.
Integer venenatis rhoncus nibh dictumst nibh faucibus adipiscing elit dictumst sit.
Justo eros lorem.
At interdum Mauris justo Quisque Vestibulum tincidunt tempus vel nec Morbi.
)"
; ----------------------------------------------------------------------------------------------------------------------
Gui, +LastFound
Gui, Margin, 20, 20
Gui, Add, Text, w300, Common ListBox
Gui, Add, Text, ym w300, Owner-drawn variable ListBox
Gui, Font, % (FontOptions := "s12"), % (FontName := "Arial")
Gui, Add, ListBox, xm y+5 w300 r6 cNavy Choose1, %Items%
H := LBODVAR_GetHeightByRows(6, FontOptions, FontName)
Gui, Add, ListBox, x+20 yp w300 h%H% hwndHLB2 vLBVar cNavy gSubLBVar %LBODStyles% Choose1, %Items%
Gui, Show, , ODLBVAR
WinSet, Redraw
OnMessage(WM_MEASUREITEM, "") ; message handling isn't needed any more after the last control is created
Return
; ----------------------------------------------------------------------------------------------------------------------
GuiClose:
ExitApp
; ----------------------------------------------------------------------------------------------------------------------
SubLBVar:
GuiControlGet, LBVar
ToolTip, %LBVar%
SetTimer, KillTT, -1000
Return
KillTT:
ToolTip
Return
;=======================================================================================================================
; Owner drawing ========================================================================================================
;=======================================================================================================================
;=======================================================================================================================
LBODVAR_GetHeightByRows(Rows, FontOptions := "", FontName := "") {
;=======================================================================================================================
; -------------------------------------------------------------------------------------------------------------------
; Rows - number of visible rows
; FontOptions - font options like Gui, Font, Options
; FontName - font name like Gui, Font, ..., FontName
; -------------------------------------------------------------------------------------------------------------------
Gui, LBODVAR_GetHeight_Gui:Font, %FontOptions%, %FontName%
Gui, LBODVAR_GetHeight_Gui:Add, ListBox, r%Rows% hwndHLB
GuiControlGet, P, LBODVAR_GetHeight_Gui:Pos, %HLB%
Gui, LBODVAR_GetHeight_Gui:Destroy
Return PH
}
;=======================================================================================================================
LBODVAR_Init() { ; processed at load-time
;=======================================================================================================================
; WM_MEASUREITEM = 0x002C, WM_DRAWITEM = 0x002B
Static MI := OnMessage(0x002C, "LBODVAR_MeasureItem") ; sets the message handler for WM_MEASUREITEM
Static DI := OnMessage(0x002B, "LBODVAR_DrawItem") ; sets the message handler for WM_DRAWITEM
}
;=======================================================================================================================
LBODVAR_MeasureItem(WP, LP, M, H) { ; called by system
;=======================================================================================================================
; -------------------------------------------------------------------------------------------------------------------
; Owner-Drawn ListBox -> http://msdn.microsoft.com/en-us/library/hh298352(v=vs.85).aspx
; WM_MEASUREITEM -> http://msdn.microsoft.com/en-us/library/bb775925(v=vs.85).aspx
; MEASUREITEMSTRUCT -> http://msdn.microsoft.com/en-us/library/bb775804(v=vs.85).aspx
; -------------------------------------------------------------------------------------------------------------------
; LP -> MEASUREITEMSTRUCT offsets
Static offType := 0, offItem := 8, offHeight := 16
; ListBox messages
Static LB_GETITEMRECT := 0x0198, LB_GETTEXT := 0x0189, LB_GETTEXTLEN := 0x018A
; Other messages
Static WM_GETFONT := 0x31
; Styles
Static OWNERDRAWVARIABLE := 0x0020
; DrawText format flags
Static DT := 0x0410 ; DT_WORDBREAK = 0x10, DT_CALCRECT = 0x0400
; Control related
Static HOD := 0, HFONT := 0
; -------------------------------------------------------------------------------------------------------------------
HWND := DllCall("User32.dll\GetDlgItem", "Ptr", H, "Int", WP, "UPtr")
If (HWND <> HOD) {
SendMessage, %WM_GETFONT%, 0, 0, , ahk_id %HWND%
HFONT := ErrorLevel
WinGetClass, Class, ahk_id %HWND%
ControlGet, Styles, Style, , , ahk_id %HWND%
If (Class <> "ListBox") || !(Styles & OWNERDRAWVARIABLE)
Return
HOD := HWND
}
Item := NumGet(LP + 0, offItem, "Int")
SendMessage, %LB_GETTEXTLEN%, %Item%, 0, , ahk_id %HOD%
Len := ErrorLevel
VarSetCapacity(ItemText, Len << 2, 0)
SendMessage, %LB_GETTEXT%, %Item%, &ItemText, , ahk_id %HOD%
VarSetCapacity(RECT, 16, 0)
SendMessage, %LB_GETITEMRECT%, %Item%, &RECT, , ahk_id %HOD%
NumPut(NumGet(RECT, 0, "Int") + 2, RECT, 0, "Int") ; 2 pixel text indent
HDC := DllCall("User32.dll\GetDC", "Ptr", HOD, "UPtr")
OFONT := DllCall("Gdi32.dll\SelectObject", "Ptr", HDC, "Ptr", HFONT)
Height := DllCall("User32.dll\DrawText", "Ptr", HDC, "Ptr", &ItemText, "Int", Len, "Ptr", &RECT, "UInt", DT)
DllCall("Gdi32.dll\SelectObject", "Ptr", HDC, "Ptr", OFONT)
DllCall("User32.dll\ReleaseDC", "Ptr", HOD, "Ptr", HDC)
NumPut(Height, LP + 0, offHeight, "Int")
Return True
}
;=======================================================================================================================
LBODVAR_DrawItem(WP, LP, M, H) { ; called by system
;=======================================================================================================================
; -------------------------------------------------------------------------------------------------------------------
; Owner-Drawn ListBox -> http://msdn.microsoft.com/en-us/library/hh298352(v=vs.85).aspx
; WM_DRAWITEM -> http://msdn.microsoft.com/en-us/library/bb775923(v=vs.85).aspx
; DRAWITEMSTRUCT -> http://msdn.microsoft.com/en-us/library/bb775802(v=vs.85).aspx
; -------------------------------------------------------------------------------------------------------------------
; LP / DRAWITEMSTRUCT offsets
Static offItem := 8, offAction := offItem + 4, offState := offAction + 4, offHWND := offState + A_PtrSize
, offDC := offHWND + A_PtrSize, offRECT := offDC + A_PtrSize, offData := offRECT + 16
; Styles
Static OWNERDRAWVARIABLE := 0x0020
; ListBox messages
Static LB_GETTEXT := 0x0189, LB_GETTEXTLEN := 0x018A
; Owner Draw Actions
Static ODA_DRAWENTIRE := 0x0001, ODA_SELECT := 0x0002, ODA_FOCUS := 0x0004
; Owner Draw States
Static ODS_SELECTED := 0x0001, ODS_FOCUS := 0x0010
; Draw text format flags
Static DT_WORDBREAK := 0x10
; Selection
Static SelBg := DllCall("User32.dll\GetSysColor", "Int", 13, "UInt") ; COLOR_HIGHLIGHT
Static SelTx := DllCall("User32.dll\GetSysColor", "Int", 14, "UInt") ; COLOR_HIGHLIGHTTEXT
; Control specific
Static HOD := 0
; -------------------------------------------------------------------------------------------------------------------
Critical ; may help in case of drawing issues
HWND := NumGet(LP + offHWND, 0, "UPtr")
HDC := NumGet(LP + offDC, 0, "UPtr")
If (HWND <> HOD) {
WinGetClass, Class, ahk_id %HWND%
ControlGet, Styles, Style, , , ahk_id %HWND%
If (Class <> "ListBox") || !(Styles & OWNERDRAWVARIABLE)
Return
HOD := HWND
}
CtlBg := DllCall("Gdi32.dll\GetBkColor", "Ptr", HDC, "UInt")
CtlTx := DllCall("Gdi32.dll\GetTextColor", "Ptr", HDC, "UInt")
Item := NumGet(LP + offItem, 0, "Int")
Action := NumGet(LP + offAction, 0, "UInt")
State := NumGet(LP + offState, 0, "UInt")
RECT := LP + offRECT
SendMessage, %LB_GETTEXTLEN%, %Item%, 0, , ahk_id %HOD%
Len := ErrorLevel
VarSetCapacity(ItemText, Len << 2, 0)
SendMessage, %LB_GETTEXT%, %Item%, &ItemText, , ahk_id %HOD%
BgColor := State & ODS_SELECTED ? SelBg : CtlBg
TxColor := State & ODS_SELECTED ? SelTx : CtlTx
Brush := DllCall("Gdi32.dll\CreateSolidBrush", "UInt", BgColor, "UPtr")
DllCall("User32.dll\FillRect", "Ptr", HDC, "Ptr", RECT, "Ptr", Brush)
DllCall("Gdi32.dll\DeleteObject", "Ptr", Brush)
DllCall("Gdi32.dll\SetTextColor", "Ptr", HDC, "UInt", TxColor)
DllCall("Gdi32.dll\SetBkMode", "Ptr", HDC, "Int", 1) ; TRANSPARENT
NumPut(NumGet(RECT + 0, 0, "Int") + 2, RECT + 0, 0, "Int") ; 2 pixel text indent
DllCall("User32.dll\DrawText", "Ptr", HDC, "Ptr", &ItemText, "Int", Len, "Ptr", RECT, "UInt", DT_WORDBREAK)
NumPut(NumGet(RECT + 0, 0, "Int") - 2, RECT + 0, 0, "Int")
DllCall("Gdi32.dll\SetTextColor", "Ptr", HDC, "UInt", CtlTx)
If (State & ODS_FOCUS)
DllCall("User32.dll\DrawFocusRect", "Ptr", HDC, "Ptr", RECT)
Return True
}