diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h
index 049ef755ff..047a0d2c84 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.h
@@ -82,6 +82,8 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT GetExtendTitleBarHeight (double*ret) override;
virtual HRESULT SetExtendTitleBarHeight (double value) override;
+
+ virtual HRESULT GetWindowZOrder (long* zOrder) override;
void EnterFullScreenMode ();
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
index be761e0af7..1cdf81e2fb 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -366,6 +366,20 @@ HRESULT WindowImpl::GetWindowState(AvnWindowState *ret) {
}
}
+HRESULT WindowImpl::GetWindowZOrder(long* zOrder) {
+ START_COM_CALL;
+ @autoreleasepool {
+ if (zOrder == nullptr) {
+ return E_POINTER;
+ }
+
+ // negate the value to match expected z-order in Avalonia
+ // (top-most window should have the highest z-order value)
+ *zOrder = -[Window orderedIndex];
+ return S_OK;
+ }
+}
+
HRESULT WindowImpl::TakeFocusFromChildren() {
START_COM_CALL;
diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs
index a66e5e138f..383515b9f9 100644
--- a/src/Avalonia.Controls/Platform/IWindowImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs
@@ -143,6 +143,15 @@ namespace Avalonia.Platform
/// Sets how big the non-client titlebar area should be.
///
/// -1 for platform default, otherwise the height in DIPs.
- void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight);
+ void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight);
+
+ ///
+ /// Fills zOrder with numbers that represent the relative order of the windows in the z-order.
+ /// The topmost window should have the highest number.
+ /// Both the windows and zOrder lists are expected to be the same length.
+ ///
+ /// A span of windows to get their z-order
+ /// Span to be filled with associated window z-order
+ internal void GetWindowsZOrder(Span windows, Span zOrder);
}
}
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index b2c9ebd986..a3c70d5b82 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -842,6 +842,29 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Sorts the windows ascending by their Z order - the topmost window will be the last in the list.
+ ///
+ ///
+ public static void SortWindowsByZOrder(Window[] windows)
+ {
+ if (windows.Length == 0)
+ return;
+
+ if (windows[0].PlatformImpl is not { } platformImpl)
+ throw new InvalidOperationException("Window.PlatformImpl is null");
+
+#if NET5_0_OR_GREATER
+ Span zOrder = stackalloc long[windows.Length];
+ platformImpl.GetWindowsZOrder(windows, zOrder);
+ zOrder.Sort(windows.AsSpan());
+#else
+ long[] zOrder = new long[windows.Length];
+ platformImpl.GetWindowsZOrder(windows, zOrder);
+ Array.Sort(zOrder, windows);
+#endif
+ }
+
private void UpdateEnabled()
{
bool isEnabled = true;
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
index 9463224b99..f6598d0928 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
@@ -153,5 +153,7 @@ namespace Avalonia.DesignerSupport.Remote
public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
{
}
+
+ public void GetWindowsZOrder(Span windows, Span zOrder) => throw new NotSupportedException();
}
}
diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
index cc183135ba..568e56a130 100644
--- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs
+++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
@@ -178,6 +178,8 @@ namespace Avalonia.DesignerSupport.Remote
{
}
+ public void GetWindowsZOrder(Span windows, Span zOrder) => throw new NotSupportedException();
+
public IPopupPositioner PopupPositioner { get; }
public Action GotInputWhenDisabled { get; set; }
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index 11b264bfdd..10a0afc6da 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/src/Avalonia.Native/WindowImpl.cs
@@ -103,6 +103,8 @@ namespace Avalonia.Native
public Thickness OffScreenMargin { get; } = new Thickness();
+ public IntPtr? ZOrder => _native.WindowZOrder;
+
private bool _isExtended;
public bool IsClientAreaExtendedToDecorations => _isExtended;
@@ -237,5 +239,13 @@ namespace Avalonia.Native
return base.TryGetFeature(featureType);
}
+
+ public void GetWindowsZOrder(Span windows, Span zOrder)
+ {
+ for (int i = 0; i < windows.Length; i++)
+ {
+ zOrder[i] = (windows[i].PlatformImpl as WindowImpl)?.ZOrder?.ToInt64() ?? 0;
+ }
+ }
}
}
diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl
index e3ffe11034..5ea53d22d1 100644
--- a/src/Avalonia.Native/avn.idl
+++ b/src/Avalonia.Native/avn.idl
@@ -2,6 +2,7 @@
@clr-access internal
@clr-map bool int
@clr-map u_int64_t ulong
+@clr-map long IntPtr
@cpp-preamble @@
#pragma once
#include "com.h"
@@ -750,6 +751,7 @@ interface IAvnWindow : IAvnWindowBase
HRESULT SetExtendClientAreaHints(AvnExtendClientAreaChromeHints hints);
HRESULT GetExtendTitleBarHeight(double*ret);
HRESULT SetExtendTitleBarHeight(double value);
+ HRESULT GetWindowZOrder(long*ret);
}
[uuid(939b6599-40a8-4710-a4c8-5d72d8f174fb)]
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 55387057e3..85eda99418 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -1460,5 +1460,79 @@ namespace Avalonia.X11
32, PropertyMode.Replace, new[] { atom }, 1);
}
+
+ ///
+ public void GetWindowsZOrder(Span windows, Span outputZOrder)
+ {
+ // a mapping of parent windows to their children, sorted by z-order (bottom to top)
+ var windowsChildren = new Dictionary>();
+
+ var indexInWindowsSpan = new Dictionary();
+ for (var i = 0; i < windows.Length; i++)
+ if (windows[i].PlatformImpl is { } platformImpl)
+ indexInWindowsSpan[platformImpl.Handle.Handle] = i;
+
+ foreach (var window in windows)
+ {
+ if (window.PlatformImpl is not X11Window x11Window)
+ continue;
+
+ var node = x11Window.Handle.Handle;
+ while (node != IntPtr.Zero)
+ {
+ if (windowsChildren.ContainsKey(node))
+ {
+ break;
+ }
+
+ if (XQueryTree(_x11.Display, node, out _, out var parent,
+ out var childrenPtr, out var childrenCount) == 0)
+ {
+ break;
+ }
+
+ if (childrenPtr != IntPtr.Zero)
+ {
+ var children = (IntPtr*)childrenPtr;
+ windowsChildren[node] = new List(childrenCount);
+ for (var i = 0; i < childrenCount; i++)
+ {
+ windowsChildren[node].Add(children[i]);
+ }
+ XFree(childrenPtr);
+ }
+
+ node = parent;
+ }
+ }
+
+ var stack = new Stack();
+ var zOrder = 0;
+ stack.Push(_x11.RootWindow);
+
+ while (stack.Count > 0)
+ {
+ var currentWindow = stack.Pop();
+
+ if (!windowsChildren.TryGetValue(currentWindow, out var children))
+ {
+ continue;
+ }
+
+ if (indexInWindowsSpan.TryGetValue(currentWindow, out var index))
+ {
+ outputZOrder[index] = zOrder;
+ }
+
+ zOrder++;
+
+ // Children are returned bottom to top, so we need to push them in reverse order
+ // In order to traverse bottom children first
+ for (int i = children.Count - 1; i >= 0; i--)
+ {
+ stack.Push(children[i]);
+ }
+ }
+ }
}
}
diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
index 815452bf30..e0d6df5c53 100644
--- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
+++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
@@ -16,12 +16,15 @@ namespace Avalonia.Headless
{
internal class HeadlessWindowImpl : IWindowImpl, IPopupImpl, IFramebufferPlatformSurface, IHeadlessWindow
{
+ private static int _nextGlobalZOrder = 1;
+
private readonly IKeyboardDevice _keyboard;
private readonly Stopwatch _st = Stopwatch.StartNew();
private readonly Pointer _mousePointer;
private WriteableBitmap? _lastRenderedFrame;
private readonly object _sync = new object();
private readonly PixelFormat _frameBufferFormat;
+ private int _zOrder;
public bool IsPopup { get; }
public HeadlessWindowImpl(bool isPopup, PixelFormat frameBufferFormat)
@@ -80,7 +83,10 @@ namespace Avalonia.Headless
public void Show(bool activate, bool isDialog)
{
if (activate)
+ {
+ _zOrder = _nextGlobalZOrder++;
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
+ }
}
public void Hide()
@@ -102,6 +108,7 @@ namespace Avalonia.Headless
public Action? PositionChanged { get; set; }
public void Activate()
{
+ _zOrder = _nextGlobalZOrder++;
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
}
@@ -412,5 +419,14 @@ namespace Avalonia.Headless
{
}
+
+ public void GetWindowsZOrder(Span windows, Span zOrder)
+ {
+ for (int i = 0; i < windows.Length; ++i)
+ {
+ if (windows[i].PlatformImpl is HeadlessWindowImpl headlessWindowImpl)
+ zOrder[i] = headlessWindowImpl._zOrder;
+ }
+ }
}
}
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 89ae3c5db7..8ed687a330 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -1274,6 +1274,11 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(SystemMetric smIndex);
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern bool EnumChildWindows(IntPtr parentHwnd, EnumWindowsProc enumFunc, IntPtr lParam);
+
+ public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
+
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongPtrW", ExactSpelling = true)]
public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex);
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index e34a64a267..fa2223a466 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -184,6 +184,8 @@ namespace Avalonia.Win32
internal IInputRoot Owner
=> _owner ?? throw new InvalidOperationException($"{nameof(SetInputRoot)} must have been called");
+ internal WindowImpl? ParentImpl => _parent;
+
public Action? Activated { get; set; }
public Func? Closing { get; set; }
@@ -1573,6 +1575,38 @@ namespace Avalonia.Win32
ExtendClientArea();
}
+ ///
+ public void GetWindowsZOrder(Span windows, Span zOrder)
+ {
+ var handlesToIndex = new Dictionary(windows.Length);
+ var outputArray = new long[windows.Length];
+
+ for (int i = 0; i < windows.Length; i++)
+ {
+ if (windows[i].PlatformImpl is WindowImpl platformImpl)
+ handlesToIndex.Add(platformImpl.Handle.Handle, i);
+ }
+
+ long nextZOrder = 0;
+ bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam)
+ {
+ if (handlesToIndex.TryGetValue(hWnd, out var index))
+ {
+ // We negate the z-order so that the topmost window has the highest number.
+ outputArray[index] = -nextZOrder;
+ nextZOrder++;
+ }
+ return nextZOrder < outputArray.Length;
+ }
+
+ EnumChildWindows(IntPtr.Zero, EnumWindowsProc, IntPtr.Zero);
+
+ for (int i = 0; i < windows.Length; i++)
+ {
+ zOrder[i] = outputArray[i];
+ }
+ }
+
///
public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended;