Search code examples
c#.netvb.netwinapidwm

How to use Win32 'DwmSetIconicThumbnail' from C# or VB.Net?


I would like to use the DwmSetIconicThumbnail function to set a static image for the thumbnail preview of my app.

As pointed in the reference link above, firstly is necessary to call DwmSetWindowAttribute to enable DWMWA_FORCE_ICONIC_REPRESENTATION and DWMWA_HAS_ICONIC_BITMAP attributes.

I already did all that. I've taken all the definitions from WindowsAPICodePack source code here, and I'm following the same steps (or I think so).

The problem is that when I try to adapt the example for my WinForms Window, I get a E_INVALIDARG HRESULT code when calling DwmSetIconicThumbnail function at the end of the code below, I'm not sure whether the problematic argument is the hwnd, or the hBitmap.

What I'm doing wrong?.


C#:

Bitmap bmp;
IntPtr hBitmap;
IntPtr hwnd;
int hresult;

const int DisplayThumbnailFrame = 0x1;
public enum DwmWindowAttribute : uint
{
    NcRenderingEnabled = 1,
    NcRenderingPolicy,
    TransitionsForceDisabled,
    AllowNcPaint,
    CaptionButtonBounds,
    NonClientRtlLayout,
    ForceIconicRepresentation,
    Flip3DPolicy,
    ExtendedFrameBounds,
    HasIconicBitmap,
    DisallowPeek,
    ExcludedFromPeek,
    Cloak,
    Cloaked,
    FreezeRepresentation,
    Last
}

[DllImport("dwmapi.dll", PreserveSig = true)]
static internal extern int DwmSetWindowAttribute(IntPtr hwnd, 
                                                 DwmWindowAttribute dwAttributeToSet, 
                                                 IntPtr pvAttributeValue, 
                                                 uint cbAttribute);

[DllImport("Dwmapi.dll")]
public static extern int DwmSetIconicThumbnail(IntPtr hwnd, 
                                               IntPtr hBitmap, 
                                               int flags);

private void Form1_Shown() {

    bmp = (Bitmap)Bitmap.FromFile("C:\\Image.jpg");
    hBitmap = bmp.GetHbitmap();
    hwnd = Process.GetCurrentProcess.MainWindowHandle;

    IntPtr block = Marshal.AllocHGlobal(4);
    int value = Math.Abs(Convert.ToInt32(true)); // or 1
    Marshal.WriteInt32(block, value);

    try {
        hresult = DwmSetWindowAttribute(hwnd, DwmWindowAttribute.HasIconicBitmap, block, 4);
        if ((hresult != 0)) {
            throw Marshal.GetExceptionForHR(hresult);
        }

        hresult = DwmSetWindowAttribute(hwnd, DwmWindowAttribute.ForceIconicRepresentation, block, 4);
        if ((hresult != 0)) {
            throw Marshal.GetExceptionForHR(hresult);
        }

    } finally {
        Marshal.FreeHGlobal(block);
    }

    hresult = DwmSetIconicThumbnail(hwnd, hBitmap, DisplayThumbnailFrame);
    if ((hresult != 0)) {
        throw Marshal.GetExceptionForHR(hresult);
    }

}

VB.NET:

Dim bmp As Bitmap
Dim hBitmap As IntPtr
Dim hwnd As IntPtr
Dim hresult As Integer

Const DisplayThumbnailFrame As Integer = &H1

Enum DwmWindowAttribute As UInteger
    NcRenderingEnabled = 1
    NcRenderingPolicy
    TransitionsForceDisabled
    AllowNcPaint
    CaptionButtonBounds
    NonClientRtlLayout
    ForceIconicRepresentation
    Flip3DPolicy
    ExtendedFrameBounds
    HasIconicBitmap
    DisallowPeek
    ExcludedFromPeek
    Cloak
    Cloaked
    FreezeRepresentation
    Last
End Enum

<DllImport("dwmapi.dll", PreserveSig:=True)>
Friend Shared Function DwmSetWindowAttribute(hwnd As IntPtr,
                                             dwAttributeToSet As DwmWindowAttribute,
                                             pvAttributeValue As IntPtr,
                                             cbAttribute As UInteger
) As Integer
End Function

<DllImport("Dwmapi.dll")>
Public Shared Function DwmSetIconicThumbnail(ByVal hwnd As IntPtr,
                                             ByVal hBitmap As IntPtr,
                                             ByVal flags As Integer
) As Integer
End Function

