{"id":28961816,"url":"https://github.com/fafalone/listviewsubitemcontrols","last_synced_at":"2025-09-06T14:45:12.038Z","repository":{"id":300683391,"uuid":"1006792733","full_name":"fafalone/ListViewSubItemControls","owner":"fafalone","description":"Undocumented ListView SubItem Controls Demo","archived":false,"fork":false,"pushed_at":"2025-06-23T04:02:45.000Z","size":10742,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-23T04:22:01.504Z","etag":null,"topics":["comctl32","common-controls","listview","listview-customization","twinbasic","undocumented-api","vb6","windows"],"latest_commit_sha":null,"homepage":"","language":"Visual Basic 6.0","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fafalone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-23T02:07:49.000Z","updated_at":"2025-06-23T04:02:48.000Z","dependencies_parsed_at":"2025-06-23T04:32:31.952Z","dependency_job_id":null,"html_url":"https://github.com/fafalone/ListViewSubItemControls","commit_stats":null,"previous_names":["fafalone/listviewsubitemcontrols"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/fafalone/ListViewSubItemControls","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fafalone%2FListViewSubItemControls","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fafalone%2FListViewSubItemControls/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fafalone%2FListViewSubItemControls/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fafalone%2FListViewSubItemControls/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fafalone","download_url":"https://codeload.github.com/fafalone/ListViewSubItemControls/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fafalone%2FListViewSubItemControls/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261589933,"owners_count":23181438,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["comctl32","common-controls","listview","listview-customization","twinbasic","undocumented-api","vb6","windows"],"created_at":"2025-06-24T02:05:33.489Z","updated_at":"2025-06-24T02:05:34.212Z","avatar_url":"https://github.com/fafalone.png","language":"Visual Basic 6.0","readme":"# ListViewSubItemControls v1.1\nUndocumented ListView SubItem Controls Demo\n\n![ScreenShot](https://github.com/user-attachments/assets/f3cf5881-e693-4367-a99e-0a3c702e30b6)\n\nThis project has been my white whale. Back in 2015 I started a series of articles on undocumented ListView features available in Windows Vista+: [Footer Items](https://www.vbforums.com/showthread.php?798159-VB6-Vista-Undocumented-ListView-feature-Footer-items), [Subsetted Groups](https://www.vbforums.com/showthread.php?798321-VB6-Vista-Undocumented-ListView-feature-Subsetted-Groups-(simple-no-TLB)), [Groups in Virtual Mode](https://www.vbforums.com/showthread.php?808981-VB6-Vista-Undocumented-ListView-feature-Groups-in-Virtual-Mode), [Column Backcolors](https://www.vbforums.com/showthread.php?869049-VB6-Undocumented-ListView-feature-Highlight-column), and [Explorer-style selection](https://www.vbforums.com/showthread.php?894841-VB6-Win7-Undocumented-ListView-Feature-Multiselect-in-first-column-like-Explorer). But the coolest undocumented feature of all was the automatic subitem controls shown in the picture above. I just could not get it. The work was based on a fully working [project by Timo Kunze](https://www.codeproject.com/Articles/35197/Undocumented-List-View-Features), but even though I had this C++ sample that worked, every effort to port it to VB6 failed. Weeks were spent on it. Then at least half a dozen major efforts over the following decade of a few days. Not willing to give it up, I tried again starting 2 days ago, only this time instead of trying to fix the giant mess of spaghetti code packed with debugging stuff and the remnants of numerous different approaches, I started over completely from scratch and tried to make the port as line-by-line identical as possible...\n\n🥳🥳 ** IT WORKED ** 🥳🥳\n\nI'll no doubt be digging into the old code to find out exactly what I could have possibly missed in all the other failed attempts, which only ever got as far as glitched rendering of one or two controls followed by a hard crash. But the bottom line is now every control is working perfectly! In both 32 and 64bit! In future versions I'll explore the control types not used in Timo's demo.\n\n**Requirements**\n\n- Windows 7+ (Vista+ could be supported by switching the IListView version, but it's not done here in v1.0).\n- Windows Development Library for twinBASIC v9.1+\n- Common Controls 6.0 enabled by manifest\n\n**Updates**\n\nv1.1 (22 Jun 2025)\nNow supports toggling to Tiles view to show how they work great there too:\n![image](https://github.com/user-attachments/assets/4610bd77-b391-4e76-a4e6-dc4342f587b3)\n\n\n**How it works**\n\nThis technique is based around the undocumented `ISubItemCallback` interface:\n\n```vba\n[InterfaceId(\"11A66240-5489-42C2-AEBF-286FC831524C\")]\n[OleAutomation(False)]\nInterface ISubItemCallback Extends stdole.IUnknown\n    Sub GetSubItemTitle(ByVal subitemIndex As Long, ByVal lpszBuffer As LongPtr, ByVal BufferSize As Long)\n    Sub GetSubItemControl(ByVal itemIndex As Long, ByVal subItemIndex As Long, requiredInterface As UUID, ppObject As Any)\n    Sub BeginSubItemEdit(ByVal itemIndex As Long, ByVal subItemIndex As Long, ByVal mode As Long, requiredInterface As UUID, ppObject As Any)\n    Sub EndSubItemEdit(ByVal itemIndex As Long, ByVal subItemIndex As Long, ByVal mode As Long, ByVal ppc As IPropertyControl)\n    Sub BeginGroupEdit(ByVal groupIndex As Long, requiredInterface As UUID, ppObject As Any)\n    Sub EndGroupEdit(ByVal groupIndex As Long, ByVal mode As Long, ByVal pPropertyControl As IPropertyControl)\n    Sub OnInvokeVerb(ByVal itemIndex As Long, ByVal pVerb As LongPtr)\nEnd Interface\n```\n\nWe implement in our Form then set it as the callback object via `IListView`'s `SetSubItemCallback` method. The initial values of the controls we just set by the subitem text of the listview items, e.g. 46 for 46% on the percent bar, or the numeric value of a `FILETIME` for the Date/Time control. The control in the picture that says 'Center weighted average' is actually a EXIF property the shell displays for photos... it automatically populates the values just by giving it an `IPropertyDescription` for System.Photo.MeteringMode, and we just provide an index.\n\nThe `BeginSubItemEdit` and identically handled `GetSubItemControl` callback methods are where the interesting part happens. This is a difficult interface to use with `Implements` because these take `void**` (`As Any`) arguments. twinBASIC lets us implement these methods as-is by using `ByRef LongPtr`; the key is we use `CoCreateInstance` to create an object for these controls on the argument, only when the subitemIndex is 1.\n\n```vba\n        Select Case itemIndex\n            Case 0\n                hr = CoCreateInstance(CLSID_CInPlaceMLEditBoxControl, Nothing, CLSCTX_INPROC_SERVER, requiredInterface, ppObject)\n                If pPropertyDescription Is Nothing Then\n                    PSGetPropertyDescriptionByName(\"System.Generic.String\", IID_IPropertyDescription, pPropertyDescription)\n                End If\n            Case 1\n                hr = CoCreateInstance(CLSID_CCustomDrawPercentFullControl, Nothing, CLSCTX_INPROC_SERVER, requiredInterface, ppObject)\n            Case 2\n                hr = CoCreateInstance(CLSID_CRatingControl, Nothing, CLSCTX_INPROC_SERVER, requiredInterface, ppObject)\n            Case 3\n                If IsEqualIID(requiredInterface, IID_IDrawPropertyControl) Then\n                    hr = CoCreateInstance(CLSID_CStaticPropertyControl, Nothing, CLSCTX_INPROC_SERVER, requiredInterface, ppObject)\n                Else\n                    hr = CoCreateInstance(CLSID_CInPlaceEditBoxControl, Nothing, CLSCTX_INPROC_SERVER, requiredInterface, ppObject)\n                End If\n                If pPropertyDescription Is Nothing Then\n                    PSGetPropertyDescriptionByName(\"System.Generic.String\", IID_IPropertyDescription, pPropertyDescription)\n                End If\n\n```\n\netc.\n\nAfter that there's some voodoo regarding visual styles I don't fully understand; I think it's just getting the strings for the window themes from the `GetProp` accessible locations they're stored in; but Timo doesn't explain why we don't just pass \"explorer\" like the main ListView control. Finally, and this is where I think earlier efforts went wrong, is the method for storing and editing values with `IPropertyValue`. I didn't want tB doing anything behind my back, so almost everything there now uses a manually defined UDT version of `PROPVARIANT`. We have a class that implements it, and we populate it with the text values of the subitem coerced into their proper `PROPVARIANT` data type; `VT_LPWSTR`   types are an epic pain and this time around I think battle-tested helpers I had for it made a difference.\n\n```vba\n            Dim pBuffer As LongPtr = HeapAlloc(GetProcessHeap(), 0, (1024 + 1) * 2 /* sizeof(WCHAR) */)\n            If pBuffer Then\n                Dim item As LVITEMW\n                item.iSubItem = subItemIndex\n                item.cchTextMax = 1024\n                item.pszText = pBuffer\n                SendMessage(hLV, LVM_GETITEMTEXTW, itemIndex, item)\n                If (itemIndex = 1) Or (itemIndex = 2) Or (itemIndex = 7) Then\n                    Dim tmp As Variant\n                    PropVariantInit(tmp)\n                    InitPropVariantFromString(item.pszText, tmp)\n                    PropVariantChangeType(ByVal pPropertyValue, tmp, 0, VT_UI4)\n                    PropVariantClear(tmp)\n...\n            Dim pPropertyValueObj As IPropertyValue\n            Set pPropertyValueObj = New IPropertyValueImpl\n            pPropertyValueObj.InitValue(propertyValue)\n            If pPropertyDescription IsNot Nothing Then\n                pControl.Initialize(pPropertyDescription, 0)\n            End If\n            pControl.SetValue(pPropertyValueObj)\n            pControl.SetTextColor(textColor)\n            If hFont Then\n                pControl.SetFont(hFont)\n            End If\n```\n\netc.\n\nWhen the values are changed through the control, it sends the `EndSubItemEdit` and we take the `IPropertyValue` interface and turn it back into a String we store as the item text:\n\n```vba\n    Private Sub ISubItemCallback_EndSubItemEdit(ByVal itemIndex As Long, ByVal subItemIndex As Long, ByVal mode As Long, ByVal ppc As IPropertyControl) Implements ISubItemCallback.EndSubItemEdit\n...\n        Dim modified As BOOL\n        ppc.IsModified(modified)\n        If modified Then\n            Dim pPropertyValue As IPropertyValue\n            ppc.GetValue(IID_IPropertyValue, pPropertyValue)\n            If SUCCEEDED(Err.LastHresult) Then\n                Dim propertyValue As PROPVARIANT\n                PropVariantInit(propertyValue)\n                pPropertyValue.GetValue(propertyValue)\n                If SUCCEEDED(Err.LastHresult) Then\n                    Dim pBuffer As LongPtr\n                    If SUCCEEDED(PropVariantToStringAlloc(propertyValue, pBuffer)) AndAlso (pBuffer \u003c\u003e 0) Then\n                        LVSetItemText(itemIndex, subItemIndex, pBuffer)\n                        CoTaskMemFree(pBuffer)\n                    End If\n                    PropVariantClear(propertyValue)\n                End If\n            End If\n        End If\n```\n\nSo that's the broad strokes. There's obviously a ton of details I've left out, so grab the code and dig in! Later this summer I hope to bring these controls to ucShellBrowse, as they're far more stable than my current method of creating a bunch of new windows and drawing the stars myself in `WM_PAINT` handlers. 😄\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffafalone%2Flistviewsubitemcontrols","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffafalone%2Flistviewsubitemcontrols","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffafalone%2Flistviewsubitemcontrols/lists"}