Results 1 to 4 of 4

Thread: [VB6, Vista+] Undocumented ListView feature: Groups in Virtual Mode

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,625

    [VB6, Vista+] Undocumented ListView feature: Groups in Virtual Mode

    Name:  vgroups.jpg
Views: 534
Size:  61.2 KBName:  vgroups2.jpg
Views: 626
Size:  64.7 KB
    Undocumented ListView Features : Part 3 - Groups With Virtual Mode
    See Also: Part 1 - Footer Items | Part 2 - Subsetted Groups | Part 4 - Column Backcolor | Part 5 - Explorer-style selection


    Well, this project has been a long time coming. Just when I thought I had it, a mysterious and difficult-to-trace crash reared its head. But that last issue has finally been resolved.

    According to Microsoft, Group Mode can't be used when the ListView is in Virtual Mode-- when LVS_OWNERDATA is set. But they have confused 'can't be done' with 'undocumented and unsupported'. Through the use of undocumented interfaces, IListView and IOwnerDataCallback, I have brought grouping while in virtual mode to VB6 as a port of some excellent work by Timo Kunze. You can also thank LucasMKG here since if he hadn't kept on me to work on this and finally got me looking at OnCacheHint, this project might never have been completed.

    How It Works

    -A class module must implement the IOwnerDataCallback interface
    -Then, the reference is set like this:
    Set cLVODC = New cLVOwnerDataCB
    Dim pILV As IListView
    Call SendMessage(hLVVG, LVM_QUERYINTERFACE, VarPtr(IID_IListView), pILV)
    pILV.SetOwnerDataCallback cLVODC

    -After that, it's just a matter of creating the ListView.

    Project Notes
    -This project fully supports Unicode by responding to WM_NOTIFYFORMAT with NFR_UNICODE and responding to LVN_GETDISPINFOW. Currently I'm experiencing a problem with StrPtr that corrupts subitem text; I haven't had this problem before (as in, it was fine the other day with this same unchanged code) and it shouldn't effect anyone else, but if for some reason it does let me know; but when compiled the problem goes away.
    -This project is mainly a proof of concept; information about the groups is hard-coded. In a real project you'll need to be careful to keep item numbers and information updated, including the .cItems LVGROUP member (which isn't read-only, it must be set), and to return the correct group information in cLVOwnerDataCB--- see below.
    -You could probably apply this method to a Comctllib (5) ListView, but this project makes its own with CreateWindowEx.

    Requirements
    -Windows 7 or higher (for Vista, the typelib needs to be recompiled with a different IID for IListView; if anyone wants this let me know)
    -lvundoc.tlb - Must be added as a reference. If you have a previous version, make sure to replace it with the one in this download.
    -Common Controls 6.0 Manifest - Your project (and the IDE if you want to see groups) needs a manifest specifying 6.0 controls; see here. The demo project has a manifest built in.

    Setting Group Callback Information
    The demo project now handles this better, but still doesn't account for all scenarios. Refer to the following information from Timo's project:
    /// \brief <em>Will be called to retrieve an item's zero-based control-wide index</em>
    ///
    /// This method is called by the listview control to retrieve an item's zero-based control-wide index.
    /// The item is identified by a zero-based group index, which identifies the listview group in which
    /// the item is displayed, and a zero-based group-wide item index, which identifies the item within its
    /// group.
    ///
    /// \param[in] groupIndex The zero-based index of the listview group containing the item.
    /// \param[in] groupWideItemIndex The item's zero-based group-wide index within the listview group
    /// specified by \c groupIndex.
    /// \param[out] pTotalItemIndex Receives the item's zero-based control-wide index.
    ///
    /// \return An \c HRESULT error code.
    virtual HRESULT STDMETHODCALLTYPE GetItemInGroup(int groupIndex, int groupWideItemIndex, PINT pTotalItemIndex) = 0;
    /// \brief <em>Will be called to retrieve the group containing a specific occurrence of an item</em>
    ///
    /// This method is called by the listview control to retrieve the listview group in which the specified
    /// occurrence of the specified item is displayed.
    ///
    /// \param[in] itemIndex The item's zero-based (control-wide) index.
    /// \param[in] occurrenceIndex The zero-based index of the item's copy for which the group membership is
    /// retrieved.
    /// \param[out] pGroupIndex Receives the zero-based index of the listview group that shall contain the
    /// specified copy of the specified item.
    ///
    /// \return An \c HRESULT error code.
    virtual HRESULT STDMETHODCALLTYPE GetItemGroup(int itemIndex, int occurenceIndex, PINT pGroupIndex) = 0;
    /// \brief <em>Will be called to determine how often an item occurs in the listview control</em>
    ///
    /// This method is called by the listview control to determine how often the specified item occurs in the
    /// listview control.
    ///
    /// \param[in] itemIndex The item's zero-based (control-wide) index.
    /// \param[out] pOccurrencesCount Receives the number of occurrences of the item in the listview control.
    ///
    /// \return An \c HRESULT error code.
    virtual HRESULT STDMETHODCALLTYPE GetItemGroupCount(int itemIndex, PINT pOccurenceCount) = 0;
    PROJECT UPDATED
    VirtualGroupsDemoV2 updated with:
    -No longer fixed to a particular number of items, groups, or items per group... provides an VLItems array to hold information for each listitem, and a VLGroups array to hold information for groups (this is automatically set with the SetGroupsData, which should be called when VLItems changes)
    -Added group header icons and group links (and how to respond to a link click)
    -The link click is used to demonstrate how to append a new item
    -A number of other refinements that eliminate hardcoded numbers

    NOTE (15 May 2018): All of lvundoc.tlb (and lvfooter) are included in oleexp.tlb as of version 4.42. So if your project has that version or higher, you don't need the TLBs in this thread. The types have been standardized however so the code needs to be updated to run with that (i.e. LGUID now is the standard UUID instead, LPROPKEY is PROPERTYKEY, and the 'tag' has been dropped from the ListView types). The switch is optional; I'm still going to leave the standalone TLBs here.
    Attached Files Attached Files
    Last edited by fafalone; Jan 25th, 2022 at 01:24 AM. Reason: Added link for Part 5

  2. #2

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,625

    Re: [VB6, Vista+] Undocumented ListView feature: Groups in Virtual Mode

    Other Thoughts
    I may or may not update the demo project with these since they're related to virtual lists in general and not groups (but they work with groups enabled), but did want to explain and provide code since I haven't seen this done in VB (on VIRTUAL ListViews, Jonney )..
    Edit: For now.. I don't want to overcomplicate the basic demo, but OTOH this is really complex enough that it needs a demo project, so I'm attaching it as a separate, advanced version.

    Overlays and Checkboxes
    If you're not familiar with overlays, think of the little shortcut arrow on links in Explorer, or the one that's on shared folders. The demo project uses the system image list, so it's those that would appear if you used this code with the demo.

    So while it requires a few extra steps, it's entirely possible to have both overlays and checkboxes in your virtual grouped listview.

    First, enable flags and new data members are added to keep track of the values for each item:
    Code:
    Public bUseChecks As Boolean
    Public bUseOverlays As Boolean
    
    Public Type VListItem
        sText As String
        sSubItems() As String
        iImage As Long
        iSubItemImages() As Long 'LVS_EX_SUBITEMIMAGES must be enabled, then must dim same as sSubItems
        iGrp As Long
        iPos As Long
        iStateImageIndex As Long
        iOverlayIndex As Long
    End Type
    Then we'll change the startup routine to enable them and fill in some random values to test it all out:
    Code:
    Private Sub Form_Load()
    Dim i As Long
    ReDim VLItems(99)
    bUseOverlays = True
    bUseChecks = True
    For i = 0 To 30
        VLItems(i).sText = "Item " & CStr(i)
        ReDim VLItems(i).sSubItems(2)
        VLItems(i).sSubItems(0) = "SubItem 1," & CStr(i)
        VLItems(i).sSubItems(1) = "SubItem 2," & CStr(i)
        VLItems(i).sSubItems(2) = "SubItem 3," & CStr(i)
        VLItems(i).iGrp = 0
        VLItems(i).iImage = CLng(Right(CStr(i), 1))
        If bUseOverlays And ((i Mod 3) = 0) Then
            VLItems(i).iOverlayIndex = 1
        End If
        If bUseChecks Then
            'the checked items are in the 3rd group; but when checks are
            'enabled, a state index of 0 means no box at all. for an
            'unchecked box, it must be set to 1 (and 2 for checked)
            'the reason it's not true or false is that you can set your
            'own state image list with however many states you want, e.g.
            'unchecked, checked; grayed,filled-- which aren't built in
            VLItems(i).iStateImageIndex = 1
        End If
    Next
    For i = 31 To 70
        VLItems(i).sText = "Item " & CStr(i)
        ReDim VLItems(i).sSubItems(2)
        VLItems(i).sSubItems(0) = "SubItem 1," & CStr(i)
        VLItems(i).sSubItems(1) = "SubItem 2," & CStr(i)
        VLItems(i).sSubItems(2) = "SubItem 3," & CStr(i)
        VLItems(i).iGrp = 1
        VLItems(i).iImage = CLng(Right(CStr(i), 1))
        If bUseOverlays And ((i Mod 3) = 0) Then
            VLItems(i).iOverlayIndex = 2
        End If
        If bUseChecks Then
            VLItems(i).iStateImageIndex = 1
        End If
    Next
    For i = 71 To 99
        VLItems(i).sText = "Item " & CStr(i)
        ReDim VLItems(i).sSubItems(2)
        VLItems(i).sSubItems(0) = "SubItem 1," & CStr(i)
        VLItems(i).sSubItems(1) = "SubItem 2," & CStr(i)
        VLItems(i).sSubItems(2) = "SubItem 3," & CStr(i)
        VLItems(i).iGrp = 2
        VLItems(i).iImage = CLng(Right(CStr(i), 1))
        If bUseChecks Then
            If (i Mod 2) = 0 Then
                VLItems(i).iStateImageIndex = 1
            Else
                VLItems(i).iStateImageIndex = 2
            End If
        End If
    Next
    lGroupCount = 2 'must correspond to number of groups you add; zero based, 2 means 3 groups
    SetGroupData lGroupCount
    Subclass2 Me.hWnd, AddressOf FGVWndProc
    InitListView
    
    End Sub
    Now let's look at the back-end code that makes it all work.
    First, right after CreateWindowEx in Form1.CreateListView, we need to set a callback mask- this tells the LV to ask us for the data in LVN_GETDISPINFO. It doesn't do this by default because non-virtual lists keep track of this internally, but can be set to ask for it as a callback as well.
    Code:
       Dim dwCallback As LVITEM_state
       If bUseChecks Then
         dwCallback = LVIS_STATEIMAGEMASK
       End If
       If bUseOverlays Then
         dwCallback = dwCallback Or LVIS_OVERLAYMASK
       End If
       If dwCallback Then Call ListView_SetCallbackMask(hwndLV, dwCallback)
    Checkboxes require a style change as well, and we might as well set it at the same time we're setting an extended style already for the Explorer theme. In InitListView:
    Code:
        Dim lvsex As LVStylesEx
        lvsex = LVS_EX_DOUBLEBUFFER
        If bUseChecks Then
            lvsex = lvsex Or LVS_EX_CHECKBOXES
        End If
    Call ListView_SetExtendedStyle(hLVVG, lvsex)
    All that's left to do now is respond with the data when asked in LVN_GETDISPINFO. In DoGVNotify, as a 3rd block after LVIF_TEXT and LVIF_IMAGE:
    Code:
                        If (.mask And LVIF_STATE) Then
                            If bUseChecks Then
                                .StateMask = LVIS_STATEIMAGEMASK
                                .State = INDEXTOSTATEIMAGEMASK(VLItems(.iItem).iStateImageIndex)
                            End If
                            If bUseOverlays Then
                                .StateMask = .StateMask Or LVIS_OVERLAYMASK
                                .State = .State Or INDEXTOOVERLAYMASK(VLItems(.iItem).iOverlayIndex)
                            End If
                        End If
    One final issue- the check state doesn't change automatically; you'll have to handle the click and change the value in your items array: it doesn't repaint on its own, and when it does repaint it will just get the value from your items array. Add another notification code to handle in DoGVNotify:
    Code:
                Case LVN_ITEMCHANGED
                    Dim NMLVW As NMLISTVIEW
                    CopyMemory NMLVW, ByVal lParam, Len(NMLVW)
                    If NMLVW.uChanged = LVIF_STATE Then
                        If (NMLVW.uNewState And LVIS_STATEIMAGEMASK) = &H1000 Then
                            'item unchecked
                            VLItems(NMLVW.iItem).iStateImageIndex = 1
                            RedrawWindow hWnd, 0, 0, RDW_UPDATENOW Or RDW_INVALIDATE Or RDW_ERASE Or RDW_ALLCHILDREN
                        ElseIf (NMLVW.uNewState And LVIS_STATEIMAGEMASK) = &H2000 Then
                            'item checked
                            VLItems(NMLVW.iItem).iStateImageIndex = 2
                            RedrawWindow hWnd, 0, 0, RDW_UPDATENOW Or RDW_INVALIDATE Or RDW_ERASE Or RDW_ALLCHILDREN
                        End If
                    End If

    And you're good to go. You can use either one, both, or none- just change the bUse... variables on start and make sure the data is filled in if enabled (overlays can be left at zero, but if using checkboxes, unchecked items must be 1 instead of 0-- see note in code).

    Edit: For now.. I don't want to overcomplicate the basic demo, but OTOH this is really complex enough that it needs a demo project, so I'm attaching it as a separate, advanced version.
    Attached Files Attached Files
    Last edited by fafalone; Nov 15th, 2015 at 01:26 AM.

  3. #3
    Frenzied Member
    Join Date
    Jan 2010
    Posts
    1,103

    Re: [VB6, Vista+] Undocumented ListView feature: Groups in Virtual Mode

    On leandroascierto website, there're a project called ucListViewEx_3beta which have implemented similar features via APIs in year 2011.

  4. #4

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,625

    Re: [VB6, Vista+] Undocumented ListView feature: Groups in Virtual Mode

    I'm familiar with that control. It does not use virtual mode. This isn't about groups, there's quite a few controls that do groups, this is about groups in virtual mode (LVS_OWNERDATA).

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width