Jump to content

LVN_KEYDOWN and checked/unchecked?


GaryC
 Share

Recommended Posts

I am trying to catch when the user checks an item in a list view so that it can be saved in a recovery info file. I found no way to find the focused item without iterating through the list view items, so I save the current item index when I receive LVN_ITEMCHANGED with focused state. Then when LVN_KEYDOWN for SPACE occurs I change the checked state and record the state with the param of the current item. I suppress default handling. I might have been able to allow default processing and record the opposite of the checked state (NOT _GUICtrlListView_GetItemChecked()) but I thought doing it myself was safer. The problem is that most of the time the checked state doesn't change, at least not long enough to show on the screen. The exception is if I press SPACE immediately after the program starts.

The following is the debug output resulting from loading the program, and pressing DownArrow twice followed by SPACE. (Why does the first DownArrow do nothing?) The SPACE should cause the current item to be checked, and according to the debug output it does, but after the following LVN_ITEMCHANGED the debug output shows it is unchecked. Can anyone tell me why that's happening?

WMNotify Roster keydown key = 40

LVN_ITEMCHANGED: item 0 changed, not focus, NewState = 0x00000000, Changed = 00000008

Checked = False

LVN_ITEMCHANGED: item 0 changed, new state 00000003, Changed = 00000008

Checked = False

WMNotify Roster keydown key = 40

LVN_ITEMCHANGED: item 0 changed, not focus, NewState = 0x00000000, Changed = 00000008

Checked = False

LVN_ITEMCHANGED: item 0 changed, not focus, NewState = 0x00000000, Changed = 00000008

Checked = False

LVN_ITEMCHANGED: item 1 changed, new state 00000003, Changed = 00000008

Checked = False

WMNotify Roster keydown key = 32

$giCurItem = 1

Checking

LVN_KEYDOWN after checking/unchecking Item check state is True

LVN_ITEMCHANGED: item 1 changed, not focus, NewState = 0x00001000, Changed = 00000008

Checked = False

(Note: I am blind. I used a modified version of the LbC.au3 (Layout by Code) library by Jamal Mazrui to do the rough layout and then tweaked it based on coordinate information provided by my JAWS screen reader. My understanding of the checked/unchecked state of list view items is that reported by JAWS. If the layout is strange, or if there is something happening on the screen I'm not seeing, that's probably why.)

#include <array.au3>
#Include <GUIConstantsEx.au3>
#Include <WindowsConstants.au3>
#include <ListviewConstants.au3>
#include <GuiListview.au3>

Global Const $VK_SPACE = 0x20

Global $gIdlRoster, $gIdeMemo
Global $ghForm


Global $giCurItem = 0 ; index in $gIdlRoster of current item, set in WMNotify

Main()

Func Main()
    FormCreate()
    ; Try to make the first press of DownArrow move to the second item, but it doesn't change this, even with focused set to true.
    _GUICtrlListView_SetItemSelected($gIdlRoster, 0)
    ;Local $sDbg = LbcDbgCtrlCoords()
    ;ConsoleWrite($sDbg)
    FormExec()
    GUIDelete()
EndFunc   ;==>Main


Func Dbg($sMsg)
    Local $iRtn = GUICtrlSetData($gIdeMemo, $sMsg & @CRLF, 1)
    If $iRtn <> 1 Then MsgBox(0, "Dbg", "GUICtrlSetData returned " & $iRtn & "@LFmsg = " & $sMsg)
    ;_lbcSay($sMsg)
EndFunc   ;==>Dbg

; Create the GUI.
Func FormCreate()
$ghForm = GUICreate("Test logging of LV check/uncheck", 600, 500)
$gIdlRoster = GUICtrlCreateListView("Item        ", 14, 14, 400, 300, 0x5003000D, 0x00000004)
    For $i = 1 To 5
        Local $sItem = "Item " & $i
        GUICtrlCreateListViewItem($sItem, $gIdlRoster)
        _GUICtrlListView_SetItemParam($gIdlRoster, $i - 1, $i) ; store index
    Next ; $i
