How to make Rubberband selection to select multiple items in Autohotkey v2 listview?

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
Joeyy
Posts: 52
Joined: 08 Mar 2019, 01:57

How to make Rubberband selection to select multiple items in Autohotkey v2 listview?

24 May 2024, 08:31

like the pic below I make a Autohotkey Gui, I want to make Rubberband selection to select multiple items as the 2nd pic. Can any one help me?

Image
Image
teadrinker
Posts: 4412
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to make Rubberband selection to select multiple items in Autohotkey v2 listview?

24 May 2024, 14:20

Try:

Code: Select all

#Requires AutoHotkey v2

wnd := Gui()
wnd.OnEvent('Close', (*) => ExitApp())
wnd.SetFont('s12', 'Calibri')
lv := wnd.Add('ListView', 'r10 w400', ['Name','Size (KB)'])
Loop Files, A_MyDocuments . '\*.*' {
    lv.Add(, A_LoopFileName, A_LoopFileSizeKB)
}
lv.ModifyCol(1, 'AutoHdr')
lv.ModifyCol(2, 'AutoHdr Integer')
wnd.Show()
OnMessage(0x201, WM_LBUTTONDOWN)

WM_LBUTTONDOWN(wp, lp, msg, hwnd) {
    static LVM_HITTEST := 0x1012, LVM_ISITEMVISIBLE := 0x10B6, LVM_GETITEMRECT := 0x100E
    res := true
    if hwnd == lv.hwnd {
        NumPut('Int', lp & 0xFFFF, 'Int', lp >> 16, buf := Buffer(24))
        res := SendMessage(LVM_HITTEST,, buf, lv) != -1
    }
    if res {
        WinGetPos(&x, &y,,, lv)
        lv.itemRects := []
        Loop lv.GetCount() {
            i := A_Index - 1
            if !SendMessage(LVM_ISITEMVISIBLE, i,, lv) {
                lv.itemRects.Push('')
                continue
            }
            SendMessage LVM_GETITEMRECT, i, RECT := Buffer(16, 0), lv
            NumPut('Int', x,
                   'Int', NumGet(RECT,  4, 'Int') + y,
                   'Int', NumGet(RECT,  8, 'Int') + x,
                   'Int', NumGet(RECT, 12, 'Int') + y, RECT)
            lv.itemRects.Push(RECT)
        }
        lv.area := SelectArea(0x5A00AAEE, SelectItems)
    }
}

SelectItems(p1, p2) {
    static testRECT := Buffer(16)
    NumPut('Int', p1['x'], 'Int', p1['y'], 'Int', p2['x'], 'Int', p2['y'], RECT := Buffer(16))
    for i, itemRECT in lv.itemRects {
        res := itemRECT && DllCall('IntersectRect', 'Ptr', testRECT, 'Ptr', RECT, 'Ptr', itemRECT)
        lv.Modify(i, (res ? '' : '-') . 'Select')
    }
}

~*LButton Up:: lv.area := ''

class SelectArea
{
    WH_MOUSE_LL := 14, WM_MOUSEMOVE := 0x200

    __New(colorARGB, callback?) {
        IsSet(callback) && this.callback := callback
        this.CreateGui(colorARGB)
        CoordMode 'Mouse'
        MouseGetPos &x, &y
        this.startX := x
        this.startY := y
        this.hook := WindowsHook(this.WH_MOUSE_LL, LowLevelMouseProc)
        ObjRelease(ObjPtr(this))

        LowLevelMouseProc(nCode, wParam, lParam) {
            if wParam == this.WM_MOUSEMOVE {
                mouseX := NumGet(lParam + 0, 'Int')
                mouseY := NumGet(lParam + 4, 'Int')
                this.ShowBetweenTwoPoints(Map('x', this.startX, 'y', this.startY), Map('x', mouseX, 'y', mouseY))
            }
            return DllCall('CallNextHookEx', 'Ptr', 0, 'Int', nCode, 'UInt', wParam, 'Ptr', lParam, 'Ptr')
        }
    }

    __Delete() {
        ObjAddRef(ObjPtr(this))
        this.hook := ''
        this.gui.Destroy()
    }

    CreateGui(colorARGB) {
        wnd := Gui('-Caption ToolWindow AlwaysOnTop Border -DPIScale')
        wnd.BackColor := Format('{:X}', colorARGB & 0xFFFFFF)
        WinSetTransparent(colorARGB >> 24, wnd)
        this.gui := wnd
    }

    ShowBetweenTwoPoints(p1, p2) {
        p1['x'] < p2['x'] ? (x1 := p1['x'], x2 := p2['x']) : (x1 := p2['x'], x2 := p1['x'])
        p1['y'] < p2['y'] ? (y1 := p1['y'], y2 := p2['y']) : (y1 := p2['y'], y2 := p1['y'])
        this.gui.Show('NA x' . x1 . ' y' . y1 . ' w' . x2 - x1 . ' h' . y2 - y1)
        Loop 2 {
            this.x%A_Index% := x%A_Index%
            this.y%A_Index% := y%A_Index%
        }
        if this.HasOwnProp('callback') {
            this.callback.Call(Map('x', this.x1, 'y', this.y1), Map('x', this.x2, 'y', this.y2))
        }
    }
}

class WindowsHook {
    __New(type, callback, isGlobal := true) {
        this.pCallback := CallbackCreate(callback, 'Fast', 3)
        this.hHook := DllCall('SetWindowsHookEx', 'Int', type, 'Ptr', this.pCallback
            , 'Ptr', !isGlobal ? 0 : DllCall('GetModuleHandle', 'UInt', 0, 'Ptr')
            , 'UInt', isGlobal ? 0 : DllCall('GetCurrentThreadId'), 'Ptr')
    }
    __Delete() {
        DllCall('UnhookWindowsHookEx', 'Ptr', this.hHook)
        CallbackFree(this.pCallback)
    }
}
Joeyy
Posts: 52
Joined: 08 Mar 2019, 01:57

Re: How to make Rubberband selection to select multiple items in Autohotkey v2 listview?

24 May 2024, 22:05

teadrinker wrote:
24 May 2024, 14:20
Try:

Code: Select all

#Requires AutoHotkey v2

wnd := Gui()
wnd.OnEvent('Close', (*) => ExitApp())
wnd.SetFont('s12', 'Calibri')
lv := wnd.Add('ListView', 'r10 w400', ['Name','Size (KB)'])
Loop Files, A_MyDocuments . '\*.*' {
    lv.Add(, A_LoopFileName, A_LoopFileSizeKB)
}
lv.ModifyCol(1, 'AutoHdr')
lv.ModifyCol(2, 'AutoHdr Integer')
wnd.Show()
OnMessage(0x201, WM_LBUTTONDOWN)

WM_LBUTTONDOWN(wp, lp, msg, hwnd) {
    static LVM_HITTEST := 0x1012, LVM_ISITEMVISIBLE := 0x10B6, LVM_GETITEMRECT := 0x100E
    res := true
    if hwnd == lv.hwnd {
        NumPut('Int', lp & 0xFFFF, 'Int', lp >> 16, buf := Buffer(24))
        res := SendMessage(LVM_HITTEST,, buf, lv) != -1
    }
    if res {
        WinGetPos(&x, &y,,, lv)
        lv.itemRects := []
        Loop lv.GetCount() {
            i := A_Index - 1
            if !SendMessage(LVM_ISITEMVISIBLE, i,, lv) {
                lv.itemRects.Push('')
                continue
            }
            SendMessage LVM_GETITEMRECT, i, RECT := Buffer(16, 0), lv
            NumPut('Int', x,
                   'Int', NumGet(RECT,  4, 'Int') + y,
                   'Int', NumGet(RECT,  8, 'Int') + x,
                   'Int', NumGet(RECT, 12, 'Int') + y, RECT)
            lv.itemRects.Push(RECT)
        }
        lv.area := SelectArea(0x5A00AAEE, SelectItems)
    }
}

