ListView- GRRRRR!!!!

Started by JR Heathcote, July 21, 2008, 08:49:26 PM

Previous topic - Next topic

JR Heathcote

OK, I have a ListView control, but I can't trap a user click on the listview with the mouse.  Aggrevation, thy name is 'ListView'.

Here is the code:


'--------------------------------------------------------------------------
Function IFE_LVUNDO_CUSTOM ( _
                           ControlIndex  As Long,  _  ' index in Control Array
                           hWndForm      As Dword, _  ' handle of Form
                           hWndControl   As Dword, _  ' handle of Control
                           wMsg          As Long,  _  ' type of message
                           wParam        As Dword, _  ' first message parameter
                           lParam        As Long   _  ' second message parameter
                           ) As Long

  Local nRow      As Long
 
  Local nInc      As Long
  Local sVBTerm   As String
  Local sPBTerm   As String
  Local sFileName As String
 
  Local pNmLvw    As NM_LISTVIEW Ptr
  Local pNmHdr    As NMHDR Ptr
  '------------------------------------------------------------------------

'THE FOLLOWING IS FROM ANOTHER FIREFLY PROJECT, WHICH WORKS, BUT BRINGING
'THAT CODE OVER TO THIS APPLICATION DOES *NOT* WORK, CRAP-A-LAP-A-DOO-DAP!

' Select Case wMsg
'    Case %WM_NOTIFY    '%WM_NOTIFY IS FIRED
'     
'      'NOTE: The LO(Word, wParam) IS NEVER GETS SET TO IDC_IFE_LVUNDO
'      Select Case Lo(Word, wParam)   
'        Case IDC_IFE_LVUNDO
'          pNmLvw = lParam
'         
'          Select Case @pNmLvw.hdr.code
'            Case %NM_CLICK                            'Click in listview?
'              'pNmLvw = lParam
'                If @pNmLvw.iItem > -1 Then
'                  nRow = @pNmLvw.iItem
'                 
'                  nInc = Val(FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 1))
'                  sVBTerm = FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 2)
'                  sPBTerm = FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 3)
'                  sFileName = FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 5)
'                 
'                  'Then load this file in the proper tab/child window.
'                 
'                   
'                  'Call ife_GotoProcedure(nRow)
'                  'Call ife_CodeFinderHide()
'                End If
'         
'            Case %NM_DBLCLK
'                 
'          End Select
'      End Select
'   
'    'SYSCOMMAND msg
'    Case %WM_SYSCOMMAND
'   
' End Select


'THE FOLLOWING IS FROM POFFS, WHICH DOESN'T WORK EITHER, GRRRRRRRR!!! 
Select Case wMsg
    Case %WM_NOTIFY
      pNmHdr = lParam
     
      'NOTE: @pNmHdr.idFrom NEVER GETS SET TO IDC_IFE_LVUNDO, I'M BEGINNING
      'TO SUSPECT A CONSPIRACY HERE!!!
      Select Case @pNmHdr.idFrom
        Case IDC_IFE_LVUNDO
          'pNmLvw = lParam
         
          Select Case @pNmHdr.code
            Case %NM_CLICK                            'Click in listview?
              pNmLvw = lParam
                If @pNmLvw.iItem > -1 Then
                'If @pNmLvw.hdr.code = %NM_DBLCLK Then 'double click in listview
                  nRow = @pNmLvw.iItem

                  nInc = Val(FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 1))
                  sVBTerm = FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 2)
                  sPBTerm = FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 3)
                  sFileName = FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 5)
                 
                  'Then load this file in the proper tab/child window.

                  'Call ife_GotoProcedure(nRow)
                  'Call ife_CodeFinderHide()
                End If
         
            Case %NM_DBLCLK
                 
          End Select
      End Select
   
    'SYSCOMMAND msg
    Case %WM_SYSCOMMAND
   
End Select