GUICtrlCreateLabel("Memo", 14, 328, 29, 16, 0x50020100, 0x00000000)
$gIdeMemo = GUICtrlCreateEdit("", 49, 328, 500, 100, 0x503110C4, 0x00000000)
WinSetState($ghForm, "", @SW_MAXIMIZE)
    GUISetState()
EndFunc   ;==>FormCreate

Func FormExec()
    GUIRegisterMsg($WM_NOTIFY, "WmNotify")

    While 1
        Local $iMsg = GUIGetMsg()

        Switch $iMsg
            Case 0
                ContinueLoop
            Case $GUI_EVENT_CLOSE
                ExitLoop
        EndSwitch
    WEnd ; main loop
EndFunc   ;==>FormExec

Func ErrorMsg($sMsg)
    MsgBox(0, "Error", $sMsg)
EndFunc   ;==>ErrorMsg

; GUIRegisterMsg function to handle the WM_NOTIFY message.
; We want to change the checked state of the item when SPACE is pressed as the default action,
; but we want to record that the item is checked.  We do it by recording the 0-based
; index of the focused item in LVN_ITEMCHANGED, then we record it and set the state
; in LVN_KEYDOWN.  We also set the checked/unchecked state and avoid default
; processing, although we could reverse the test and then let the default processing change the state.
Func WmNotify($hWnd, $iMsg, $iWParam, $iLParam)
    Local $tagNM = DllStructCreate($tagNMHdr, $iLParam)
    Switch DllStructGetData($tagNM, "Code")
        Case $LVN_KEYDOWN
            If DllStructGetData($tagNM, "IdFrom") = $gIdlRoster Then
                $tagNM = DllStructCreate($tagNMLVKEYDOWN, $iLParam)
                Local $iKey = DllStructGetData($tagNM, "VKEY")
                Dbg("WMNotify Roster keydown key = " & $iKey) ; debug
                If $iKey = $VK_SPACE Then
                    Local $iCheckin = _GUICtrlListView_GetItemParam($gIdlRoster, $giCurItem)
                    Dbg("  $giCurItem = " & $giCurItem) ; debug
                    ; $giCurItem was set in previous LVN_ITEMCHANGED.
                    If _GUICtrlListView_GetItemChecked($gIdlRoster, $giCurItem) Then
                        Dbg("  unchecking") ; debug
                        _GUICtrlListView_SetItemChecked($gIdlRoster, $giCurItem, False)
                        CheckinRemove($iCheckin)
                    Else
                        Dbg("  Checking") ; debug
                        _GUICtrlListView_SetItemChecked($gIdlRoster, $giCurItem, True)
                        CheckinAdd($iCheckin)
                    EndIf ; checked
                    ; Checked state is shown to be changed here.
                    Dbg("  LVN_KEYDOWN after checking/unchecking Item check state is " & _GUICtrlListView_GetItemChecked($gIdlRoster, $giCurItem)) ; debug
                    Return 0 ; suppress normal message handling
                    ;Return $GUI_RUNDEFMSG ; debug
                EndIf ; if $VK_SPACE
            EndIf ; if lRoster

        Case $LVN_ITEMCHANGED
            If DllStructGetData($tagNM, "IdFrom") = $gIdlRoster Then
                $tagNM = DllStructCreate($tagNMLISTVIEW, $iLParam)
                If BitAND(DllStructGetData($tagNM, "NewState"), $LVIS_FOCUSED) Then
                    ; We get here when we arrow up/down to the item.
                    ; remember the item in this event.
                    $giCurItem = DllStructGetData($tagNM, "Item")
                    Dbg("LVN_ITEMCHANGED: item " & $giCurItem & " changed, new state " & Hex(DllStructGetData($tagNM, "NewState")) & ", Changed = " & Hex(DllStructGetData($tagNM, "Changed"))) ; debug
                    Dbg("  Checked = " & _GUICtrlListView_GetItemChecked($gIdlRoster, $giCurItem)) ; debug
                Else ; debug
                    Dbg("LVN_ITEMCHANGED: item " & $giCurItem & " changed, not focus, NewState = 0x" & Hex(DllStructGetData($tagNM, "NewState")) & ", Changed = " & Hex(DllStructGetData($tagNM, "Changed"))) ; debug
                    Dbg("  Checked = " & _GUICtrlListView_GetItemChecked($gIdlRoster, $giCurItem)) ; debug
                EndIf ; else not focused
                Return $GUI_RUNDEFMSG ; do normal message handling
            EndIf ; if lRoster
    EndSwitch ; code