SelectItems(p1, p2) {
    static testRECT := Buffer(16)
    NumPut('Int', p1['x'], 'Int', p1['y'], 'Int', p2['x'], 'Int', p2['y'], RECT := Buffer(16))
    for i, itemRECT in lv.itemRects {
        res := itemRECT && DllCall('IntersectRect', 'Ptr', testRECT, 'Ptr', RECT, 'Ptr', itemRECT)
        lv.Modify(i, (res ? '' : '-') . 'Select')
    }
}

~*LButton Up:: lv.area := ''

class SelectArea
{
    WH_MOUSE_LL := 14, WM_MOUSEMOVE := 0x200

    __New(colorARGB, callback?) {
        IsSet(callback) && this.callback := callback
        this.CreateGui(colorARGB)
        CoordMode 'Mouse'
        MouseGetPos &x, &y
        this.startX := x
        this.startY := y
        this.hook := WindowsHook(this.WH_MOUSE_LL, LowLevelMouseProc)
        ObjRelease(ObjPtr(this))

        LowLevelMouseProc(nCode, wParam, lParam) {
            if wParam == this.WM_MOUSEMOVE {
                mouseX := NumGet(lParam + 0, 'Int')
                mouseY := NumGet(lParam + 4, 'Int')
                this.ShowBetweenTwoPoints(Map('x', this.startX, 'y', this.startY), Map('x', mouseX, 'y', mouseY))
            }
            return DllCall('CallNextHookEx', 'Ptr', 0, 'Int', nCode, 'UInt', wParam, 'Ptr', lParam, 'Ptr')
        }
    }

    __Delete() {
        ObjAddRef(ObjPtr(this))
        this.hook := ''
        this.gui.Destroy()
    }

    CreateGui(colorARGB) {
        wnd := Gui('-Caption ToolWindow AlwaysOnTop Border -DPIScale')
        wnd.BackColor := Format('{:X}', colorARGB & 0xFFFFFF)
        WinSetTransparent(colorARGB >> 24, wnd)
        this.gui := wnd
    }

    ShowBetweenTwoPoints(p1, p2) {
        p1['x'] < p2['x'] ? (x1 := p1['x'], x2 := p2['x']) : (x1 := p2['x'], x2 := p1['x'])
        p1['y'] < p2['y'] ? (y1 := p1['y'], y2 := p2['y']) : (y1 := p2['y'], y2 := p1['y'])
        this.gui.Show('NA x' . x1 . ' y' . y1 . ' w' . x2 - x1 . ' h' . y2 - y1)
        Loop 2 {
            this.x%A_Index% := x%A_Index%
            this.y%A_Index% := y%A_Index%
        }
        if this.HasOwnProp('callback') {
            this.callback.Call(Map('x', this.x1, 'y', this.y1), Map('x', this.x2, 'y', this.y2))
        }
    }
}

class WindowsHook {
    __New(type, callback, isGlobal := true) {
        this.pCallback := CallbackCreate(callback, 'Fast', 3)
        this.hHook := DllCall('SetWindowsHookEx', 'Int', type, 'Ptr', this.pCallback
            , 'Ptr', !isGlobal ? 0 : DllCall('GetModuleHandle', 'UInt', 0, 'Ptr')
            , 'UInt', isGlobal ? 0 : DllCall('GetCurrentThreadId'), 'Ptr')
    }
    __Delete() {
        DllCall('UnhookWindowsHookEx', 'Ptr', this.hHook)
        CallbackFree(this.pCallback)
    }
}
Thanks! but when Rubberband selecting, it will deselect the previous selected items. Is it possible not to do that when "Ctrl" is pressed? (Like File Explorer, when "Ctrl" is pressed, Rubberband selecting won't deselect the already selected items.
teadrinker
Posts: 4412
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to make Rubberband selection to select multiple items in Autohotkey v2 listview?

24 May 2024, 23:41

In this variant, Rubberband behaves the same way as in Windows Explorer: if the selection rectangle crosses a previously selected line, the selection is deselected; if the line was not selected, it is selected.

Code: Select all

#Requires AutoHotkey v2

wnd := Gui()
wnd.OnEvent('Close', (*) => ExitApp())
wnd.SetFont('s12', 'Calibri')
lv := wnd.Add('ListView', 'r10 w400', ['Name','Size (KB)'])
Loop Files, A_MyDocuments . '\*.*' {
    lv.Add(, A_LoopFileName, A_LoopFileSizeKB)
}
lv.ModifyCol(1, 'AutoHdr')
lv.ModifyCol(2, 'AutoHdr Integer')
wnd.Show()
OnMessage(0x201, WM_LBUTTONDOWN)

WM_LBUTTONDOWN(wp, lp, msg, hwnd) {
    static LVM_HITTEST := 0x1012, LVM_ISITEMVISIBLE := 0x10B6, LVM_GETITEMRECT := 0x100E
    res := true
    lv.prevSelected := Map()
    if GetKeyState('Ctrl', 'P') {
        i := 0
        while i := lv.GetNext(i) {
            lv.prevSelected[i] := ''
        }
    }
    if hwnd == lv.hwnd {
        NumPut('Int', lp & 0xFFFF, 'Int', lp >> 16, buf := Buffer(24))
        res := SendMessage(LVM_HITTEST,, buf, lv) != -1
    }
    if res {
        WinGetPos(&x, &y,,, lv)
        lv.itemRects := []
        Loop lv.GetCount() {
            i := A_Index - 1
            if !SendMessage(LVM_ISITEMVISIBLE, i,, lv) {
                lv.itemRects.Push('')
                continue
            }
            SendMessage LVM_GETITEMRECT, i, RECT := Buffer(16, 0), lv
            NumPut('Int', x,
                   'Int', NumGet(RECT,  4, 'Int') + y,
                   'Int', NumGet(RECT,  8, 'Int') + x,
                   'Int', NumGet(RECT, 12, 'Int') + y, RECT)
            lv.itemRects.Push(RECT)
        }
        lv.area := SelectArea(0x5A00AAEE, SelectItems)
    }
}

SelectItems(p1, p2) {
    static testRECT := Buffer(16)
    NumPut('Int', p1['x'], 'Int', p1['y'], 'Int', p2['x'], 'Int', p2['y'], RECT := Buffer(16))
    for i, itemRECT in lv.itemRects {
        res := itemRECT && DllCall('IntersectRect', 'Ptr', testRECT, 'Ptr', RECT, 'Ptr', itemRECT)
        lv.Modify(i, (res ^ lv.prevSelected.Has(i) ? '' : '-') . 'Select')
    }
}

~*LButton Up:: lv.area := ''

class SelectArea
{
    WH_MOUSE_LL := 14, WM_MOUSEMOVE := 0x200

    __New(colorARGB, callback?) {
        IsSet(callback) && this.callback := callback
        this.CreateGui(colorARGB)
        CoordMode 'Mouse'
        MouseGetPos &x, &y
        this.startX := x
        this.startY := y
        this.hook := WindowsHook(this.WH_MOUSE_LL, LowLevelMouseProc)
        ObjRelease(ObjPtr(this))

        LowLevelMouseProc(nCode, wParam, lParam) {
            if wParam == this.WM_MOUSEMOVE {
                mouseX := NumGet(lParam + 0, 'Int')
                mouseY := NumGet(lParam + 4, 'Int')
                this.ShowBetweenTwoPoints(Map('x', this.startX, 'y', this.startY), Map('x', mouseX, 'y', mouseY))
            }
            return DllCall('CallNextHookEx', 'Ptr', 0, 'Int', nCode, 'UInt', wParam, 'Ptr', lParam, 'Ptr')
        }
    }

    __Delete() {
        ObjAddRef(ObjPtr(this))
        this.hook := ''
        this.gui.Destroy()
    }

    CreateGui(colorARGB) {
        wnd := Gui('-Caption ToolWindow AlwaysOnTop Border -DPIScale')
        wnd.BackColor := Format('{:X}', colorARGB & 0xFFFFFF)
        WinSetTransparent(colorARGB >> 24, wnd)
        this.gui := wnd
    }

    ShowBetweenTwoPoints(p1, p2) {
        p1['x'] < p2['x'] ? (x1 := p1['x'], x2 := p2['x']) : (x1 := p2['x'], x2 := p1['x'])
        p1['y'] < p2['y'] ? (y1 := p1['y'], y2 := p2['y']) : (y1 := p2['y'], y2 := p1['y'])
        this.gui.Show('NA x' . x1 . ' y' . y1 . ' w' . x2 - x1 . ' h' . y2 - y1)
        Loop 2 {
            this.x%A_Index% := x%A_Index%
            this.y%A_Index% := y%A_Index%
        }
        if this.HasOwnProp('callback') {
            this.callback.Call(Map('x', this.x1, 'y', this.y1), Map('x', this.x2, 'y', this.y2))
        }
    }
}

class WindowsHook {
    __New(type, callback, isGlobal := true) {
        this.pCallback := CallbackCreate(callback, 'Fast', 3)
        this.hHook := DllCall('SetWindowsHookEx', 'Int', type, 'Ptr', this.pCallback
            , 'Ptr', !isGlobal ? 0 : DllCall('GetModuleHandle', 'UInt', 0, 'Ptr')
            , 'UInt', isGlobal ? 0 : DllCall('GetCurrentThreadId'), 'Ptr')
    }
    __Delete() {
        DllCall('UnhookWindowsHookEx', 'Ptr', this.hHook)
        CallbackFree(this.pCallback)
    }
}
User avatar
andymbody
Posts: 996
Joined: 02 Jul 2017, 23:47

Re: How to make Rubberband selection to select multiple items in Autohotkey v2 listview?

25 May 2024, 05:07

teadrinker wrote:
24 May 2024, 23:41
Fyi... the code seems to have a bug. As soon as I tried to click-drag, it locked my computer up so I am unable to provide any more details. But I did see, "expected a number but got an empty string" prior to forced POPO. The errors came so rapidly that it is probably related to mouse coords. I don't dare run it again. lol.
teadrinker
Posts: 4412
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to make Rubberband selection to select multiple items in Autohotkey v2 listview?

25 May 2024, 10:08

@andymbody
Understood what might have been the issue, fixed, please try again:

Code: Select all

#Requires AutoHotkey v2

wnd := Gui()
wnd.OnEvent('Close', (*) => ExitApp())
wnd.SetFont('s12', 'Calibri')
lv := wnd.Add('ListView', 'r10 w400', ['Name','Size (KB)'])
Loop Files, A_MyDocuments . '\*.*' {
    lv.Add(, A_LoopFileName, A_LoopFileSizeKB)
}
lv.ModifyCol(1, 'AutoHdr')
lv.ModifyCol(2, 'AutoHdr Integer')
wnd.Show()
OnMessage(0x201, WM_LBUTTONDOWN)

WM_LBUTTONDOWN(wp, lp, msg, hwnd) {
    static LVM_HITTEST := 0x1012, LVM_ISITEMVISIBLE := 0x10B6, LVM_GETITEMRECT := 0x100E
    res := true
    lv.prevSelected := Map()
    if GetKeyState('Ctrl', 'P') {
        i := 0
        while i := lv.GetNext(i) {
            lv.prevSelected[i] := ''
        }
    }
    if hwnd == lv.hwnd {
        NumPut('Int', lp & 0xFFFF, 'Int', lp >> 16, buf := Buffer(24))
        res := SendMessage(LVM_HITTEST,, buf, lv) != -1
    }
    if res {
        WinGetPos(&x, &y,,, lv)
        lv.itemRects := []
        Loop lv.GetCount() {
            i := A_Index - 1
            if !SendMessage(LVM_ISITEMVISIBLE, i,, lv) {
                lv.itemRects.Push('')
                continue
            }
            SendMessage LVM_GETITEMRECT, i, RECT := Buffer(16, 0), lv
            NumPut('Int', x,
                   'Int', NumGet(RECT,  4, 'Int') + y,
                   'Int', NumGet(RECT,  8, 'Int') + x,
                   'Int', NumGet(RECT, 12, 'Int') + y, RECT)
            lv.itemRects.Push(RECT)
        }
        lv.area := SelectArea(0x5A00AAEE, SelectItems)
    }
}

SelectItems(p1, p2) {
    static testRECT := Buffer(16)
    NumPut('Int', p1['x'], 'Int', p1['y'], 'Int', p2['x'], 'Int', p2['y'], RECT := Buffer(16))
    for i, itemRECT in lv.itemRects {
        res := itemRECT && DllCall('IntersectRect', 'Ptr', testRECT, 'Ptr', RECT, 'Ptr', itemRECT)
        lv.Modify(i, (!!res ^ lv.prevSelected.Has(i) ? '' : '-') . 'Select')
    }
}

~*LButton Up:: lv.area := ''

class SelectArea
{
    WH_MOUSE_LL := 14, WM_MOUSEMOVE := 0x200

    __New(colorARGB, callback?) {
        IsSet(callback) && this.callback := callback
        this.CreateGui(colorARGB)
        CoordMode 'Mouse'
        MouseGetPos &x, &y
        this.startX := x
        this.startY := y
        this.hook := WindowsHook(this.WH_MOUSE_LL, LowLevelMouseProc)
        ObjRelease(ObjPtr(this))

        LowLevelMouseProc(nCode, wParam, lParam) {
            if wParam == this.WM_MOUSEMOVE {
                mouseX := NumGet(lParam + 0, 'Int')
                mouseY := NumGet(lParam + 4, 'Int')
                this.ShowBetweenTwoPoints(Map('x', this.startX, 'y', this.startY), Map('x', mouseX, 'y', mouseY))
            }
            return DllCall('CallNextHookEx', 'Ptr', 0, 'Int', nCode, 'UInt', wParam, 'Ptr', lParam, 'Ptr')
        }
    }

    __Delete() {
        ObjAddRef(ObjPtr(this))
        this.hook := ''
        this.gui.Destroy()
    }

    CreateGui(colorARGB) {
        wnd := Gui('-Caption ToolWindow AlwaysOnTop Border -DPIScale')
        wnd.BackColor := Format('{:X}', colorARGB & 0xFFFFFF)
        WinSetTransparent(colorARGB >> 24, wnd)
        this.gui := wnd
    }

    ShowBetweenTwoPoints(p1, p2) {
        p1['x'] < p2['x'] ? (x1 := p1['x'], x2 := p2['x']) : (x1 := p2['x'], x2 := p1['x'])
        p1['y'] < p2['y'] ? (y1 := p1['y'], y2 := p2['y']) : (y1 := p2['y'], y2 := p1['y'])
        this.gui.Show('NA x' . x1 . ' y' . y1 . ' w' . x2 - x1 . ' h' . y2 - y1)
        Loop 2 {
            this.x%A_Index% := x%A_Index%
            this.y%A_Index% := y%A_Index%
        }
        if this.HasOwnProp('callback') {
            this.callback.Call(Map('x', this.x1, 'y', this.y1), Map('x', this.x2, 'y', this.y2))
        }
    }
}

class WindowsHook {
    __New(type, callback, isGlobal := true) {
        this.pCallback := CallbackCreate(callback, 'Fast', 3)
        this.hHook := DllCall('SetWindowsHookEx', 'Int', type, 'Ptr', this.pCallback
            , 'Ptr', !isGlobal ? 0 : DllCall('GetModuleHandle', 'UInt', 0, 'Ptr')
            , 'UInt', isGlobal ? 0 : DllCall('GetCurrentThreadId'), 'Ptr')
    }
    __Delete() {
        DllCall('UnhookWindowsHookEx', 'Ptr', this.hHook)
        CallbackFree(this.pCallback)
    }
}
User avatar
andymbody
Posts: 996
Joined: 02 Jul 2017, 23:47

Re: How to make Rubberband selection to select multiple items in Autohotkey v2 listview?

25 May 2024, 20:36

teadrinker wrote:
25 May 2024, 10:08
fixed, please try again:
Yep... works... thanks! Need to study your code to see how this was done. I may look into how to get it to scroll when selecting as well. But have other priorities at the moment.

Thank you very much! This is great!
Andy

Return to “Ask for Help (v2)”

Who is online

Users browsing this forum: pedro45_vs and 62 guests