'THE FOLLOWING IS CODE FROM ANOTHER PROJECT WHICH WORKS!!!
'
' Select Case wMsg
'   Case %WM_SETFOCUS
'     FF_Control_ShowState(HWND_DOC_EDIT, %SW_SHOW)
'   
'    Case %WM_VSCROLL
'        If FF_ListView_IsLineVisible(HWND_DOC_LV, glEDITROW) = %TRUE Then
'          FF_Control_ShowState(HWND_DOC_EDIT, %SW_SHOW)
'        End If
'   
'    Case %WM_NOTIFY
'      Select Case LoWrd(wParam)
'        Case IDC_FRMLISTVIEWEDIT_LVWEDIT
'          pNmLvw = lParam
'          Select Case @pNmLvw.hdr.code
'            Case %NM_CLICK                            'Click in listview?
'                If @pNmLvw.iItem > -1 Then
'                'If @pNmLvw.hdr.code = %NM_DBLCLK Then 'double click in listview
'                  glEDITROW  = @pNmLvw.iItem
'                Call SetTextBox(HWND_DOC_LV, HWND_DOC_EDIT, glEDITROW)
'                Call SetCaretToPos(HWND_DOC_EDIT, pt)
'                End If
'               
'            Case %NM_CUSTOMDRAW         'Draw column colors?
'              lpLvCd = lParam
'              Select Case @lplvcd.nmcd.dwDrawStage
'                Case %CDDS_PREPAINT, %CDDS_ITEMPREPAINT
'                   Function = %CDRF_NOTIFYSUBITEMDRAW
'                   
'                Case %CDDS_ITEMPREPAINT Or %CDDS_SUBITEM
'                  Select Case @lpLvCd.iSubItem
'                    Case 0      'Color for column 0 (not used)?
'                   
'                    Case 1      'Set Line Number column color?
'                      @lpLvCd.clrTextBk = IFE_LTYELLOW3
'                      @lpLvCd.clrText = IFE_BLACK
'                     
'                    Case 2      'Set Debug column color?
'                      @lpLvCd.clrTextBk = IFE_LTGREY2
'                      @lpLvCd.clrText = IFE_BLACK
'                   
'                    Case 3      'Set Text Edit column color?
'                      @lpLvCd.clrTextBk = IFE_WHITE
'                      @lpLvCd.clrText = IFE_BLACK
'                     
''                        If glDBGMODE = %FALSE Then
''                          @lpLvCd.clrTextBk = IFE_WHITE
''                          @lpLvCd.clrText = IFE_BLACK
''                        Else
''                            If @lpLvCd.nmcd.dwItemSpec = glEDITROW Then
''                              @lpLvCd.clrTextBk = IFE_ORANGE
''                              @lpLvCd.clrText = IFE_WHITE
''                            End If
''                              @lpLvCd.clrTextBk = IFE_WHITE
''                              @lpLvCd.clrText = IFE_BLACK
''                            Else
''                        End If
'                       
'                  End Select
'                       
'                  Function = %CDRF_NEWFONT
'              End Select
'          End Select
'      End Select
'   
'    'SYSCOMMAND msg
'    Case %WM_SYSCOMMAND
'      Select Case wParam
'        Case %SC_CLOSE
'    DeleteObject(hFont)
'    FF_CloseForm hWndForm, 1  'End Programm
'   
'      End Select
'   
''    'resize listview
''    Case %WM_SIZE
''      GetClientRect hWndForm, rectForm
''      MoveWindow HWND_DOC_LV, rectForm.nLeft, rectForm.nTop , rectForm.nRight, rectForm.nBottom, 1
'
' End Select

End Function


I certainly hope that FireFly 3 will have a series of common control "CLICK" events that will help prevent this kind of brain damage.  Normally 99.9% of the time I want to load data into a listview control (no problem) then select the data via a mouse pick (big problem).  I would LOVE to have a "CLICK" event I could call up that would reliably and consistently give me access to a ListView control and allow me to move forward in my prototyping efforts.  I've been fighting this issue now for four days

GRRRR!!!!!

JR

TechSupport

Hi JR,

I am not at my development computer to test but it looks like you have your code in the CUSTOM message handler for the ListView. You should be putting your code in the CUSTOM message handler for the Form. This is because notifications from child controls (i.e. the ListView) get sent to the control's parent (i.e. the Form).

Try moving your code to the Form's CUSTOM message handler. I expect you will have better (and less frustrating) results.  :)

TechSupport

BTW, I loved reading your comments in your source code.  I feel your frustration.  :) 


Chris Boss

JR,

You need to process the WM_NOTIFY message for the listview controls parent Form.

The notification message (in WM_NOTIFY) you need to test for is:

LVN_ITEMCHANGING

You then test the state of the item to see if it is:

LVIS_SELECTED


TechSupport

Using Chris' approach you could put your code in FireFly's message handler and deal with the NM_LISTVIEW pointer:


Function FORM1_LISTVIEW1_LVN_ITEMCHANGING ( _
                                          ControlIndex  As Long,            _  ' index in Control Array
                                          hWndForm      As Dword,           _  ' handle of Form
                                          hWndControl   As Dword,           _  ' handle of Control
                                          ByVal lpNMV   As NM_LISTVIEW Ptr  _  ' pointer to NM_LISTVIEW
                                          ) As Long


End Function


JR Heathcote

Paul and Chris,

Thanks for the help, your direction as usual was right on.

I did something similar to your suggestion Paul, but instead of using the  "IFE_LVUNDO_LVN_ITEMCHANGING" I used the "IFE_LVUNDO_LVN_ITEMCHANGED" event instead.  After all, I want to select the listview row and process after the user has made a decision and clicked on a specific row.  I suppose that there is very little real difference between the "...ITEMCHANGING" and the "...ITEMCHANGED" events in view of the limited way I coded the events, but it made much more sense to me to use the "...ITEMCHANGED" event.  And now that I'm at the AC (After Concussion) part of my life my doctor told me to exercise my brain, but avoid undue aggrevation, which is why I program in Windows.

Anyway, here is what I did, in case anyone else is interested.


'--------------------------------------------------------------------------
Function IFE_LVUNDO_LVN_ITEMCHANGED ( _
                                    ControlIndex  As Long,            _  ' index in Control Array
                                    hWndForm      As Dword,           _  ' handle of Form
                                    hWndControl   As Dword,           _  ' handle of Control
                                    ByVal lpNMV   As NM_LISTVIEW Ptr  _  ' pointer to NM_LISTVIEW
                                    ) As Long

  Local nRow      As Long
 
  Local nInc      As Long
  Local sVBTerm   As String
  Local sPBTerm   As String
  Local sFileName As String
  '------------------------------------------------------------------------
 
  Select Case @lpNMV.hdr.code
    Case %LVN_ITEMCHANGED
        If IsTrue @lpNMV.uChanged And %LVIS_SELECTED Then
          'Put your code here
          nRow = @lpNMV.iItem
         
          sVBTerm = FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 2)
          sPBTerm = FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 3)
          sFileName = FF_ListView_GetItemText(HWND_IFE_LVUNDO, nRow, 5)
         
          Call ife_UndoHide()
         
          'Reload the selected file from the UNDO list in the current tab
         
        End If
   
    Case Else
   
  End Select

End Function


Although I got the above to work with the help of Paul and Chris the official ListView 'CLICK' event would be much appreciated in FireFly 3.  The problem I have with all this Windows stuff is that I don't program it enough to be really proficient, just enough to be really, REALLY dangerous.  I know within a couple of months I will forget that I (with Paul and Chris) solved this little problem. 

The frustrating part for me when developing Windows apps is when one is in a prototyping mode the last thing I want to do is to stop that effort, switch hats to a developer/research mode, then try to figure out how to make a control work.  Only to switch hats again (after the usual epithets directed towards Microsoft) back to prototype mode.

JR

TechSupport

Hi JR,

Sorry, adding "click" events for ListViews will not happen. It goes against the design philosophy of FireFly. All of the "events" in FireFly correspond to actual Windows messages/notifications. In the long run, this makes it easier for the programmer to equate message handlers  in FireFly to Windows documentation. Creating 'click' events may be easier in the short run but it adds no learning benefit to the programmer when the programmer goes looking for help in the Windows world of documentation.

Having said all that, what would be more useful would be better, extensive, documentation that the programmer could refer to in FireFly that shows how to respond to 'clicks', etc... for a ListVoew, TreeView, etc... This is what FireFly 3 will do.

BTW, in your example you do not need the following code:


  Select Case @lpNMV.hdr.code
    Case %LVN_ITEMCHANGED


You are already in the LVN_ITEMCHANGED notification handler.  ;)

Chris Boss

JR,

It is best to use the existing notification messages provided by a common control, rather than do any subclassing to trap standard window messages. The Listview for example does some wierd stuff with capturing the mouse, so that the window messages don't always occur the way you would expect. That said, when you click an item in the Listview control the following notification messages will occur:

Mouse Button Down:

NM_RELEASEDCAPTURE
LVN_ITEMCHANGING           (state changes for item unselected)
LVN_ITEMCHANGED
LVN_ITEMCHANGING
LVN_ITEMCHANGED
LVN_ITEMCHANGING          (state changes for item selected)
LVN_ITEMCHANGED
NM_CUSTOMDRAW


Mouse Up:

NM_CLICK     (thats your mouse click event, but doesn't tell you what item was selected)

It should be noted, that along with the notification messages available for a specific window class (control) (ie. for listview LVN_ messages), there are a set of universal notifications available for all the Common controls that start with the prefix NM_ .

The reason I like to use LVN_ITEMCHANGING instead of LVN_CHANGED, is that it occurs just before the control finalizes the selection, so you can cancel it (you can prevent the item from being selected).

The best way to appreciate how a common control handles its notification messages, is to create a simple program with just the one control you are working with and then process the WM_NOTIFY message and display the notification messages in another window as they occur. If you can see them occur in real time, you can see what (and when) messages occurs.

Chris Boss

This may be helpful.

To see what notification messages occur and in what order, it is useful to have a debug window where you can display the all the WM_NOTIFY notification codes in real time.

Here is some DDT debug window code, which likely you can convert to SDK style code to produce your own debug window (I don't know if FireFly has such a feature built in):

http://www.powerbasic.com/support/pbforums/showthread.php?t=24463


JR Heathcote

Paul,

QuoteSorry, adding "click" events for ListViews will not happen.

Well I guess this is one of those instances where we will have to agree to disagree.  The main reason I would like to have a "CLICK" event centers on the fact a ListView CLICK is what I want to do, and that I, in all likelyhood, will never approach the expertise that you and Chris posses when it comes to solving Windows problems.  You guys can call up the esoteric aspects of Windows at will, I cannot do this, not that I wouldn't like to, but I just don't spend the time programming as you fellows do to become proficient at this endeavor.  As a result I spend many agonizing and frustrating hours (or even days) fighting through a relatively simple problem of knowing what I want to do, but not knowing how to do it.

Quite honestly, I would rather calculate tangent offsets to a clothoid spiral, that only requires calculus, as opposed to responding to ListView messages, that requires Windows.  Ugh!

QuoteHaving said all that, what would be more useful would be better, extensive, documentation that the programmer could refer to in FireFly that shows how to respond to 'clicks', etc... for a ListVoew, TreeView, etc...

I could go along with that possibly with the provision of accessing this knowledge base by allowing users to enter terms of what they want to do and letting the "How do I..." data base return "how" to do it.  I think this type of correlation needs to be made too.

QuoteThis is what FireFly 3 will do.

Which will be available for purchase next week, right?

Chris,

QuoteThe Listview for example does some wierd stuff with capturing the mouse, so that the window messages don't always occur the way you would expect.

This is exactly what I'm talking about.  How is the part time programmer to be expected to know these facts up front?  They don't.

QuoteHere is some DDT debug window code, which likely you can convert to SDK style code to produce your own debug window (I don't know if FireFly has such a feature built in):

I don't think FireFly does this, but I'm sure Paul could make the addition to FireFly to include this functionality (hint, hint).

Anyway, thanks again guys.

JR

Chris Boss

JR,

If your intent is to know whether a listview item been clicked or not, then the LVN_ITEMCHANGED or LVN_ITEMCHANGING notification is what you want. These notification messages occur when an item is clicked (and selected) and it will pass the index number of the item so you know which one it was.

Windows also generates the NM_CLICK notification message (through WM_NOTIFY) which is your equilivant to a CLICK event you are looking for and you can process that also, but it does not tell you which item was clicked.

If you want to do things differently, another technique would be to subclass the listview control and then process the WM_LBUTTONDOWN message, which would require subclassing the control. When WM_LBUTTONDOWN occurs, the LPARAM value is the mouse location in client coordinates ( X= LOWRD(LPARAM) , Y=HIWRD(LPARAM) ).

Using that coordinate, you can send the listview control the LVM_HITTEST message to get info about where the mouse click occured. It will tell you what item (index) it was and even what part of the item it was clicked on, including which column (subitem).

JR Heathcote

Chris,

Actually, allowing the user to select an item from the ListView control and then finding the row (I hate the term "item") selected is all I want to do, so it wouldn't be advantageous to me to subclass the various controls to do what I want through the "CLICK" event.  This particular ListView control is used for an Undo option and since it can take a destructive path I chose to use the %LVN_ITEMCHANGED rather than LVN_ITEMCHANGING for obvious reasons.

All I was trying to do in my rant is to point out some of the problems us part time programmers have with the inconsistencies of Windows and getting them to work in a consistent manner.  As Mr. Mattis says it's the artist not the paintbrush, which is true, but sometimes I feel like I'm going to be perpetually stuck in the "paint by numbers" section.  You know with all of the inconsistencies Windows has under the hood I'm amazed it works as well as it does, or even at all.

JR