Private Sub Form1_Shown() Handles MyBase.Shown

    bmp = DirectCast(Bitmap.FromFile("C:\Image.jpg"), Bitmap)
    hBitmap = bmp.GetHbitmap()
    hwnd = Process.GetCurrentProcess.MainWindowHandle

    Dim block As IntPtr = Marshal.AllocHGlobal(4)
    Dim value As Integer = Math.Abs(CInt(True)) ' or 1
    Marshal.WriteInt32(block, value)

    Try
        hresult = DwmSetWindowAttribute(hwnd, DwmWindowAttribute.HasIconicBitmap, block, 4)
        If (hresult <> 0) Then
            Throw Marshal.GetExceptionForHR(hresult)
        End If

        hresult = DwmSetWindowAttribute(hwnd, DwmWindowAttribute.ForceIconicRepresentation, block, 4)
        If (hresult <> 0) Then
            Throw Marshal.GetExceptionForHR(hresult)
        End If

    Finally
        Marshal.FreeHGlobal(block)

    End Try

    hresult = DwmSetIconicThumbnail(hwnd, hBitmap, DisplayThumbnailFrame)
    If (hresult <> 0) Then
        Throw Marshal.GetExceptionForHR(hresult)
    End If

End Sub

Solution

  • According to MSDN Documentation:

    An application typically calls the DwmSetIconicThumbnail function after it receives a WM_DWMSENDICONICTHUMBNAIL message for its window. The thumbnail should not exceed the maximum x-coordinate and y-coordinate that are specified in that message. The thumbnail must also have a 32-bit color depth.

    So, using the following 32-by-32 bitmap, with 32-bit color depth, it worked:

    enter image description here

    The exception was gone. However, it didn't quite replace the application icon thumbnail, but appended it.

    This is what it looks like using ALT+TAB:

    enter image description here

    And this when hovering over it:

    enter image description here

    Note that I did not modify your code at all and ran it exactly as it is, but just using a suitable Bitmap. These are results for a Windows 10 machine.


    UPDATE

    The reason because the DwmSetIconicThumbnail function returns an error is because the image exceeds the maximum size for the thumbnail, that's it, a resize is not handled by Windows itself, so we need to do a little bit more of work.

    I'm not sure about which factor determines the maximum possible thumbnail size that we can establish for our image, this is speculation but I think it depends on a Windows registry value that determines the width and height for thumbnail previews (I exactlly don't remember the registry location of that value, sorry, but it can be Googled easy).

    Well, as pointed above, we need to handle the WM_DWMSENDICONICTHUMBNAIL (0x323) message, so we need to override the base Window procedure a.k.a WNDPROC of our Win32 window (a Form), then finally we can retrieve the maximum width and height for the thumbnail creation from the message parameters.

    This is a working code sample:

    Private Const WM_DWMSENDICONICTHUMBNAIL As Integer = &H323
    
    Protected Overrides Sub WndProc(ByRef m As Windows.Forms.Message)
    
        Select Case m.Msg
    
            Case WM_DWMSENDICONICTHUMBNAIL
    
                Dim hwnd As IntPtr = Process.GetCurrentProcess().MainWindowHandle
                Dim dWord As Integer = m.LParam.ToInt32()
                Dim maxWidth As Short = BitConverter.ToInt16(BitConverter.GetBytes(dWord), 2)
                Dim maxHeight As Short = BitConverter.ToInt16(BitConverter.GetBytes(dWord), 0)
    
                Using img As Image = Bitmap.FromFile("C:\Image.jpg")
    
                    Using thumb As Bitmap = CType(img.GetThumbnailImage(maxWidth, maxHeight, Nothing, Nothing), Bitmap)
    
                        Dim hBitmap As IntPtr = thumb.GetHbitmap()
    
                        Dim hresult As Integer = NativeMethods.DwmSetIconicThumbnail(hwnd, hBitmap, 0)
                        If (hresult <> 0) Then
                            ' Handle error...
                            ' Throw Marshal.GetExceptionForHR(hresult)
                        End If
    
                        NativeMethods.DeleteObject(hBitmap)
    
                    End Using
    
                End Using
    
        End Select
    
        ' Return Message to base message handler.
        MyBase.WndProc(m)
    
    End Sub
    

    As last comment, and if in the future I need to remember this, I will share this question that I found on MSDN, which can be helpful for someone having problems with WM_DWMSENDICONICTHUMBNAIL message: