I want to display a custom cursor (32x32) in WinUI3. It seems that there is no simple way to load a .cur file from the embedded resources or is there?
So far I tried to create a win32 .res file with the cursors embedded and then tried to use them using InputDesktopResourceCursor.CreateFromModule (and variations of that).
However, when I put Win32Resource and WindowsAppSDKSelfContained in the same csproj file, an error occurs "conflicting options specified: Win32 resource file, Win32 manifest". When I remove WindowsAppSDKSelfContained, the project can be compiled, but this is not what I want.
Is there another way to use a .cur file without Win32Resource?
Here is a way to load a custom cursor from a .cur
file or from any native HCURSOR. It's based on WinApp SDK's IInputCursorStaticsInterop interface that allows to define an InputCursor
from a native HCURSOR
.
So for this XAML:
<Window
x:Class="WinUI3App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Border
x:Name="pencilZone"
Width="50"
Height="50"
Background="LightGreen" />
</StackPanel>
</Window>
And this window:
public sealed partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var cursor = CursorUtilities.LoadCursor(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Windows),
@"cursors\aero_pen.cur"));
pencilZone.ChangeCursor(cursor);
}
}
This is what you should see:
#nullable enable
using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
namespace WinUI3Tools;
public static class CursorUtilities
{
public static void ChangeCursor(this UIElement element, InputCursor? cursor)
{
ArgumentNullException.ThrowIfNull(element);
// the stupid hack...
typeof(UIElement).InvokeMember("ProtectedCursor", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, element, new[] { cursor });
}
public static InputCursor? LoadCursor(string filePath)
{
ArgumentNullException.ThrowIfNull(filePath);
var hcursor = LoadCursorFromFileW(filePath);
if (hcursor == 0)
throw new Win32Exception(Marshal.GetLastWin32Error());
return CreateCursorFromHCURSOR(hcursor);
}
public static InputCursor? CreateCursorFromHCURSOR(nint hcursor)
{
if (hcursor == 0)
return null;
const string classId = "Microsoft.UI.Input.InputCursor";
_ = WindowsCreateString(classId, classId.Length, out var hs);
_ = RoGetActivationFactory(hs, typeof(IActivationFactory).GUID, out var fac);
_ = WindowsDeleteString(hs);
if (fac is not IInputCursorStaticsInterop interop)
return null;
interop.CreateFromHCursor(hcursor, out var cursorAbi);
if (cursorAbi == 0)
return null;
return WinRT.MarshalInspectable<InputCursor>.FromAbi(cursorAbi);
}
[ComImport, Guid("ac6f5065-90c4-46ce-beb7-05e138e54117"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IInputCursorStaticsInterop
{
// IInspectable unused methods
void GetIids();
void GetRuntimeClassName();
void GetTrustLevel();
[PreserveSig]
int CreateFromHCursor(nint hcursor, out nint inputCursor);
}
[ComImport, Guid("00000035-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IActivationFactory
{
// IInspectable unused methods
void GetIids();
void GetRuntimeClassName();
void GetTrustLevel();
[PreserveSig]
int ActivateInstance(out nint instance);
}
[DllImport("api-ms-win-core-winrt-l1-1-0.dll")]
private static extern int RoGetActivationFactory(nint runtimeClassId, [MarshalAs(UnmanagedType.LPStruct)] Guid iid, out IActivationFactory factory);
[DllImport("user32", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern nint LoadCursorFromFileW(string name);
[DllImport("user32", CharSet = CharSet.Unicode)]
private static extern bool DestroyCursor(nint hcursor);
[DllImport("api-ms-win-core-winrt-string-l1-1-0", CharSet = CharSet.Unicode)]
private static extern int WindowsCreateString(string? sourceString, int length, out nint @string);
[DllImport("api-ms-win-core-winrt-string-l1-1-0", CharSet = CharSet.Unicode)]
private static extern int WindowsDeleteString(nint @string);
}