EndFunc   ;==>WmNotify


; Add a checkin to the list of checkins.
; $iCheckin - Identifier of checkin record (Index in $gaRoster of checkin or $gciNewCheckinFirst + index in $gaNewCheckins).
; Updates globals $gaCheckins, $gaNewCheckins.
Func CheckinAdd($iCheckin)
EndFunc   ;==>CheckinAdd

; Updates globals $gaCheckins, $gaNewCheckins.
Func CheckinRemove($iCheckin)
EndFunc   ;==>CheckinRemove

Thanks.

GaryC

Link to comment
Share on other sites

Look here

You were very close ;-)

LVN_KEYDOWN logic is not neccessary here

EDIT:

In my scripts where I need to react on change of checked/unchecked state in listview I use it like this:

...
    If $hWndGUI = $Form1 And $wParam = $ListView1 And $disable_refresh_status = False Then
        If $event = $LVN_ITEMCHANGED Then
            $NMLISTVIEW = DllStructCreate($tagNMLISTVIEW, $lParam)
            If BitAND(DllStructGetData($NMLISTVIEW, "Changed"), $LVIF_STATE) = $LVIF_STATE And _
                DllStructGetData($NMLISTVIEW, "NewState") <> DllStructGetData($NMLISTVIEW, "OldState") Then RefreshStatusBar()
        EndIf
    EndIf
...

in Func RefreshStatusBar() I go through listview items and call

_GUICtrlListView_GetItemChecked($ListView1, $i)

to check number of checked items and show this number in statusbar

Edited by Zedna
Link to comment
Share on other sites

  • 4 weeks later...

Thanks, Zedna.

I ended up using something like you suggested. I use LVN_ITEMCHANGED. The trick is that checked and unchecked appear as state images in this notification.

(This is extracted from working code but this sample doesn't stand alone.)

#include <GUIConstantsEx.au3>
#include <ListviewConstants.au3>
#include <structureConstants.au3>
Global Const $LVIS_CHECKED = 0x2000 ; list view item checked
Global Const $LVIS_UNCHECKED = 0x1000

; ....

Func WmNotify($hWnd, $iMsg, $iWParam, $iLParam)
    Local $tagNM = DllStructCreate($tagNMHDR, $iLParam)
    Switch DllStructGetData($tagNM, "Code")

        Case $LVN_ITEMCHANGED
            If DllStructGetData($tagNM, "IdFrom") = $gIdlRoster Then
                $tagNM = DllStructCreate($tagNMLISTVIEW, $iLParam)
                Local $iCheckedState = BitAND(DllStructGetData($tagNM, "NewState"), $LVIS_STATEIMAGEMASK) ; state image index * 0x1000
                If $iCheckedState Then
                    $giCurItem = DllStructGetData($tagNM, "Item")
                    Local $fChecked = ($iCheckedState = $LVIS_CHECKED)
                    Local $fUnChecked = ($iCheckedState = $LVIS_UNCHECKED)
                EndIf ; $iCheckedState
            EndIf ; IDFrom
    EndSwitch
EndFunc   ;==>WmNotify

I'm not testing that newstate is different from oldstate, or testing the member indicating what has changed, but it seems to work for my application.

Gary

Link to comment
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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...