This project was updated on 26 Feb 2016. There's several bits of additional information throughout this post, then see the bottom of this post for details of the new demo project.
Ran across this nifty thing on codeproject, and successfully got it working in VB.
Tested and working with 5.0 ListView and API ListView (it will also work on krool's Common Control Replacement ListView), have not tried with 6.0 ListView and presumably it wouldn't work (Windows Common Controls 5.0 is actually the more modern control due to linkage with the real comctl32.dll, and required for a lot of modern features like this and group view). The items are present and displayed the same way in all views, including tile and group view modes.
This one is a little complicated to set up, but straightforward to use. First, it requires a type library with the undocumented interfaces IListViewFooter and IListViewFooterCallback, then the latter has to be implemented by a class module. From there, more undocumented goodness: LVM_SETIMAGELIST with a wParam of 4 will set the icons used in the footer, and LVM_QUERYINTERFACE retrieves an instance of IListViewFooter.
For the purposes of this code, I'll assume you have a ListView set up already. I use the system imagelist, but you can assign any imagelist (well, api imagelist.. also note that while technically you can use icons larger than 16x16, the focus/highlight rectangle and text will not adjust so it won't look good):
Code:
Public Const IID_IListViewFooter = "{F0034DA8-8A22-4151-8F16-2EBA76565BCC}"
Public Const LVM_QUERYINTERFACE = (LVM_FIRST + 189)
Public Declare Function CLSIDFromString Lib "ole32" (ByVal lpszGuid As Long, pGuid As Any) As Long
Public Type GUIDA
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Public m_himlSysSmall As Long
Public Function GetFileTypeIconIndex(ext As String) As Long
Dim sfi As SHFILEINFO
Dim pidl As Long
If SHGetFileInfo(ext, FILE_ATTRIBUTE_NORMAL, sfi, Len(sfi), SHGFI_SYSICONINDEX Or SHGFI_SMALLICON Or SHGFI_USEFILEATTRIBUTES) Then
GetFileTypeIconIndex = sfi.iIcon
End If
End Function
The code to insert items can be placed wherever, but it won't show until there's items in the ListView.
Code:
m_himlSysSmall = GetSystemImagelist(SHGFI_SMALLICON)
Call SendMessage(ListView1.hWnd, LVM_SETIMAGELIST, 4, ByVal m_himlSysSmall)
Dim pLVF As IListViewFooter
Dim pFtrCB As cLVFooterCallback
Set pFtrCB = New cLVFooterCallback
Dim iidLVF As GUIDA
Call CLSIDFromString(StrPtr(IID_IListViewFooter), iidLVF)
Call SendMessage(hLVS, LVM_QUERYINTERFACE, VarPtr(iidLVF), pLVF)
If (pLVF Is Nothing) Then
Debug.Print "Failed to get LV Footer interface"
Exit Sub
End If
Dim lFtrIco As Long
lFtrIco = GetFileTypeIconIndex(".jpg") 'just an example, it's a standard index for the assigned image list.
With pLVF
.SetIntroText "Intro text - hello!"
.InsertButton 0, "Test Item 1", "Alt", lFtrIco, 2000
.Show pFtrCB
End With
'2000' - the lParam - has no special meaning, you can store whatever Long you want there. NOTE: It must not be 0, otherwise the buttonclick/buttondelete callback events won't fire. UPDATE: I finally figured out what that second string argument for .InsertButton is: an alternate text that is displayed when there's not enough room for the first string. It will only show up if it has the same or fewer number of characters.
The attached ZIP contains the typelib, the typelib source code, a batch file to compile it from a standard VS6 install, and the class module implementing the callback. I didn't bother will a full fledged example because presumably anyone interested in this would be adding it onto an already well set-up ListView, but if really needed let me know.
Coming up next in the world of undocumented ListView: subsetted groups (link for "Display all x items"), subitem label editing, and if I'm particularly ambitious.. apparently you can use groups in full virtual mode.
2015-11-20 UPDATE: It turns out you can indeed have more than 4 items. You can't specify a position >3, but you can insert at 0-3 again and the new button is inserted at that position. So for example you if you always use 0 for InsertAt, just add as many buttons as you want in reverse order (add the button you want first last).
A couple practical limits tho; there's no way to get a second row, and >4 results in squeezing to fit the listview width (but icon will always show).
2016-02-26 UPDATE: I made a demo project for another user in another thread, and thought I would add it here as well. It shows how you can include the IListViewFooterCallback interface inside an existing UserControl. The UserControl in the project contains a 5.0 ListView that shows the adding of footer items and how to assign the system imagelist to those items. It also uses SetWindowTheme to get the light-blue item highlighting like in the first two screenshots at the top; without the theme it looks like the 3rd.
footertest.zip includes both the new demo project and an updated TLB (lvundoc.tlb - a single typelib for all the undocumented listview projects, source included).
lvfooter.zip is the original attachment to this thread, and includes just the simple TLB for only footer items, the source of that, and a sample class module for IListViewFooterCallback
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.
Last edited by fafalone; Jan 25th, 2022 at 01:20 AM.
Reason: Added link to Part 5
For reference.. these are in the zip but I thought I'd put them up here too.
Interface def:
Code:
[
odl,
uuid(88EB9442-913B-4AB4-A741-DD99DCB7558B)
]
interface IListViewFooterCallback : stdole.IUnknown
{
HRESULT OnButtonClicked(
[in] int itemIndex,
[in] LONG lParam,
[in, out] LONG *pRemoveFooter);
HRESULT OnDestroyButton(
[in] int ItemIndex,
[in] LONG lParam);
}
[
odl,
uuid(F0034DA8-8A22-4151-8F16-2EBA76565BCC)
]
interface IListViewFooter : stdole.IUnknown
{
HRESULT IsVisible(
[in] LONG pVisible);
HRESULT GetFooterFocus(
[out] LONG *pItemIndex);
HRESULT SetFooterFocus(
[in] LONG pItemIndex);
HRESULT SetIntroText(
[in] LPWSTR pText);
HRESULT Show(
[in] IListViewFooterCallback *pCallbackObject);
HRESULT RemoveAllButtons();
HRESULT InsertButton(
[in] int insertAt,
[in] LPWSTR pText,
[in] LPWSTR pUnknown,
[in] LONG iconIndex,
[in] LONG lParam);
HRESULT GetButtonLParam(
[in] int itemIndex,
[out] LONG *lParam);
}
and class:
Code:
Option Explicit
Implements IListViewFooterCallback
Public Sub IListViewFooterCallback_OnButtonClicked(ByVal itemIndex As Long, ByVal lParam As Long, pRemoveFooter As Long)
Debug.Print "Get footer button click, index=" & itemIndex & ",lparam=" & lParam
End Sub
Public Sub IListViewFooterCallback_OnDestroyButton(ByVal itemIndex As Long, ByVal lParam As Long)
End Sub
Coming up next in the world of undocumented ListView: subsetted groups (link for "Display all x items"), subitem label editing, and if I'm particularly ambitious.. apparently you can use groups in full virtual mode.
It seems you were ambitious enough...you did it 'GROUPS IN FULL VIRTUAL MODE'..yeah!
- Can you have more than one instance of 'FooterWorkArea' (meaning one for every Group created)?
LucasMKG
going through fafalone's archives..for solutions
As in, you'd have to draw absolutely everything yourself with graphics apis. You wouldn't even be using IListviewFooter, instead you'd be using DrawText, DrawIcon, etc.
I would like to toggle between IListViewFooter.Show and (IListViewFooter.Hide [currently, not existing] ), and i don't want to remove any button (in this case referring to 'RemoveAllButtons')
IListViewFooter...has IsVisible and when is set to false...listview hangs
- since IListViewFooter has 'Show'...can the Hide feature be inserted (since i can't edit 'lvundoc.tlb').
LucasMKG
!
Last edited by LucasMKG; Feb 24th, 2016 at 04:21 AM.
The type libraries are like APIs... they call methods in other modules. All the existing functions are included; if it's not already there it doesn't exist. But FWIW, the download does include the source to lvundoc.tlb and can be edited-- but look at the source and you'll see how it works.
What is the problem with just removing all the buttons to hide it (the entire thing is hidden then; the header line/text too)? You could make them module-level so you don't have to rebuild them from scratch; although it's so very few lines of code I can't see the dilemma.
IsVisible is read-only; you have to pass a variable (a Long) using VarPtr().
Edit: I don't recall how exactly it happened, I somehow forgot to include the lvundoc.tlb source in most of the downloads that used it. Attached is the most recent version/source of it/example callback classes. Sorry it's the one time I've forgotten to include TLB source.
Last edited by fafalone; Feb 25th, 2016 at 02:30 AM.
- in the 'cLVOwnerDataCB' you've added the 'glbRes(iItem).lGroup'...of which in the 'VirtualGroupsDemoAdvanced' doesn't feature. -> i guess the demo has to be updated.
- to terminate virtual groups interface...i simply insert this line
Code:
'If Not (cLVODC is Nothing) Then Set cLVODC = Nothing'
It's just meant as an example; what's important is the function declarations so to make sure it matched the TLB version the class came from my production project rather than the demo; the demo is still pGroupIndex = VLItems(iItem).iGrp.
If you're switching out of virtual mode you have to destroy and recreate the entire ListView since LVS_OWNERDATA must be set in the CreateWindowEx call.. but yeah if you're just switching to non-grouped but still virtual just kill the class like that.
What do you mean by backwards compatibility in this case? XP?
The interface could exist in the typelib, and have a class module implementing it, and still run on XP as long as it wasn't called. Edit: In fact I just fired up the XP VM and you can even have the callback implemented inside a UserControl, and it won't cause a problem.
Not sure there's any other way to go about it in this case. How would you even get IListViewFooter, nevermind worrying about the callback. It's one thing to simulate things by calling the v-table manually with DispCallFunc, but here it has to be retrieved via SendMessage LVM_QUERYINTERFACE.. so I'm not even sure you could cast it to stdole.IUnknown and call by offset. Then after that I'm even less confident in being able to build the callback on IUnknown as I've never even seen such a thing done before.
Last edited by fafalone; Feb 26th, 2016 at 05:54 AM.
What do you mean by backwards compatibility in this case? XP?
The interface could exist in the typelib, and have a class module implementing it, and still run on XP as long as it wasn't called. Edit: In fact I just fired up the XP VM and you can even have the callback implemented inside a UserControl, and it won't cause a problem.
Not sure there's any other way to go about it in this case. How would you even get IListViewFooter, nevermind worrying about the callback. It's one thing to simulate things by calling the v-table manually with DispCallFunc, but here it has to be retrieved via SendMessage LVM_QUERYINTERFACE.. so I'm not even sure you could cast it to stdole.IUnknown and call by offset. Then after that I'm even less confident in being able to build the callback on IUnknown as I've never even seen such a thing done before.
Creating IListViewFooter is not a problem, cast LVM_QUERYINTERFACE to IUnknown then calling via DispCallFunc. This is working. (CoCreateInstance with IID_IListViewFooter is not needed as the ListView already have an instance you just retrieve it by LVM_QUERYINTERFACE)
I was just wondering if there is a way also with IListViewFooterCallback. But that seems very difficult as it has to be 'Implement'.
But if you are saying it does not cause any compatibility issues to implement IListViewFooterCallback via type lib then I might consider this way. (Haven't tested yet on my own on XP so I was not aware)
Yeah it's ok to have a control/class implement it in XP. On XP, the LVM_QUERYINTERFACE call will smoothly fail (pLVF or pUnk object will = Nothing; it's not a crash or error), so you can cancel out of IListViewFooter calls from there, and nothing related to IListViewFooterCallback is ever called- and the uncalled 'Implements' statement never even raises an error since it never gets to the point of checking with the system whether the interface exists or not, and since it is defined in the typelib it exists as far as running/compiling is concerned. Verified on XP SP3 (IDE was manifested; and I compiled the test without a manifest and got the same result).
Since your ListView is already using a typelib, it's definitely better to just throw in the definition there.
Code:
[
odl,
uuid(88EB9442-913B-4AB4-A741-DD99DCB7558B)
]
interface IListViewFooterCallback : stdole.IUnknown
{
HRESULT OnButtonClicked(
[in] int itemIndex,
[in] LONG lParam,
[in, out] LONG *pRemoveFooter);
HRESULT OnDestroyButton(
[in] int ItemIndex,
[in] LONG lParam);
}
If you still do want to pursue the other way; what about just putting it on top of IUnknown.. have the class use 'Implements IUnknown' and then just use DispCallFunc like elsewhere. You'd have to include implementations of the 3 IUnknown methods, but that at least I have seen done in VB before. But I'd really advise against it, as there's no compatibility problems with uncalled interfaces (you could have used IListViewFooter from a typelib too; it's same deal, LVM_QUERYINTERFACE's lParam object =Nothing, and you just skip over).
Edit: To further verify, I added Implements IListViewFooterCallback and the two prototypes right into your latest ListView.ctl on XP, and it ran fine with no errors.
Last edited by fafalone; Feb 26th, 2016 at 11:42 PM.
...if you were feeling adventurous (although you'd still need mktyplib / midl.exe to compile into a TLB).
- I've been fiddling with the above MS-DOS Tools but they need something like language description...that's where i halted.
- but "EditTLB.exe" is ideal but one need to buy a book (Advanced Visual Basic 6) which is no longer in print from PowerVB - I have the "EditTLBEval.exe" which is the evaluation version.
They're just compilers... for example see the mklv.bat file in the TLB source folder; it's used like C:\path\to\mktyplib.exe lvundocdefs.odl
and that's it. I didn't use anything besides plain text editing for either lvundocdefs or oleexp.
thanks fafalone, (I had to figure-out MKTYPLIB related files)
for those (like mwaa!) who opted not to install Visual C++...here is a list of files needed to run 'MKTYPLIB' successfully;
EDIT::
(File + File location in CD)
C1.DLL...............VC98\BIN
CL.EXE...............VC98\BIN
MKTYPLIB.EXE.....VC98\BIN
MSPDB60.DLL......VB98
::
LucasMKG
making progress...
Last edited by LucasMKG; Mar 27th, 2016 at 01:44 AM.