ElektroStudios ElektroStudios - 6 months ago 66
Vb.net Question

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

Answer

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: