microsoft / microsoft-ui-xaml

Windows UI Library: the latest Windows 10 native controls and Fluent styles for your applications
MIT License
6.35k stars 678 forks source link

Proposal: Add ability to get hosting Window from XamlRoot or UIElement #9857

Open dotMorten opened 3 months ago

dotMorten commented 3 months ago

Proposal: Add ability to get hosting Window from XamlRoot or UIElement

Summary

Several APIs requires access to the Window handle, but it's currently impossible to get the window that is hosting a visual element. We need an API to make it easier to get the handle.

Rationale

In order to use various pickers like OpenFilePicker, SaveFilePicker etc, you need to first get the Window handle before you can use them as described here.

The approach discussed here for getting that handle requires you to create a MainWindow property on your Application instance in order to do that.

This presents some problems:

One example would be a 3rd party control that includes a save or open button. These buttons would not be able to load or save to/from files, since they have no way of using the file pickers and make them modal to the current window (current as "in the context of that button").

Scope

'Must' implies that the feature should not ship without this capability.

Capability Priority
This proposal will allow developers to access the parent Window or its handle that is currently hosting a visual element Must
castorix commented 3 months ago

You can do something like (GetByVisual returns null on my Windows 10 OS) :

                Microsoft.UI.Composition.Visual visual = Microsoft.UI.Xaml.Hosting.ElementCompositionPreview.GetElementVisual(myButton);
                var ci = Microsoft.UI.Content.ContentIsland.FindAllForCompositor(visual.Compositor);
                //var ci = Microsoft.UI.Content.ContentIsland.GetByVisual(visual);
                if (ci[0] != null)
                {
                    IntPtr hWndHost = Win32Interop.GetWindowFromWindowId(ci[0].Environment.AppWindowId);
                }
lhak commented 3 months ago

XamlRoot.ContentIslandEnvironment.AppWindowId can be also be used here. However, I think functionality should be added to get the XAML window from a UIElement.

ghost1372 commented 3 months ago

XamlRoot.ContentIslandEnvironment.AppWindowId can be also be used here. However, I think functionality should be added to get the XAML window from a UIElement.

excelent, is there any limitation or Are there any special situation that lead to a null result? i want to use it in my class library

ghost1372 commented 3 months ago

also there is another way if there is no uielement!

public static AppWindow GetCurrentAppWindow()
{
    var tops = GetProcessWindowList();

    var firstWinUI3 = tops.FirstOrDefault(w => w.ClassName == "WinUIDesktopWin32WindowClass");

    var windowId = Win32Interop.GetWindowIdFromWindow(firstWinUI3.Handle);

    return AppWindow.GetFromWindowId(windowId);
}

public static IReadOnlyList<Win32Window> GetProcessWindowList()
{
    var process = Process.GetCurrentProcess();
    var list = new List<Win32Window>();
    NativeMethods.EnumWindows((h, l) =>
    {
        var window = new Win32Window(h);
        if (window.ProcessId == process.Id)
        {
            list.Add(window);
        }
        return true;
    }, IntPtr.Zero);
    return list.AsReadOnly();
}

public class Win32Window
{
    public Win32Window(IntPtr handle)
    {
        Handle = handle;
        ThreadId = NativeMethods.GetWindowThreadProcessId(handle, out var processId);
        ProcessId = processId;
    }

    public IntPtr Handle { get; }
    public int ThreadId { get; }
    public int ProcessId { get; }
    public string ClassName => WindowHelper.GetClassName(Handle);
    public string Text => WindowHelper.GetWindowText(Handle);
    public bool IsEnabled => NativeMethods.IsWindowEnabled(Handle);

    public override string ToString()
    {
        var s = ClassName;
        var text = Text;
        if (text != null)
        {
            s += " '" + text + "'";
        }
        return s;
    }
}
public static string GetWindowText(IntPtr hwnd)
{
    var sb = new StringBuilder(1024);
    NativeMethods.GetWindowText(hwnd, sb, sb.Capacity - 1);
    return sb.ToString();
}

public static string GetClassName(IntPtr hwnd)
{
    var sb = new StringBuilder(256);
    NativeMethods.GetClassName(hwnd, sb, sb.Capacity - 1);
    return sb.ToString();
}

public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);

[DllImport(ExternDll.User32, SetLastError = true)]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

[DllImport(ExternDll.User32)]
public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);

[DllImport(ExternDll.User32)]
public static extern bool IsWindowEnabled(IntPtr hwnd);

[DllImport(ExternDll.User32, CharSet = CharSet.Unicode)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

[DllImport(ExternDll.User32, CharSet = CharSet.Unicode)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);