I'm making my own thumbnail manager because I needed some obscure features.
I implemented IThumbnailProvider, and it mostly works, but now I'm trying to make some basic UI.
One of the things I got stuck on is the equivalent of a feature of "Media Preview":
In the "Testing and Cache" tab you can drag-n-drop files to check and regenerate their thumbnails.
Somehow, "Media Preview" immediately updates the thumbnails of those files in explorer, but I only managed to read an existing thumbnail for a file.
I'm pretty sure it's not about something like permissions.
VisualStudio is running as admin (only while I'm trying to get anything working ofc).
UAC is dead, and so are Windows Defender and Firewall.
And in the script registering the IThumbnailProvider implementation, I added DisableProcessIsolation=1 so it can report every problem to me.
SHChangeNotify is not it. It notifies the system to check, but there needs to be an actual change (in the registry or file itself) for IThumbnailProvider to be called.
For instance, if I change the last write time of the file before calling SHChangeNotify - it works. But, needless to say, that's not a solution.
My latest try is based on Phind's suggestion on how to read thumbnails (when I asked how to override them). Nevertheless, I checked the interfaces and found ISharedBitmap::InitializeBitmap. At least I can't find any other use for it. I mean, from msdn - ISharedBitmap is only returned from 2 methods of IThumbnailCache, so surely it would have something to do with my case...
Anyway, here is what I've done in my dummy "Test" project:
[ComImport()]
[Guid("091162a4-bc96-411f-aae8-c5122cd03363")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ISharedBitmap
{
uint GetSharedBitmap(
[Out] out IntPtr phbm
);
uint GetSize(
[Out, MarshalAs(UnmanagedType.Struct)] out SIZE pSize
);
uint GetFormat(
[Out] out WTS_ALPHATYPE pat
);
uint InitializeBitmap(
[In] IntPtr hbm,
[In] WTS_ALPHATYPE wtsAT
);
uint Detach(
[Out] out IntPtr phbm
);
}
This is the only part of the definitions I'm still anxious about. At first, the order of methods was the same as here, as it was on pinvoke, but GetFormat was returning the bitmap size, etc., so I reordered them as here.
Then, in the method using them:
public static void ReplaceThumbnail(string filePath, string newThumbnailPath)
{
var newThumbnail = new Bitmap(newThumbnailPath);
SHCreateItemFromParsingName(filePath, IntPtr.Zero, typeof(IShellItem).GUID, out var item);
var CLSID_LocalThumbnailCache = new Guid("50EF4544-AC9F-4A8E-B21B-8A26180DB13F");
var thumbnailCache = (IThumbnailCache)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_LocalThumbnailCache, true)!)!;
thumbnailCache.GetThumbnail(item, int.MaxValue, WTS_FLAGS.WTS_INCACHEONLY, out var sharedBitmap, out var cacheFlags, out _);
var res1 = sharedBitmap.GetFormat(out var format);
var res2 = sharedBitmap.GetSize(out var size);
var res3 = sharedBitmap.GetSharedBitmap(out var hbmp);
Bitmap.FromHbitmap(hbmp).Save(@"G:\0Prog\Thumbnailer\Test\1.bmp");
//var bd = newThumbnail.LockBits(new Rectangle(default, newThumbnail.Size), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
//sharedBitmap.InitializeBitmap(bd.Scan0, WTS_ALPHATYPE.WTSAT_RGB);
var res4 = sharedBitmap.InitializeBitmap(newThumbnail.GetHbitmap(), WTS_ALPHATYPE.WTSAT_ARGB);
var res5 = sharedBitmap.GetSharedBitmap(out var hbmp2);
Bitmap.FromHbitmap(hbmp2).Save(@"G:\0Prog\Thumbnailer\Test\2.bmp");
thumbnailCache.GetThumbnail(item, int.MaxValue, WTS_FLAGS.WTS_INCACHEONLY, out var sharedBitmap2, out var cacheFlags2, out _);
var res6 = sharedBitmap2.GetSharedBitmap(out var hbmp3);
Bitmap.FromHbitmap(hbmp3).Save(@"G:\0Prog\Thumbnailer\Test\3.bmp");
//SHChangeNotify(HChangeNotifyEventID.SHCNE_ALLEVENTS, HChangeNotifyFlags.SHCNF_PATHW, filePath, IntPtr.Zero);
}
[DllImport("shell32.dll")]
static extern void SHChangeNotify(
HChangeNotifyEventID wEventId,
HChangeNotifyFlags uFlags,
[MarshalAs(UnmanagedType.LPWStr)] string dwItem1,
IntPtr dwItem2
);
Looking in debug:
- Every
resN(error code) is 0. hbmp2is the exact same handle as passed to.InitializeBitmapbd.Scan0is accepted by.InitializeBitmap, unlike any random handle value, but then I can'tBitmap.FromHbitmapit after I get it back.- format is
WTSAT_RGB, size is1280 x 720, all as expected. 1.bmpis the thumbnail I see in Explorer, the same as what my implementation ofIThumbnailProvidergenerated, tho a bit downscaled.3.bmpis its exact copy, so only the local instance ofISharedBitmapwas changed by.InitializeBitmap.- My implementation of
IThumbnailProvideris not overriding the thumbnail right after. I log it when it is called. And, well, it isn't.
Also, as a side note, if .InitializeBitmap is in fact the intended mechanism for overriding thumbnails - it is a very bad interface. Because .GetThumbnail refuses to give any kind of ISharedBitmap if the implementation of IThumbnailProvider has failed (or returned an empty result).
While for the purposes of just overriding the thumbnail - the success of IThumbnailProvider should not matter.
So, what am I doing wrong here? Is there any other WinAPI to set the thumbnail of a specific file?