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..33ddaf3795 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -366,6 +366,18 @@ HRESULT WindowImpl::GetWindowState(AvnWindowState *ret) {
}
}
+HRESULT WindowImpl::GetWindowZOrder(long* zOrder) {
+ START_COM_CALL;
+ @autoreleasepool {
+ if (zOrder == nullptr) {
+ return E_POINTER;
+ }
+
+ *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..83d15424c2 100644
--- a/src/Avalonia.Controls/Platform/IWindowImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs
@@ -98,6 +98,12 @@ namespace Avalonia.Platform
///
Thickness OffScreenMargin { get; }
+ ///
+ /// Gets the window's z-order position. The highest value is the front, lower numbers are further back.
+ /// If the z-order can't be determined or the platform doesn't support window stacking, null is returned.
+ ///
+ IntPtr? ZOrder { get; }
+
///
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
///
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index a45f544203..732f38a4e3 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -428,6 +428,12 @@ namespace Avalonia.Controls
set => PlatformImpl?.Move(value);
}
+ ///
+ /// Gets the window's z-order position. The highest value is the front, lower numbers are further back.
+ /// If the z-order can't be determined or the platform doesn't support window stacking, null is returned.
+ ///
+ public IntPtr? WindowZOrder => PlatformImpl?.ZOrder;
+
///
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler
///
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
index 9463224b99..f6f15fba94 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
@@ -39,6 +39,7 @@ namespace Avalonia.DesignerSupport.Remote
public double DesktopScaling => 1.0;
public PixelPoint Position { get; set; }
+ public IntPtr? ZOrder => null;
public Action PositionChanged { get; set; }
public Action Deactivated { get; set; }
public Action Activated { get; set; }
diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
index cc183135ba..7dcaecbb3d 100644
--- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs
+++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
@@ -41,6 +41,7 @@ namespace Avalonia.DesignerSupport.Remote
public IPopupImpl CreatePopup() => new WindowStub(this);
public PixelPoint Position { get; set; }
+ public IntPtr? ZOrder => null;
public Action PositionChanged { get; set; }
public WindowState WindowState { get; set; }
public Action WindowStateChanged { get; set; }
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index 11b264bfdd..62f6bee91a 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/src/Avalonia.Native/WindowImpl.cs
@@ -103,6 +103,15 @@ namespace Avalonia.Native
public Thickness OffScreenMargin { get; } = new Thickness();
+ public IntPtr? ZOrder
+ {
+ get
+ {
+ // macOS returns z-order in reverse order - the topmost window has the lowest z-order
+ return new IntPtr(-_native.WindowZOrder.ToInt64());
+ }
+ }
+
private bool _isExtended;
public bool IsClientAreaExtendedToDecorations => _isExtended;
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..c1d0442e89 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -1133,6 +1133,46 @@ namespace Avalonia.X11
}
}
+ public IntPtr? ZOrder
+ {
+ get
+ {
+ var stack = new Stack();
+ var zOrder = 0;
+ stack.Push(_x11.RootWindow);
+
+ while (stack.Count > 0)
+ {
+ var currentWindow = stack.Pop();
+
+ if (currentWindow == _handle)
+ {
+ return new IntPtr(zOrder);
+ }
+
+ zOrder++;
+
+ if (XQueryTree(_x11.Display, currentWindow, out _, out _,
+ out var childrenPtr, out var childrenCount) != 0)
+ {
+ if (childrenPtr == IntPtr.Zero)
+ continue;
+
+ var children = (IntPtr*)childrenPtr;
+
+ // 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 = childrenCount - 1; i >= 0; i--)
+ stack.Push(children[i]);
+
+ XFree(childrenPtr);
+ }
+ }
+
+ return null;
+ }
+ }
+
public IMouseDevice MouseDevice => _mouse;
public TouchDevice TouchDevice => _touch;
diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
index 5c2e320383..00c1106b78 100644
--- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
+++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
@@ -100,6 +100,7 @@ namespace Avalonia.Headless
public PixelPoint Position { get; set; }
public Action? PositionChanged { get; set; }
+ public IntPtr? ZOrder => null;
public void Activate()
{
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 89ae3c5db7..5c77b40189 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -1274,6 +1274,20 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(SystemMetric smIndex);
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern IntPtr GetWindow(IntPtr hWnd, GetWindowCommand uCmd);
+
+ public enum GetWindowCommand : uint
+ {
+ GW_CHILD = 5,
+ GW_ENABLEDPOPUP = 6,
+ GW_HWNDFIRST = 0,
+ GW_HWNDLAST = 1,
+ GW_HWNDNEXT = 2,
+ GW_HWNDPREV = 3,
+ GW_OWNER = 4
+ }
+
[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 7a022d1c0d..7ffda8a5f7 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -711,6 +711,29 @@ namespace Avalonia.Win32
public void SetEnabled(bool enable) => EnableWindow(_hwnd, enable);
+ public IntPtr? ZOrder
+ {
+ get
+ {
+ var lowestHwnd = GetWindow(_hwnd, GetWindowCommand.GW_HWNDLAST);
+
+ var zOrder = 0;
+ var hwnd = lowestHwnd;
+ while (hwnd != IntPtr.Zero)
+ {
+ if (_hwnd == hwnd)
+ {
+ return new IntPtr(zOrder);
+ }
+
+ hwnd = GetWindow(hwnd, GetWindowCommand.GW_HWNDPREV);
+ zOrder++;
+ }
+
+ return null;
+ }
+ }
+
public void BeginMoveDrag(PointerPressedEventArgs e)
{
e.Pointer.Capture(null);