winui-3windows-app-sdk

Retrive Window Handle in Class Library WinUI3


I am trying to use the PrintManager in a WinUI3 class library project. But with WinUI, currently, we can only use PrintManager if we have the windows handle(HWND) unlike UWP where we could simply use ForCurrentView() without any parameters.

Here is something similar (the difference is they call the PrintManager in UI project itself and not a different class library) https://github.com/marb2000/PrintSample. In this, we retrieve the HWND using the below API:

var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);

Notice that in the above line a Window object needs to be injected. This is not available to me currently in my class library which will be consumed by a UI project.

Could we retrieve the Windows Handle in the class library without injecting it from the UI code that will be using the library?


Solution

  • In a WinUI3 app (or more generally in a Windows desktop app), you can have multiple Windows, contrary to UWP where you could only have only one Window.

    That's why there's no more the concept of a "current" window/view.

    So, you must pass the Window (or its handle directly) to your functions, something like this for example How to retrieve the window handle of the current WinUI 3 MainWindow from a page in a frame in a NavigationView control on the MainWindow

    Another solution, if you're sure there's only one Window for example, is to get its handle by browsing the list of Windows for the current process (you could also filter by current thread, etc.). Here's some C# code with interop calls that does this:

    // get all windows in the process  
    var tops = Win32Window.GetProcessWindows();
    
    // get the first WinUI3 window, its class name is fixed as far as we know
    var firstWinUI3 = tops.FirstOrDefault(w => w.ClassName == "WinUIDesktopWin32WindowClass");
    
    // utility class that wraps a win32 window
    public class Win32Window
    {
        public Win32Window(IntPtr handle)
        {
            Handle = handle;
            ThreadId = GetWindowThreadProcessId(handle, out var processId);
            ProcessId = processId;
        }
    
        public IntPtr Handle { get; }
        public int ThreadId { get; }
        public int ProcessId { get; }
        public string ClassName => GetClassName(Handle);
        public string Text => GetWindowText(Handle);
        public bool IsEnabled => IsWindowEnabled(Handle);
    
        public override string ToString()
        {
            var s = ClassName;
            var text = Text;
            if (text != null)
            {
                s += " '" + text + "'";
            }
            return s;
        }
    
        public static IReadOnlyList<Win32Window> GetTopLevelWindows()
        {
            var list = new List<Win32Window>();
            EnumWindows((h, l) =>
            {
                list.Add(new Win32Window(h));
                return true;
            }, IntPtr.Zero);
            return list.AsReadOnly();
        }
    
        public static IReadOnlyList<Win32Window> GetProcessWindows()
        {
            var process = Process.GetCurrentProcess();
            var list = new List<Win32Window>();
            EnumWindows((h, l) =>
            {
                var window = new Win32Window(h);
                if (window.ProcessId == process.Id)
                {
                    list.Add(window);
                }
                return true;
            }, IntPtr.Zero);
            return list.AsReadOnly();
        }
    
        private static string GetWindowText(IntPtr hwnd)
        {
            var sb = new StringBuilder(1024);
            GetWindowText(hwnd, sb, sb.Capacity - 1);
            return sb.ToString();
        }
    
        private static string GetClassName(IntPtr hwnd)
        {
            var sb = new StringBuilder(256);
            GetClassName(hwnd, sb, sb.Capacity - 1);
            return sb.ToString();
        }
    
        private delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
    
        [DllImport("user32", SetLastError = true)]
        private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
    
        [DllImport("user32")]
        private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
    
        [DllImport("user32")]
        public static extern bool IsWindowEnabled(IntPtr hwnd);
    
        [DllImport("user32", CharSet = CharSet.Unicode)]
        public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    
        [DllImport("user32", CharSet = CharSet.Unicode)]
        public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
    }
    

    Update June 2024

    From WinAppSDK 1.4+, from any UIElement instance, you can now use do this (using ContentIslandEnvironment:

    var windowId = this.XamlRoot?.ContentIslandEnvironment?.AppWindowId;
    if (!windowId.HasValue)
        return;
    
    var hwnd = Win32Interop.GetWindowFromWindowId(parentId.Value);