I revised the class code trying to make it faster and maybe fixed a bug in SetRedraw(). Would you please test whether the non-client drawing issues still happen?
Code: Select all
; ==================================================================================================================================
; Namespace: TransparentListBox
; Function: Helper object for transparent ListBoxes
; Tested with: AHK 1.1.19.01 (A32/U32/U64)
; Tested on: Win 8.1 (x64)
; Version: 0.1.00.00/2013-10-03/just me - Initial release.
; 0.1.01.00/2015-01-17/just me - Added scrolling by keys without a vertical scroll bar.
; ==================================================================================================================================
; CLASS TransparentListBox
;
; The class provides transparent background for ListBox controls. The whole stuff is done by subclassing the control.
;
; On creation via 'TLB := New TranparentListBox' you have to pass the HWNDs of the ListBox and the underlying control
; or window. Additionally you may pass a text color and a seperate color to draw selected items with. Colors have to
; be passed as RGB integer values.
;
; After you've changed the content or size of the ListBox, or if you want to change the text or selection color, you
; have to call 'TLB.Update()' once, passing the new color values if desired. Thw whole action should be enclosed by
; two calls of 'TLB.SetRedraw(False/True)' to avoid flickering as far as possible.
;
; Due to the complex way ListBoxes do their scrolling not all options of a common ListBox are supported.
; You have to keep in mind the following restrictions:
; - The new object instance has to be created before the Gui is shown.
; - The ListBox must not have a horizontal scroll bar (because I haven't a clue how to do the scrolling).
; - The Multi(select) option is not supported as yet (but it may be feasible).
; - Scrolling is not 'smooth'.
; ==================================================================================================================================
; Lib compatible function
; ==================================================================================================================================
TransparentListBox_Create(HLB, HBG, TxtColor := "", SelColor := "", SelBkGnd := "", SelBkTrans := 255) {
Return New TransparentListBox(HLB, HBG, TxtColor, SelColor, SelBkGnd, SelBkTrans)
}
; ==================================================================================================================================
; Class
; ==================================================================================================================================
Class TransparentListBox {
; ===============================================================================================================================
; Constructor
; ===============================================================================================================================
__New(HLB, HBG, TxtColor := "", SelColor := "", SelBkGnd := "", SelBkTrans := 255) {
; ----------------------------------------------------------------------------------------------------------------
; HLB : HWND of the ListBox.
; HBG : HWND of the control or Gui containing the background.
; TxtColor : Optional - Text color (RGB integer value, e.g. 0xRRGGBB).
; SelColor : Optional - Selected text color (RGB integer value, e.g. 0xRRGGBB).
; SelBkGnd : Optional - Color of the selection rectangel (RGB integer value, e.g. 0xRRGGBB).
; SelBkTrans : Optional - Transparency of the selection rectangle (0 - 255).
; ----------------------------------------------------------------------------------------------------------------
This.HLB := HLB
This.HBG := HBG
This.Parent := DllCall("GetParent", "Ptr", HLB, "UPtr")
This.CtrlID := DllCall("GetDlgCtrlID", "Ptr", HLB, "UPtr")
HDC := DllCall("GetDC", "Ptr", HLB, "UPtr")
If (TxtColor <> "")
This.TxtColor := ((TxtColor >> 16) & 0xFF) | (TxtColor & 0x00FF00) | ((TxtColor & 0xFF) << 16)
Else
This.TxtColor := DllCall("GetTextColor", "Ptr", HDC, "UInt")
If (SelColor <> "")
This.SelColor := ((SelColor >> 16) & 0xFF) | (SelColor & 0x00FF00) | ((SelColor & 0xFF) << 16)
If (SelBkGnd <> "") {
This.SelBkGnd := ((SelBkGnd >> 16) & 0xFF) | (SelBkGnd & 0x00FF00) | ((SelBkGnd & 0xFF) << 16)
This.SelBkTrans := (SelBkTrans & 0xFF)
}
DllCall("ReleaseDC", "Ptr", HLB, "Ptr", HDC)
VarSetCapacity(RECT, 16, 0)
DllCall("SendMessage", "Ptr", HLB, "UInt", 0x0198, "Ptr", 0, "Ptr", &RECT) ; LB_GETITEMRECT
This.ItemWidth := NumGet(RECT, 8, "Int") - NumGet(RECT, 0, "Int")
This.ItemHeight := NumGet(RECT, 12, "Int") - NumGet(RECT, 4, "Int")
This.ItemCount := DllCall("SendMessage", "Ptr", HLB, "UInt", 0x018B, "Ptr", 0, "Ptr", 0, "Int") ; LB_GETCOUNT
This.Font := DllCall("SendMessage", "Ptr", HLB, "UInt", 0x0031, "Ptr", 0, "Ptr", 0, "UPtr") ; WM_GETFONT
ControlGet, Content, List, , , ahk_id %HLB%
This.Items := StrSplit(Content, "`n")
This.HasBackground := False
This.TopIndex := This.CurSel := -1
This.Drawing := True
ControlGet, Styles, Style, , , ahk_id %HLB%
If (Styles & 0x00200000) { ; WS_VSCROLL -> the lisbox has a vertical scroll bar
VarSetCapacity(SI, 28, 0) ; SCROLLINFO
NumPut(28, SI, 0, "UInt") ; cbSize
NumPut(3, SI, 4, "UInt") ; fMask = SIF_RANGE | SIF_PAGE
DllCall("GetScrollInfo", "Ptr", HLB, "Int", 1, "Ptr", &SI)
This.SIMin := NumGet(SI, 8, "Int") ; nMin
This.SIMax := NumGet(SI, 12, "Int") ; nMax
This.SIPage := NumGet(SI, 16, "UInt") ; nPage
}
Else {
DllCall("GetClientRect", "Ptr", HLB, "Ptr", &RECT)
This.SIMin := 0
This.SIPage := NumGet(RECT, 12, "Int") // This.ItemHeight
This.SIMax := This.ItemCount - 1
}
SCCB := RegisterCallback("TransparentListBox.SubClassCallback")
DllCall("SetWindowSubclass", "Ptr", HLB, "Ptr", SCCB, "Ptr", HLB, "Ptr", &This)
This.SCCB := SCCB
}
; ===============================================================================================================================
; Destructor
; ===============================================================================================================================
__Delete() {
If (This.BMPDC) {
BGDC := DllCall("GetDC", "Ptr", This.HBG, "UPtr")
DllCall("BitBlt", "Ptr", BGDC, "Int", This.Left, "Int", This.Top, "Int", This.Width, "Int", This.Height, "Ptr", This.BMPDC, "Int", 0, "Int", 0, "UInt", 0x00CC0020)
DllCall("ReleaseDC", "Ptr", This.HBG, "Ptr", BGDC)
DllCall("DeleteDC", "Ptr", This.BMPDC)
DllCall("DeleteObject", "Ptr", This.HBMP)
This.BMPDC := This.HBMP := 0
}
If (This.SELDC) {
DllCall("DeleteDC", "Ptr", This.SELDC)
DllCall("DeleteObject", "Ptr", This.HSEL)
}
If (This.HLB) {
DllCall("RemoveWindowSubclass", "Ptr", This.HLB, "Ptr", This.SCCB, "Ptr", This.HLB)
}
}
; ===============================================================================================================================
; Update the instance variables
; ===============================================================================================================================
UpDate(TxtColor := "", SelColor := "", SelBkGnd := "", SelBkTrans := "") {
; ----------------------------------------------------------------------------------------------------------------
; Optional TxtColor : New text color (RGB integer value, e.g. 0xRRGGBB).
; Optional SelColor : New selected text color (RGB integer value, e.g. 0xRRGGBB).
; Changes of the content or the size of the ListBox will be processed automatically.
; ----------------------------------------------------------------------------------------------------------------
Drawing := This.Drawing
If (Drawing)
This.SetRedraw(False)
This.__Delete()
If (TxtColor = "")
If (This.TxtColor)
TxtColor := ((This.TxtColor >> 16) & 0xFF) | (This.TxtColor & 0x00FF00) | ((This.TxtColor & 0xFF) << 16)
If (SelColor = "")
If (This.SelColor)
SelColor := ((This.SelColor >> 16) & 0xFF) | (This.SelColor & 0x00FF00) | ((This.SelColor & 0xFF) << 16)
If (SelBkGnd = "")
If (This.SelBkGnd)
SelBkGnd := ((This.SelBkGnd >> 16) & 0xFF) | (This.SelBkGnd & 0x00FF00) | ((This.SelBkGnd & 0xFF) << 16)
If (SelBkTrans = "")
If (This.SelBkTrans)
SelBkTrans := This.SelBkTrans
This.__New(This.HLB, This.HBG, TxtColor, SelColor, SelBkGnd, SelBkTrans)
If (Drawing)
This.SetRedraw(True)
Return True
}
; ===============================================================================================================================
; Set Redrawing True/False
; ===============================================================================================================================
SetRedraw(Mode) {
; It's highly recommended to call this function instead of using GuiControl, -/+Redraw,
; because the drawing state will be stored internally for use by other methods.
Mode := !!Mode
This.Drawing := Mode
DllCall("SendMessage", "Ptr", This.HLB, "UInt", 0x000B, "Ptr", Mode, "Ptr", 0) ; WM_SETREDRAW
If (Mode)
WinSet, Redraw, , % "ahk_id " . This.HLB
Return True
}
; ===============================================================================================================================
; Methods for internal use
; ===============================================================================================================================
; Subclass callback function - for internal use only ----------------------------------------------------------------------------
SubClassCallback(uMsg, wParam, lParam, IdSubclass, RefData) {
Critical 100
hWnd := This ; first parameter 'hWnd' is passed as 'This'
Return Object(RefData).SubClassProc(hWnd, uMsg, wParam, lParam)
}
; Subclassproc - for internal use only ------------------------------------------------------------------------------------------
SubClassProc(hWnd, uMsg, wParam, lParam) {
Static LB := {GETCOUNT: 0x018B, GETCURSEL: 0x0188, GETITEMRECT: 0x0198, GETTEXT: 0x0189,GETTEXTLEN: 0x018A
, GETTOPINDEX: 0x018E, ITEMFROMPOINT: 0x01A9, SETCURSEL: 0x0186}
Static LBN := {SELCHANGE: 1, DBLCLK: 2}
Static WM := {DESTROY: 0x0002, DRAWITEM: 0x002B, LBUTTONDBLCLK: 0x0203, ERASEBKGND: 0x0014, HSCROLL: 0x0114
, KEYDOWN: 0x0100, KILLFOCUS: 0x0008, LBUTTONDOWN: 0x0201, LBUTTONUP: 0x0202, MOUSEWHEEL: 0x020A
, PAINT: 0x000F, SETFOCUS: 0x0007, SETREDRAW: 0x000B, VSCROLL: 0x0115}
Static VK := {DOWN: 0x28, END: 0x23, HOME: 0x24, NEXT: 0x22, PRIOR: 0x21, UP: 0x26
, 0x21: 1, 0x22: 1, 0x23: 1, 0x24: 1, 0x26: 1, 0x28: 1}
Static DrawingMsg := ""
; ----------------------------------------------------------------------------------------------------------------------------
; Painting
; ----------------------------------------------------------------------------------------------------------------------------
; WM_PAINT message -----------------------------------------------------------------------------------------------------------
If (uMsg = 0x000F) {
VarSetCapacity(PAINTSTRUCT, A_PtrSize + (4 * 7) + 32 + (A_PtrSize - 4), 0)
, LBDC := DllCall("BeginPaint", "Ptr", This.HLB, "Ptr", &PAINTSTRUCT, "UPtr")
, This.Cursel := CurSel := DllCall("SendMessage", "Ptr", hWnd, "UInt", 0x0188, "Ptr", 0, "Ptr", 0, "Int") ; LB_GETCURSEL
, This.TopIndex := TopIndex := DllCall("SendMessage", "Ptr", hWnd, "UInt", 0x018E, "Ptr", 0, "Ptr", 0, "Int") ; LB_GETTOPINDEX
, DllCall("BitBlt", "Ptr", LBDC, "Int", 0, "Int", 0, "Int", This.Width, "Int", This.Height, "Ptr", This.BMPDC, "Int", 0, "Int", 0, "UInt", 0x00CC0020)
, HFONT := DllCall("SelectObject", "Ptr", LBDC, "Ptr", This.Font, "UPtr")
, DllCall("SetBkMode", "Ptr", LBDC, "Int", 1) ; TRANSPARENT
, VarSetCapacity(RECT, 16, 0)
, DllCall("GetClientRect", "Ptr", This.HLB, "Ptr", &RECT)
, This.Width := NumGet(RECT, 8, "Int")
, This.Height := NumGet(RECT, 12, "Int")
, DllCall("SendMessage", "Ptr", hWnd, "UInt", 0x0198, "Ptr", TopIndex, "Ptr", &RECT) ; LB_GETITEMRECT
, SELDC := This.SELDC
, ItemCount := This.ItemCount
, Height := This.Height
While (TopIndex < ItemCount) && (NumGet(RECT, 12, "Int") <= Height) {
If (SELDC) && (TopIndex = CurSel) {
L := NumGet(RECT, 0, "Int"), T := NumGet(RECT, 4, "Int")
, W := NumGet(RECT, 8, "Int") - L, H := NumGet(RECT, 12, "Int") - T
If ((BkTrans := This.SelBkTrans) = 255)
DllCall("BitBlt", "Ptr", LBDC, "Int", L, "Int", T, "Int", W, "Int", H, "Ptr", SELDC, "Int", 0, "Int", 0, "UInt", 0x00CC0020)
Else
DllCall("GdiAlphaBlend", "Ptr", LBDC, "Int", L, "Int", T, "Int", W, "Int", H, "Ptr", SELDC, "Int", 0, "Int", 0, "Int", W, "Int", H, "UInt", BkTrans << 16)
}
Txt := This.Items[TopIndex + 1], Len := StrLen(Txt)
, TextColor := (TopIndex = CurSel ? This.SelColor : This.TxtColor)
, DllCall("SetTextColor", "Ptr", LBDC, "UInt", TextColor)
, NumPut(NumGet(RECT, 0, "Int") + 3, RECT, 0, "Int")
, DllCall("DrawText", "Ptr", LBDC, "Ptr", &Txt, "Int", Len, "Ptr", &RECT, "UInt", 0x0840)
, NumPut(NumGet(RECT, 0, "Int") - 3, RECT, 0, "Int")
, DllCall("OffsetRect", "Ptr", &RECT, "Int", 0, "Int", This.ItemHeight)
, TopIndex++
}
DllCall("SelectObject", "Ptr", LBDC, "Ptr", HFONT, "UPtr")
, DllCall("EndPaint", "Ptr", &PAINTSTRUCT)
Return 0
}
; WM_ERASEBKGND message ------------------------------------------------------------------------------------------------------
If (uMsg = 0x0014) {
If (!This.HasBackground) { ; processed once after creation/update
VarSetCapacity(RECT, 16, 0)
, DllCall("GetClientRect", "Ptr", This.HLB, "Ptr", &RECT)
, This.Width := W := NumGet(RECT, 8, "Int")
, This.Height := H := NumGet(RECT, 12, "Int")
, DllCall("ClientToScreen", "Ptr", This.HLB, "Ptr", &RECT)
, DllCall("ScreenToClient", "Ptr", This.HBG, "Ptr", &RECT)
, This.Left := L := NumGet(RECT, 0, "Int")
, This.Top := T := NumGet(RECT, 4, "Int")
, BGDC := DllCall("GetDC", "Ptr", This.HBG, "UPtr")
, BMPDC := DllCall("CreateCompatibleDC", "Ptr", BGDC, "UPtr")
, HBMP := DllCall("CreateCompatibleBitmap", "Ptr", BGDC, "Int", W, "Int", H, "UPtr")
, DllCall("SelectObject", "Ptr", BMPDC, "Ptr", HBMP)
, DllCall("BitBlt", "Ptr", BMPDC, "Int", 0, "Int", 0, "Int", W, "Int", H, "Ptr", BGDC, "Int", L, "Int", T, "UInt", 0x00CC0020)
If (This.SelBkGnd <> "") {
SELDC := DllCall("CreateCompatibleDC", "Ptr", BGDC, "UPtr")
, HSEL := DllCall("CreateCompatibleBitmap", "Ptr", BGDC, "Int", This.ItemWidth, "Int", This.ItemHeight, "UPtr")
, DllCall("SelectObject", "Ptr", SELDC, "Ptr", HSEL)
, Brush := DllCall("CreateSolidBrush", "UInt", This.SelBkGnd, "UPtr")
, VarSetCapacity(RECT, 16, 0)
, NumPut(This.ItemWidth, RECT, 8, "Int")
, NumPut(This.ItemHeight, RECT, 12, "Int")
, DllCall("FillRect", "Ptr", SELDC, "Ptr", &RECT, "Ptr", Brush)
, DllCall("DeleteObject", "Ptr", Brush)
, This.SELDC := SELDC
, This.HSEL := HSEL
}
DllCall("ReleaseDC", "Ptr", This.HBG, "Ptr", BGDC)
, This.BMPDC := BMPDC
, This.HBMP := HBMP
, LBDC := DllCall("GetDC", "Ptr", hWnd, "UPtr")
, DllCall("BitBlt", "Ptr", LBDC, "Int", 0, "Int", 0, "Int", W, "Int", H, "Ptr", BMPDC, "Int", 0, "Int", 0, "UInt", 0x00CC0020)
, DllCall("ReleaseDC", "Ptr", hWnd, "Ptr", LBDC)
, This.HasBackground := True
}
Return True
}
; ----------------------------------------------------------------------------------------------------------------------------
; Selection & Focus
; ----------------------------------------------------------------------------------------------------------------------------
; WM_KILLFOCUS and WM_SETFOCUS messages --------------------------------------------------------------------------------------
If (uMsg = 0x0008) || (uMsg = 0x0007) ; not processed
Return 0
; LB_SETCURSEL message -------------------------------------------------------------------------------------------------------
If (uMsg = 0x0186) {
If (This.Drawing) {
DrawingMsg := uMsg
This.SetRedraw(False)
}
DllCall("DefSubclassProc", "Ptr", hWnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam)
If (DrawingMsg = 0x0186) {
This.SetRedraw(True)
DrawingMsg := ""
}
TopIndex := DllCall("SendMessage", "Ptr", hWnd, "UInt", 0x018E, "Ptr", 0, "Ptr", 0, "Int") ; LB_GETTOPINDEX
DllCall("SetScrollPos", "Ptr", hWnd, "Int", 1, "Int", TopIndex, "UInt", True)
Return 0
}
; ----------------------------------------------------------------------------------------------------------------------------
; Keyboard
; ----------------------------------------------------------------------------------------------------------------------------
; WM_KEYDOWN message ---------------------------------------------------------------------------------------------------------
If (uMsg = 0x0100) {
If VK.HasKey(wParam) {
CurSel := This.CurSel
CurSel := (wParam = VK.DOWN) ? (CurSel + 1)
: (wParam = VK.UP) ? (CurSel - 1)
: (wParam = VK.Next) ? (CurSel + This.SIPage - 1)
: (wParam = VK.PRIOR) ? (CurSel - This.SIPage + 1)
: (wParam = VK.HOME) ? This.SIMin
: (wParam = VK.END) ? This.SIMax
: CurSel
CurSel := (CurSel < This.SIMin) ? This.SIMin : (CurSel > This.SIMax) ? This.SIMax : CurSel
If (Cursel <> This.Cursel) {
If (This.Drawing) {
DrawingMsg := uMsg
This.SetRedraw(False)
}
DllCall("SendMessage", "Ptr", hWnd, "UInt", 0x0186, "Ptr", CurSel, "Ptr", 0) ; LB_SETCURSEL
If (DrawingMsg = 0x0100) {
This.SetRedraw(True)
DrawingMsg := ""
}
DllCall("PostMessage", "Ptr", This.Parent, "UInt", 0x0111, "Ptr", This.CtrlID | (1 << 16), "Ptr", hwnd) ; LBN_SELCHANGE
This.Cursel := CurSel
}
}
Return 0
}
; ----------------------------------------------------------------------------------------------------------------------------
; Mouse
; ----------------------------------------------------------------------------------------------------------------------------
; WM_LBUTTONDOWN message -----------------------------------------------------------------------------------------------------
If (uMsg = 0x0201) {
ControlFocus, , ahk_id %hWnd%
Return 0
}
; WM_LBUTTONUP message -------------------------------------------------------------------------------------------------------
If (uMsg = 0x0202) {
Item := DllCall("SendMessage", "Ptr", hWnd, "UInt", 0x01A9, "Ptr", 0, "Ptr", lParam, "UInt") ; LB_SETCURSEL
If !(Item & 0xFFFF0000) && (Item <> This.CurSel) {
If (This.Drawing) {
DrawingMsg := uMsg
This.SetRedraw(False)
}
DllCall("SendMessage", "Ptr", hWnd, "UInt", 0x0186, "Ptr", Item, "Ptr", 0) ; LB_SETCURSEL
If (DrawingMsg = 0x0202) {
This.SetRedraw(True)
DrawingMsg := ""
}
DllCall("PostMessage", "Ptr", This.Parent, "UInt", 0x0111, "Ptr", This.CtrlID | (1 << 16), "Ptr", hwnd) ; LBN_SELCHANGE
This.Cursel := Item
}
Return 0
}
; WM_LBUTTONDBLCLK message ---------------------------------------------------------------------------------------------------
If (uMsg = 0x0203) {
DllCall("PostMessage", "Ptr", This.Parent, "UInt", 0x0111, "Ptr", This.CtrlID | (2 << 16), "Ptr", hwnd) ; LBN_DBLCLICK
Return 0
}
; WM_MOUSEWHEEL message ------------------------------------------------------------------------------------------------------
If (uMsg = 0x020A) {
Delta := (wParam >> 16) & 0xFFFF
If ((Delta <= 0x7FFF) && (This.TopIndex > This.SIMin))
|| ((Delta > 0x7FFF) && ((This.TopIndex + This.SIPage) <= This.SIMax))
DllCall("PostMessage", "Ptr", hWnd, "UInt", 0x0115, "Ptr", Delta > 0x7FFF ? 1 : 0, "Ptr", 0) ; WM_VSCROLL
Return 0
}
; ----------------------------------------------------------------------------------------------------------------------------
; Scrolling
; ----------------------------------------------------------------------------------------------------------------------------
; WM_HCROLL message ----------------------------------------------------------------------------------------------------------
If (uMsg = 0x0114)
Return 0
; WM_VSCROLL message ---------------------------------------------------------------------------------------------------------
If (uMsg = 0x0115) {
If (((wParam = 1) || (wParam = 3) || (wParam = 7)) && ((This.TopIndex + This.SIPage) > This.SIMax))
|| (((wParam = 0) || (wParam = 2) || (wParam = 6)) && (This.TopIndex <= This.SIMin))
|| (wParam = 8) || (wParam = 4)
Return 0
If (This.Drawing) {
DrawingMsg := uMsg
This.SetRedraw(False)
}
DllCall("DefSubclassProc", "Ptr", hWnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam)
If (DrawingMsg = 0x0115) {
This.SetRedraw(True)
DrawingMsg := ""
}
This.TopIndex := DllCall("SendMessage", "Ptr", hWnd, "UInt", 0x018E, "Ptr", 0, "Ptr", 0, "Int") ; LB_GETTOPINDEX
ScrollPos := DllCall("GetScrollPos", "Ptr", hWnd, "Int", 1, "Int") ; SB_VERT = 1
DllCall("SetScrollPos", "Ptr", hWnd, "Int", 1, "Int", ScrollPos, "UInt", True)
Return 0
}
; ----------------------------------------------------------------------------------------------------------------------------
; Destroy
; ----------------------------------------------------------------------------------------------------------------------------
; WM_DESTROY message ---------------------------------------------------------------------------------------------------------
If (uMsg = 0x0002)
This.__Delete()
; ----------------------------------------------------------------------------------------------------------------------------
; Call DefSubclassProc for all which reached this point
; ----------------------------------------------------------------------------------------------------------------------------
Return DllCall("DefSubclassProc", "Ptr", hWnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam)
}
}