WPF: How to enable shadows and rounded corners on ToolTips in Windows 11

Windows 11 originally came out in 2021, however, an official Windows 11 theme has not yet been released for WPF (more on that here). Because of this, WPF apps still have more of a Windows 8/10 look. Luckily, there are some easy things you can do to modernise your WPF user interfaces. You could of course, create your own custom styles, however this can take quite a bit of work. This article covers something that can be done easily, without much work and without breaking compatibility with older operating systems.

Enabling shadows and rounded corners on ToolTips

WPF presents its ToolTips in seperate top level windows. This allows them to easily show on top of other content and not be limited to the size and position of its parent window. Windows 11 includes a built in feature as part of the DWM (Desktop Window Manager) that rounds top level windows. You can control this feature using the DwnSetWindowAttribute Windows function. Using this function, we can tell Windows 11 to have the DWM apply rounded corners and a shadow on WPF ToolTips.

Normal WPF ToolTip on Windows 11 vs WPF ToolTip on Windows 11 with rounded corners and a shadow
Normal WPF ToolTip on Windows 11 vs WPF ToolTip on Windows 11 with rounded corners and a shadow

How to enable shadows and rounded corners on ToolTips

To enable shadows and rounded corners on all ToolTips, we can create a style in App.xaml/Application.xaml that defines an EventSetter so that we can run code ever time a ToolTip opens to apply the rounded corners. If you don't have any resources added in App.xaml/Application.xaml yet, you can create an element called Application.Resources and put this style there.

Tip: You could also define this style in a Window or other element’s resources to only apply shadows and rounded corners to all ToolTips for all child elements of that element or on an individual element to only apply it to ToolTips for that element.

XAML:

<Style TargetType="ToolTip">

<EventSetter Event="Opened" Handler="ToolTip_Opened"/>

</Style>


Now, in the code we must first define the DwnSetWindowAttribute function like so, so that we can call it using platform invoke as it is a Windows function: 

C#:

[DllImport("dwmapi.dll", PreserveSig = true)]

public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);


VB:

<DllImport("dwmapi.dll", PreserveSig:=True)>

Public Shared Function DwmSetWindowAttribute(hwnd As IntPtr, attr As Integer, ByRef attrValue As Integer, attrSize As Integer) As Integer

End Function


And now, we can define the handler for the ToolTip.Opened event:

C#:

// Check that the user is on Windows 11 (which is technically Windows 10 build 22000 or higher) and that high contrast mode is not on as this method does not work well with the WPF high contrast theme.

if (Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= 22000 && SystemParameters.HighContrast == false)

{

// Gets the window handle of the ToolTip. This is used to tell Windows what window we want to apply the shadow and rounded corners to.

HwndSource SenderHwnd = (HwndSource)HwndSource.FromVisual((System.Windows.Media.Visual)sender);

// 3 indicates to apply a shadow and round the corners with a small radius.

int DWMWCP_ROUNDSMALL = 3;

// Call the DWMSetWindowAttribute function, setting attribute 33 which is DWMWA_WINDOW_CORNER_PREFERENCE.

DwmSetWindowAttribute(SenderHwnd.Handle, 33, ref DWMWCP_ROUNDSMALL, Marshal.SizeOf(3));

}

}


VB:

 Sub ToolTip_Opened(sender As Object, e As RoutedEventArgs)

   ' Check that the user Is on Windows 11 (which Is technically Windows 10 build 22000 Or higher) And that high contrast mode Is Not on as this method does Not work well with the WPF high contrast theme.

   If Environment.OSVersion.Version.Major >= 10 And Environment.OSVersion.Version.Build >= 22000 And SystemParameters.HighContrast = False Then

   ' Gets the window handle of the ToolTip. This Is used to tell Windows what window we want to apply the shadow And rounded corners to.

   Dim SenderHwnd As HwndSource = HwndSource.FromVisual(sender)

   ' Call the DWMSetWindowAttribute function, setting attribute 33 which Is DWMWA_WINDOW_CORNER_PREFERENCE. 3 indicates to apply a shadow and round the corners with a small radius.

Application.DwmSetWindowAttribute(SenderHwnd.Handle, 33, 3, Marshal.SizeOf(3))

   End If

End Sub


Now, your ToolTips should look like this:

If it does not work, make sure you followed the instructions correctly and check below for some troubleshooting information.

Troubleshooting

If Environment.OSVersion.Version.Major is returning 6 on Windows 11, it may be because Windows thinks you app was designed for older versions of Windows and so applies compatibility settings to it. This can be fixed by specifying that your app supports Windows 10 in the manifest. To add a manifest to your app if you don't have one already, first right click on your Project and select Properties to open yur project's properties. Depending on your project settings and your Visual Studio version, you may see different options in this next step. The option you're looking for is something to do with creating/opening/viewing the app manifest. It will likely be called 'Create or open app manifest for Windows settings.' or 'View Windows Settings' or something similar. If you do not see this option, you can add one manually by right clicking on your project and selecting Add -> New Item.... From there, choose Application Manifest File, leave the default name and choose Add. Now if you view your project's properties, the manifest you created should show there as the app's manifest. If it does not and you see an option to select a manifest, select the file you just created.

Within the default manifest template, you should see a section under Assembly -> Compatibility -> Application that looks like this:

XML:

<!-- Windows 10 -->

<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->


Simply remove the comment (by removing "<!--" and "-->" from the line that starts with supportedOS (second line in the above example) and now it should look like this:

XML:

<!-- Windows 10 -->

<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />


Now Environment.OSVersion.Version.Major should return 10. If you still have issues or have any other issues, let us know in the comments!

This snippet is available for Codly. Click the download link below to download the snippet. If you don't have Codly, it is available here in the Microsoft Store.

Did this guide help you? Do you have anything to add? Do you know or do you work on/have created any libraries that bring more modern visual styles to WPF? Let us know in the comments! Also, if this guide did help you and you use an AdBlocker and you want to support us, consider disabling it for this site - if you do, it will be greatly appreciated!

Follow us to be notified when we release new guides:

Comments

  1. This is very cool! And it works for context menus (so long as you remove the drop shadow on the context menu)

    ReplyDelete

Post a Comment

Popular posts from this blog

How to show placeholder text in a WPF TextBox in C# and VB

How to enable dark title bar in WPF and Windows Forms/WinForms apps in C# and VB

How to use modern icons in XAML in WPF on Windows 10 and 11

How to change the colour of a WPF or Windows Forms/WinForms title bar in Windows 11 in C# and VB

Microsoft WebView2: How to check if the WebView2 runtime is installed in C# and VB