Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fafalone/ListViewSubItemControls

Repository files navigation

ListViewSubItemControls v1.1

Undocumented ListView SubItem Controls Demo

ScreenShot

This 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, Subsetted Groups, Groups in Virtual Mode, Column Backcolors, and Explorer-style selection. 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, 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...

πŸ₯³πŸ₯³ ** IT WORKED ** πŸ₯³πŸ₯³

I'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.

Requirements

  • Windows 7+ (Vista+ could be supported by switching the IListView version, but it's not done here in v1.0).
  • Windows Development Library for twinBASIC v9.1+
  • Common Controls 6.0 enabled by manifest

Updates

v1.1 (22 Jun 2025) Now supports toggling to Tiles view to show how they work great there too: image

How it works

This technique is based around the undocumented ISubItemCallback interface:

[InterfaceId("11A66240-5489-42C2-AEBF-286FC831524C")]
[OleAutomation(False)]
Interface ISubItemCallback Extends stdole.IUnknown
 Sub GetSubItemTitle(ByVal subitemIndex As Long, ByVal lpszBuffer As LongPtr, ByVal BufferSize As Long)
 Sub GetSubItemControl(ByVal itemIndex As Long, ByVal subItemIndex As Long, requiredInterface As UUID, ppObject As Any)
 Sub BeginSubItemEdit(ByVal itemIndex As Long, ByVal subItemIndex As Long, ByVal mode As Long, requiredInterface As UUID, ppObject As Any)
 Sub EndSubItemEdit(ByVal itemIndex As Long, ByVal subItemIndex As Long, ByVal mode As Long, ByVal ppc As IPropertyControl)
 Sub BeginGroupEdit(ByVal groupIndex As Long, requiredInterface As UUID, ppObject As Any)
 Sub EndGroupEdit(ByVal groupIndex As Long, ByVal mode As Long, ByVal pPropertyControl As IPropertyControl)
 Sub OnInvokeVerb(ByVal itemIndex As Long, ByVal pVerb As LongPtr)
End Interface

We 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.

The 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.

 Select Case itemIndex
 Case 0
 hr = CoCreateInstance(CLSID_CInPlaceMLEditBoxControl, Nothing, CLSCTX_INPROC_SERVER, requiredInterface, ppObject)
 If pPropertyDescription Is Nothing Then
 PSGetPropertyDescriptionByName("System.Generic.String", IID_IPropertyDescription, pPropertyDescription)
 End If
 Case 1
 hr = CoCreateInstance(CLSID_CCustomDrawPercentFullControl, Nothing, CLSCTX_INPROC_SERVER, requiredInterface, ppObject)
 Case 2
 hr = CoCreateInstance(CLSID_CRatingControl, Nothing, CLSCTX_INPROC_SERVER, requiredInterface, ppObject)
 Case 3
 If IsEqualIID(requiredInterface, IID_IDrawPropertyControl) Then
 hr = CoCreateInstance(CLSID_CStaticPropertyControl, Nothing, CLSCTX_INPROC_SERVER, requiredInterface, ppObject)
 Else
 hr = CoCreateInstance(CLSID_CInPlaceEditBoxControl, Nothing, CLSCTX_INPROC_SERVER, requiredInterface, ppObject)
 End If
 If pPropertyDescription Is Nothing Then
 PSGetPropertyDescriptionByName("System.Generic.String", IID_IPropertyDescription, pPropertyDescription)
 End If

etc.

After 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.

 Dim pBuffer As LongPtr = HeapAlloc(GetProcessHeap(), 0, (1024 + 1) * 2 /* sizeof(WCHAR) */)
 If pBuffer Then
 Dim item As LVITEMW
 item.iSubItem = subItemIndex
 item.cchTextMax = 1024
 item.pszText = pBuffer
 SendMessage(hLV, LVM_GETITEMTEXTW, itemIndex, item)
 If (itemIndex = 1) Or (itemIndex = 2) Or (itemIndex = 7) Then
 Dim tmp As Variant
 PropVariantInit(tmp)
 InitPropVariantFromString(item.pszText, tmp)
 PropVariantChangeType(ByVal pPropertyValue, tmp, 0, VT_UI4)
 PropVariantClear(tmp)
...
 Dim pPropertyValueObj As IPropertyValue
 Set pPropertyValueObj = New IPropertyValueImpl
 pPropertyValueObj.InitValue(propertyValue)
 If pPropertyDescription IsNot Nothing Then
 pControl.Initialize(pPropertyDescription, 0)
 End If
 pControl.SetValue(pPropertyValueObj)
 pControl.SetTextColor(textColor)
 If hFont Then
 pControl.SetFont(hFont)
 End If

etc.

When 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:

 Private Sub ISubItemCallback_EndSubItemEdit(ByVal itemIndex As Long, ByVal subItemIndex As Long, ByVal mode As Long, ByVal ppc As IPropertyControl) Implements ISubItemCallback.EndSubItemEdit
...
 Dim modified As BOOL
 ppc.IsModified(modified)
 If modified Then
 Dim pPropertyValue As IPropertyValue
 ppc.GetValue(IID_IPropertyValue, pPropertyValue)
 If SUCCEEDED(Err.LastHresult) Then
 Dim propertyValue As PROPVARIANT
 PropVariantInit(propertyValue)
 pPropertyValue.GetValue(propertyValue)
 If SUCCEEDED(Err.LastHresult) Then
 Dim pBuffer As LongPtr
 If SUCCEEDED(PropVariantToStringAlloc(propertyValue, pBuffer)) AndAlso (pBuffer <> 0) Then
 LVSetItemText(itemIndex, subItemIndex, pBuffer)
 CoTaskMemFree(pBuffer)
 End If
 PropVariantClear(propertyValue)
 End If
 End If
 End If

So 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. πŸ˜„

Releases

No releases published

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /