Accessing thumbnails that don't exist

2020-06-12 03:23发布

问题:

I have made an application that presents you a list of files in your computer. Whenever you click any item in the list, a small PictureBox next to it should show the thumbnail of the corresponding file. I am using C# on Windows 7.

To obtain the thumbnail, I've recurred to a method posted in a different question. First, I reference the Windows API Code pack. Then, I use the following code:

ShellFile shellFile = ShellFile.FromFilePath(fullPathToFile);
myPictureBox.Image = shellFile.Thumbnail.LargeBitmap;

This doesn't always work. Sometimes, the thumbnail shown is merely the 'default application' icon. I've found out that the real thumbnail is only shown if Windows had previously generated the thumbnail for that file and stored it in the thumbnails cache. This means that I have to manually open a folder, wait for Windows to generate thumbnails for each file, and then my application will be able to see those thumbs.

How can my program force Windows 7 to generate real thumbnails before using them?

Update (by Li0liQ)

It is possible to force thumbnail retrieval by adding FormatOption:

ShellFile shellFile = ShellFile.FromFilePath(fullPathToFile);
shellFile.Thumbnail.FormatOption = ShellThumbnailFormatOption.ThumbnailOnly;
myPictureBox.Image = shellFile.Thumbnail.LargeBitmap;

however, I'm getting an exception in case thumbnail is not there yet:

The current ShellObject does not have a valid thumbnail handler or there was a problem in extracting the thumbnail for this specific shell object. ---> System.Runtime.InteropServices.COMException: Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))

See How do I refresh a file's thumbnail in Windows Explorer? question and code snippet for potential clues.

回答1:

Here is a piece of code that extracts thumbnail bitmaps (using Windows Vista or higher only). It's based on the cool IShellItemImageFactory interface:

    static void Main(string[] args)
    {
        // you can use any type of file supported by your windows installation.
        string path = @"c:\temp\whatever.pdf";
        using (Bitmap bmp = ExtractThumbnail(path, new Size(1024, 1024), SIIGBF.SIIGBF_RESIZETOFIT))
        {
            bmp.Save("whatever.png", ImageFormat.Png);
        }
    }

    public static Bitmap ExtractThumbnail(string filePath, Size size, SIIGBF flags)
    {
        if (filePath == null)
            throw new ArgumentNullException("filePath");

        // TODO: you might want to cache the factory for different types of files
        // as this simple call may trigger some heavy-load underground operations
        IShellItemImageFactory factory;
        int hr = SHCreateItemFromParsingName(filePath, IntPtr.Zero, typeof(IShellItemImageFactory).GUID, out factory);
        if (hr != 0)
            throw new Win32Exception(hr);

        IntPtr bmp;
        hr = factory.GetImage(size, flags, out bmp);
        if (hr != 0)
            throw new Win32Exception(hr);

        return Bitmap.FromHbitmap(bmp);
    }

    [Flags]
    public enum SIIGBF
    {
        SIIGBF_RESIZETOFIT = 0x00000000,
        SIIGBF_BIGGERSIZEOK = 0x00000001,
        SIIGBF_MEMORYONLY = 0x00000002,
        SIIGBF_ICONONLY = 0x00000004,
        SIIGBF_THUMBNAILONLY = 0x00000008,
        SIIGBF_INCACHEONLY = 0x00000010,
        SIIGBF_CROPTOSQUARE = 0x00000020,
        SIIGBF_WIDETHUMBNAILS = 0x00000040,
        SIIGBF_ICONBACKGROUND = 0x00000080,
        SIIGBF_SCALEUP = 0x00000100,
    }

    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    private static extern int SHCreateItemFromParsingName(string path, IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItemImageFactory factory);

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
    private interface IShellItemImageFactory
    {
        [PreserveSig]
        int GetImage(Size size, SIIGBF flags, out IntPtr phbm);
    }

Additional notes:

  • The GetImage method has various interesting flags (SIIGBF) you can play with.
  • For performance reasons, you can cache the factories. For example, .PDF files requires the whole Adobe Reader .exe to load in the background.
  • When talking to the shell (Windows Explorer), you want to make sure your process runs at the same UAC level than the shell otherwise, for security reasons, some operations will fail. So for example, if you run your process from F5 or CTRL+F5 in Visual Studio, and your Visual Studio runs as admin, your process may not be able to retrieve thumbnails, while it will work when run double-clicking on the .exe from the explorer. The REGDB_E_CLASSNOTREG is a typical kind of error you may get in these cases.


回答2:

After some searching, I found Microsoft Office Thumbnails in Sharepoint. It might only work on office documents, but might be what you want. Will require some translation as the examples are in C++ and VB.Net.



回答3:

How can my program force Windows 7 to generate real thumbnails before using them?

From Shell Interfaces: IThumbnailCache interface (emphasis mine)

The Thumbnail Cache API is designed to provide applications with a unified method to retrieve and cache thumbnails. In Windows XP, thumbnail caching is done on a per-folder basis, and the cache is maintained in a Thumbs.db file within each folder. While this approach provides spatial locality, it does not support previews and queries across folders. The thumbnail cache in Windows Vista addresses this shortcoming by providing a global cache.

To cache a thumbnail, an application must first obtain an IShellItem that represents the item for which a thumbnail will be obtained, and then pass the IShellItem to a call to IThumbnailCache::GetThumbnail. If the flags parameter to IThumbnailCache::GetThumbnail includes the flag WTS_EXTRACT, and the thumbnail is not already cached, a thumbnail will be extracted and placed in the cache. If the flag WTS_FORCEEXTRACTION is set, the cache is ignored and a new thumbnail is always extracted. See the IThumbnailCache::GetThumbnail topic for more details on the flags passed to IThumbnailCache::GetThumbnail.

If a thumbnail is not already in the cache, it will be automatically extracted from the source file using the existing implementation(s) of IExtractImage or IThumbnailProvider that is registered on the operating system. Your application does not have to provide an implementation of the thumbnail extractor.

So, assuming your system has an implementation of IThumbnail provider that works with PDF:

  1. Get a shell item for your PDF.
  2. Get a reference to the shell's thumbnail cache. Let's call it thumbNailCache.
  3. Call thumbNailCache.GetThumbnail(shellItem, thumbNailpx,wtxFlagSet, out thumbNailBitmap);. The documentation says this will create the thumbnail for you and return it in the out parameter thumbNailBitmap.

Caveats: I don't know if your Code Pack fully exposes IThumbnailCache, but if it does, this looks to be pretty straight forward. If it doesn't you'll have to import it from shell32.dll.

There's a IThumbnailProvider interface as well that may work for you, but read the community comments. It seems to be fragile and tricky to use.



回答4:

As far as I know you cannot force Windows 7 to generate thumbnails. However, you can create it with the code by yourself:

Image image = Image.FromFile(fileName);
Image thumbNail = image.GetThumbnailImage(120, 120, ()=>false, IntPtr.Zero);
thumbNail.Save(Path.ChangeExtension(fileName, "thumb"));

http://msdn.microsoft.com/en-us/library/system.drawing.image.getthumbnailimage.aspx