Sign in to follow this  
Followers 0
doudou

How To: catch ENTER and double clicks on a ListView item

10 posts in this topic

#1 ·  Posted (edited)

For a while I've been struggling with this problem: when user double clicks an item in a ListView or presses enter key while one is selected, I wanted a certain action to be performed. The normal way to accomplish this would be to respond to LVN_ITEMACTIVATE notification message, sad thing - AutoIt would never send this (or WM_KEYDOWN, WM_KEYUP for that matter) when enter is pressed.

An extensive research in these forums and on the Net brought up different proposals how to solve it, ranging from complete meshugge (polling for keys globally) to quite unsatisfactory (catching enter in a window proc), so - after some message spying and process debugging - I forged the following solution.

Briefly the core of my method is to subclass the ListView (the mentioned proposals came so far too) and to respond to WM_GETDLGCODE message with DLGC_WANTALLKEYS in the window procedure whereafter the rest is a piece of cake: just define WM_NOTIFY callback and handle LVN_ITEMACTIVATE.

Here is the functional example (with some convenience functions for subclassing):

#include <Constants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPI.au3>
#include <GUIListView.au3>

Global Const $DLGC_WANTALLKEYS  = 0x0004

Global $frmMain = GUICreate("LVN_ITEMACTIVATE", 531, 396, -1, -1, BitOR($WS_MINIMIZEBOX,$WS_SIZEBOX,$WS_THICKFRAME,$WS_SYSMENU,$WS_CAPTION,$WS_POPUP,$WS_POPUPWINDOW,$WS_GROUP,$WS_VISIBLE,$WS_BORDER,$WS_CLIPSIBLINGS), BitOR($WS_EX_ACCEPTFILES,$WS_EX_WINDOWEDGE))
Global $lvwTest = GUICtrlCreateListView("Items", 1, 1, 529, 394, BitOR($LVS_REPORT,$LVS_SINGLESEL,$LVS_SHOWSELALWAYS,$WS_TABSTOP,$WS_VISIBLE,$WS_CHILD,$WS_CLIPSIBLINGS), BitOR($WS_EX_CLIENTEDGE,$LVS_EX_FULLROWSELECT,$LVS_EX_FLATSB,$LVS_EX_INFOTIP))

For $i = 0 To 9
    GUICtrlCreateListViewItem("Item " & $i, $lvwTest)
Next

Main()

Func Main()
    _GUICtrl_Subclass("lvwTest")
    GUIRegisterMsg($WM_NOTIFY, "On_WM_NOTIFY")
    While CheckGUIMsg()
        ;
    WEnd
    _GUICtrl_Unsubclass("lvwTest")
EndFunc

Func CheckGUIMsg()
    Local $msg = GUIGetMsg()
    Select
        Case $msg = $GUI_EVENT_CLOSE
            Return False
    EndSelect
    Return True
EndFunc

Func On_WM_NOTIFY($hWnd, $msg, $wParam, $lParam)
    Local $nmhdr = DllStructCreate($tagNMHDR, $lParam)
    Local $hWndFrom = HWnd(DllStructGetData($nmhdr, "hWndFrom"))
    Local $iCode = DllStructGetData($nmhdr, "Code")
    If _GUICtrl_GetHandle($lvwTest) = $hWndFrom Then
        Local $i = -1
        Switch $iCode
            Case $LVN_ITEMACTIVATE
                Local $nmia = DllStructCreate($tagNMITEMACTIVATE, $lParam)
                $i = DllStructGetData($nmia, "Index")
        EndSwitch
        If -1 <> $i Then MsgBox(0, "LVN_ITEMACTIVATE", "Item " & $i)
    EndIf
    Return $GUI_RUNDEFMSG
EndFunc

Func lvwTest_WindowProc($hWnd, $uMsg, $wParam, $lParam)
    If $WM_GETDLGCODE = $uMsg Then
        Return $DLGC_WANTALLKEYS
    EndIf
    Return _GUICtrl_SubclassedProc("lvwTest", $hWnd, $uMsg, $wParam, $lParam)
EndFunc

Func _GUICtrl_Subclass($controlName)
    Local $hlvw = _GUICtrl_GetHandle(Eval($controlName))
    If $hlvw Then
        Local $hProc = DllCallbackRegister($controlName & "_WindowProc", "int", "hwnd;uint;wparam;lparam")
        If $hProc Then
            Assign($controlName & "_hProc", $hProc, 2)
            Assign($controlName & "_hProcOld", _WinAPI_SetWindowLong($hlvw, $GWL_WNDPROC, DllCallbackGetPtr($hProc)), 2)
        EndIf
    EndIf
EndFunc

