1

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?

  • 1
    In a WinUI3 app (or desktop app), you can have multiple Windows, that's why you'll have to pass the Window somehow as there's no more the concept of a "current" window/view. For example https://stackoverflow.com/a/71440820/403671 – Simon Mourier Nov 01 '22 at 16:27
  • Which window handle do you want to retrieve in your class library? We can [Call interop APIs from a .NET app](https://learn.microsoft.com/en-us/windows/apps/develop/ui-input/retrieve-hwnd#winui-3-with-c). Is it helpful? – YangXiaoPo-MSFT Nov 02 '22 at 01:55
  • Thanks, @SimonMourier. That answers my question. Could you please create an answer and I can mark it as right :) – Shantanu Methikar Nov 02 '22 at 06:23
  • @YangXiaoPo-MSFT that would not work as I do not have the Window object in the class library (which is passed as `this` parameter in the example) as Window will be created in the project consuming my library. – Shantanu Methikar Nov 02 '22 at 06:23
  • Would it be in option for you to use the project type "Class Library (WinUI 3 in Desktop)" for the location of your "PrintManager" class? In this project type, it would be possible to use UI related types like "Window". – Martin Nov 03 '22 at 11:57
  • @Martin, yes, I would be using "Class Library (WinUI 3 in Desktop)" but my library will not be responsible for creating the app window. So, even though the `Window` type is available - I need to retrieve the current app Window. So, if that is possible I would love to know that.. – Shantanu Methikar Nov 03 '22 at 12:09
  • You would need to pass the correct window instance to the UI library from your main application. – Martin Nov 03 '22 at 12:25
  • You could expose the main window instance in your "App" class as static public property like this: https://stackoverflow.com/a/73110313/4424024 Then you could access it using the App class everywhere in your main project. And then you could use this instance when you make a call to a UI library project. E.g. HelperFromLibrary.MethodThatUsesWindow(App.MainWindow). – Martin Nov 03 '22 at 12:32
  • @Martin Unfortunately, in my case - I am not the one developing the main application. I am just developing a library and people would be building on top of it. But I do agree that I will have to inject the right instance of the Window. – Shantanu Methikar Nov 03 '22 at 14:23

1 Answers1

3

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);
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298