Func _GUICtrl_Unsubclass($controlName)
    Local $hProc = Eval($controlName & "_hProc")
    If $hProc Then
        _WinAPI_SetWindowLong(_GUICtrl_GetHandle(Eval($controlName)), $GWL_WNDPROC, Eval($controlName & "_hProcOld"))
        DllCallbackFree($hProc)
        Assign($controlName & "_hProc", 0)
        Assign($controlName & "_hProcOld", 0)
    EndIf
EndFunc

Func _GUICtrl_SubclassedProc($controlName, $hWnd, $uMsg, $wParam, $lParam)
    Local $hProc = Eval($controlName & "_hProcOld")
    If $hProc Then Return _WinAPI_CallWindowProc($hProc, $hWnd, $uMsg, $wParam, $lParam)
    Return 0
EndFunc

Func _GUICtrl_GetHandle($control)
    If IsHWnd($control) Then Return $control
    Return GUICtrlGetHandle($control)
EndFunc

Hopefully this will help somebody else save some time and nerves. :(

Edited by doudou

UDFS & Apps:


DDEML.au3 - DDE Client + Server[*]
Localization.au3- localize your scripts[*]
TLI.au3 - type information on COM objects (TLBINF emulation)[*]
TLBAutoEnum.au3 - auto-import of COM constants (enums)[*]
AU3Automation - export AU3 scripts via COM interfaces
TypeLibInspector

- OleView was yesterday

Coder's last words before final release: WE APOLOGIZE FOR INCONVENIENCE 

Share this post


Link to post
Share on other sites



Very nice!

Thx for the complement. I deserve that! :(

UDFS & Apps:


DDEML.au3 - DDE Client + Server[*]
Localization.au3- localize your scripts[*]
TLI.au3 - type information on COM objects (TLBINF emulation)[*]
TLBAutoEnum.au3 - auto-import of COM constants (enums)[*]
AU3Automation - export AU3 scripts via COM interfaces
TypeLibInspector

- OleView was yesterday

Coder's last words before final release: WE APOLOGIZE FOR INCONVENIENCE 

Share this post


Link to post
Share on other sites

very cool! thanks you for all your contributes.

Share this post


Link to post
Share on other sites

Very nice. I'll be using this in my projects.

Five stars!!!!

Thank you for sharing.

Share this post


Link to post
Share on other sites

I modified the example a bit: added _GUICtrl_SubclassedProc() to keep blackboxing consistent and verification that DllCallbackRegister() returns a valid handle.


UDFS & Apps:


DDEML.au3 - DDE Client + Server[*]
Localization.au3- localize your scripts[*]
TLI.au3 - type information on COM objects (TLBINF emulation)[*]
TLBAutoEnum.au3 - auto-import of COM constants (enums)[*]
AU3Automation - export AU3 scripts via COM interfaces
TypeLibInspector

- OleView was yesterday

Coder's last words before final release: WE APOLOGIZE FOR INCONVENIENCE 

Share this post


Link to post
Share on other sites

Nice solution, but maybe easier to use HotKeySet().

Hmm... Easier? Maybe. But HotKeySet() has this effect:

When you set a hotkey, AutoIt captures the key-press and does not pass it on to the active application

This is exactly what my solution intends to avoid. And after all I didn't title it "The easiest way to catch ENTER..." :(

UDFS & Apps:


DDEML.au3 - DDE Client + Server[*]
Localization.au3- localize your scripts[*]
TLI.au3 - type information on COM objects (TLBINF emulation)[*]
TLBAutoEnum.au3 - auto-import of COM constants (enums)[*]
AU3Automation - export AU3 scripts via COM interfaces
TypeLibInspector

- OleView was yesterday

Coder's last words before final release: WE APOLOGIZE FOR INCONVENIENCE 

Share this post


Link to post
Share on other sites

#Include <WindowsConstants.au3>
#Include <WinAPI.au3>
#Include <GUIListView.au3>
#Include <GUIConstantsEx.au3>

Global $Dummy, $Index, $ListView, $hListView

$hForm = GUICreate('MyGUI', 531, 396)
$ListView = GUICtrlCreateListView('Items', 1, 1, 529, 394, BitOR($LVS_SINGLESEL, $LVS_SHOWSELALWAYS))
$hListView = GUICtrlGetHandle(-1)
$Dummy = GUICtrlCreateDummy()
For $i = 0 To 9
    GUICtrlCreateListViewItem('Item ' & $i, $ListView)
Next
HotKeySet('{ENTER}', 'HKEdit')
GUIRegisterMsg($WM_NOTIFY, 'WM_NOTIFY')
GUISetState()

While 1
    $Msg = GUIGetMsg()
    Switch $Msg
        Case $GUI_EVENT_CLOSE
            ExitLoop
        Case $Dummy
            $Index = _GUICtrlListView_GetSelectedIndices($hListView)
            If Not $Index Then
                ContinueLoop
            EndIf
            HotKeySet('{ENTER}')
            MsgBox(0, '', 'Activate: ' & $Index & @CR)
            HotKeySet('{ENTER}', 'HKEdit')
    EndSwitch
WEnd

Func HKEdit()
    Switch WinGetHandle('[ACTIVE]')
        Case 0

        Case $hForm
            If _WinAPI_GetFocus() = $hListView Then
                GUICtrlSendToDummy($Dummy)
                Return
            EndIf
    EndSwitch
    HotKeySet('{ENTER}')
    Send('{ENTER}')
    HotKeySet('{ENTER}', 'HKEdit')
EndFunc   ;==>HKEdit

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)

    Local $tNMITEMACTIVATE = DllStructCreate($tagNMITEMACTIVATE, $lParam)
    Local $hWndFrom = DllStructGetData($tNMITEMACTIVATE, 'hWndFrom')
    Local $Index = DllStructGetData($tNMITEMACTIVATE, 'Index')
    Local $Code = DllStructGetData($tNMITEMACTIVATE, 'Code')

    Switch $hWndFrom
        Case $hListView
            Switch $Code
                Case $LVN_ITEMACTIVATE
                    GUICtrlSendToDummy($Dummy)
            EndSwitch
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

Share this post


Link to post
Share on other sites

#Include <WindowsConstants.au3>
#Include <WinAPI.au3>
#Include <GUIListView.au3>
#Include <GUIConstantsEx.au3>

Global $Dummy, $Index, $ListView, $hListView

$hForm = GUICreate('MyGUI', 531, 396)
$ListView = GUICtrlCreateListView('Items', 1, 1, 529, 394, BitOR($LVS_SINGLESEL, $LVS_SHOWSELALWAYS))
$hListView = GUICtrlGetHandle(-1)
$Dummy = GUICtrlCreateDummy()
For $i = 0 To 9
    GUICtrlCreateListViewItem('Item ' & $i, $ListView)
Next
HotKeySet('{ENTER}', 'HKEdit')
GUIRegisterMsg($WM_NOTIFY, 'WM_NOTIFY')
GUISetState()

While 1
    $Msg = GUIGetMsg()
    Switch $Msg
        Case $GUI_EVENT_CLOSE
            ExitLoop
        Case $Dummy
            $Index = _GUICtrlListView_GetSelectedIndices($hListView)
            If Not $Index Then
                ContinueLoop
            EndIf
            HotKeySet('{ENTER}')
            MsgBox(0, '', 'Activate: ' & $Index & @CR)
            HotKeySet('{ENTER}', 'HKEdit')
    EndSwitch
WEnd

Func HKEdit()
    Switch WinGetHandle('[ACTIVE]')
        Case 0

        Case $hForm
            If _WinAPI_GetFocus() = $hListView Then
                GUICtrlSendToDummy($Dummy)
                Return
            EndIf
    EndSwitch
    HotKeySet('{ENTER}')
    Send('{ENTER}')
    HotKeySet('{ENTER}', 'HKEdit')
EndFunc   ;==>HKEdit

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)

    Local $tNMITEMACTIVATE = DllStructCreate($tagNMITEMACTIVATE, $lParam)
    Local $hWndFrom = DllStructGetData($tNMITEMACTIVATE, 'hWndFrom')
    Local $Index = DllStructGetData($tNMITEMACTIVATE, 'Index')
    Local $Code = DllStructGetData($tNMITEMACTIVATE, 'Code')

    Switch $hWndFrom
        Case $hListView
            Switch $Code
                Case $LVN_ITEMACTIVATE
                    GUICtrlSendToDummy($Dummy)
            EndSwitch
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

Interesting example. I would stick with my proposal nonetheless, if you don't mind.

The reasons are:

  • Your solution is even more complex and its complexity increases overproportional with every additional ListView in the project.
  • Dummy controls are needed.
  • You have to deactivate/reactivate the hotkey every now and then in order to process other ENTER events.
  • Potential timing problems: you cannot guarantee HKEdit() is executed atomically, therefore it is possible that the relayed ENTER would hit the wrong target.
  • I would always prefer the way defined by the API publisher (which LVN_ITEMACTIVATE is as documented by MS) over a hack (which your example is strictly spoken).

UDFS & Apps:


DDEML.au3 - DDE Client + Server[*]
Localization.au3- localize your scripts[*]
TLI.au3 - type information on COM objects (TLBINF emulation)[*]
TLBAutoEnum.au3 - auto-import of COM constants (enums)[*]
AU3Automation - export AU3 scripts via COM interfaces
TypeLibInspector

- OleView was yesterday

Coder's last words before final release: WE APOLOGIZE FOR INCONVENIENCE 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0