diff --git a/samples/IntegrationTestApp/App.axaml.cs b/samples/IntegrationTestApp/App.axaml.cs
index c55f5fb392..f33163acc5 100644
--- a/samples/IntegrationTestApp/App.axaml.cs
+++ b/samples/IntegrationTestApp/App.axaml.cs
@@ -4,7 +4,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
-using Avalonia.Media;
using MiniMvvm;
namespace IntegrationTestApp
diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj
index 7f8c45533a..517537826e 100644
--- a/samples/IntegrationTestApp/IntegrationTestApp.csproj
+++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj
@@ -29,12 +29,6 @@
-
-
- TopmostWindowTest.axaml
-
-
-
diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index ec49e4bb10..effed7964a 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -2,12 +2,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:integrationTestApp="using:IntegrationTestApp"
+ xmlns:vm="using:IntegrationTestApp.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="IntegrationTestApp.MainWindow"
Icon="/Assets/icon.ico"
Title="IntegrationTestApp"
- x:DataType="integrationTestApp:MainWindow">
+ x:DataType="vm:MainWindowViewModel">
@@ -18,7 +18,7 @@
-
+
@@ -27,235 +27,25 @@
WindowState:
-
+
-
-
-
-
- TextBlockWithName
-
- TextBlockWithNameAndAutomationId
-
- Label for TextBox
-
- Foo
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sample RadioButton
-
- Three States: Option 1
- Three States: Option 2
-
-
-
-
-
-
- Unchecked
- Checked
- ThreeState
-
-
-
-
-
-
- Item 0
- Item 1
-
- Wrap Selection
-
-
-
-
-
-
-
-
-
-
-
-
-
- Tray Icon Clicked
- Tray Icon Menu Clicked
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- None
-
-
-
-
-
-
-
-
-
-
- Show Dialog
-
-
-
-
-
-
-
-
-
-
- NonOwned
- Owned
- Modal
-
-
- Manual
- CenterScreen
- CenterOwner
-
-
- Normal
- Minimized
- Maximized
- FullScreen
-
-
- None
- BorderOnly
- Full
-
- ExtendClientAreaToDecorationsHint
- Can Resize
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index deced2f97d..9a69563a77 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -1,18 +1,8 @@
-using System;
using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using Avalonia;
-using Avalonia.Automation;
using Avalonia.Controls;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Primitives.PopupPositioning;
-using Avalonia.Input;
-using Avalonia.Interactivity;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.VisualTree;
+using IntegrationTestApp.Models;
+using IntegrationTestApp.Pages;
+using IntegrationTestApp.ViewModels;
namespace IntegrationTestApp
{
@@ -24,374 +14,66 @@ namespace IntegrationTestApp
Name = "MainWindow";
InitializeComponent();
- InitializeViewMenu();
- InitializeGesturesTab();
- this.AttachDevTools();
- AppOverlayPopups.Text = Program.OverlayPopups ? "Overlay Popups" : "Native Popups";
+ var viewModel = new MainWindowViewModel(CreatePages());
+ InitializeViewMenu(viewModel.Pages);
- AddHandler(Button.ClickEvent, OnButtonClick);
- ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
- DataContext = this;
+ DataContext = viewModel;
+ AppOverlayPopups.Text = Program.OverlayPopups ? "Overlay Popups" : "Native Popups";
}
- public List ListBoxItems { get; }
+ private MainWindowViewModel? ViewModel => (MainWindowViewModel?)DataContext;
- private void InitializeViewMenu()
+ private void InitializeViewMenu(IEnumerable pages)
{
var viewMenu = (NativeMenuItem?)NativeMenu.GetMenu(this)?.Items[1];
- foreach (var tabItem in MainTabs.Items.Cast())
+ foreach (var page in pages)
{
var menuItem = new NativeMenuItem
{
- Header = (string?)tabItem.Header,
- ToolTip = $"Tip:{(string?)tabItem.Header}",
- IsChecked = tabItem.IsSelected,
+ Header = (string?)page.Name,
+ ToolTip = $"Tip:{(string?)page.Name}",
ToggleType = NativeMenuItemToggleType.Radio,
};
- menuItem.Click += (_, _) => tabItem.IsSelected = true;
- viewMenu?.Menu?.Items.Add(menuItem);
- }
- }
-
- private void OnShowWindow()
- {
- var sizeTextBox = ShowWindowSize;
- var modeComboBox = ShowWindowMode;
- var locationComboBox = ShowWindowLocation;
- var stateComboBox = ShowWindowState;
- var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
- var systemDecorations = ShowWindowSystemDecorations;
- var extendClientArea = ShowWindowExtendClientAreaToDecorationsHint;
- var canResizeCheckBox = ShowWindowCanResize;
- var owner = (Window)this.GetVisualRoot()!;
-
- var window = new ShowWindowTest
- {
- WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
- CanResize = canResizeCheckBox.IsChecked ?? false,
- };
-
- if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
- {
- // Make sure the windows have unique names and AutomationIds.
- var existing = lifetime.Windows.OfType().Count();
- if (existing > 0)
- {
- AutomationProperties.SetAutomationId(window, window.Name + (existing + 1));
- window.Title += $" {existing + 1}";
- }
- }
-
- if (size.HasValue)
- {
- window.Width = size.Value.Width;
- window.Height = size.Value.Height;
- }
-
- sizeTextBox.Text = string.Empty;
- window.ExtendClientAreaToDecorationsHint = extendClientArea.IsChecked ?? false;
- window.SystemDecorations = (SystemDecorations)systemDecorations.SelectedIndex;
- window.WindowState = (WindowState)stateComboBox.SelectedIndex;
-
- switch (modeComboBox.SelectedIndex)
- {
- case 0:
- window.Show();
- break;
- case 1:
- window.Show(owner);
- break;
- case 2:
- window.ShowDialog(owner);
- break;
- }
- }
-
- private void OnShowTransparentWindow()
- {
- // Show a background window to make sure the color behind the transparent window is
- // a known color (green).
- var backgroundWindow = new Window
- {
- Title = "Transparent Window Background",
- Name = "TransparentWindowBackground",
- Width = 300,
- Height = 300,
- Background = Brushes.Green,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- };
-
- // This is the transparent window with a red circle.
- var window = new Window
- {
- Title = "Transparent Window",
- Name = "TransparentWindow",
- SystemDecorations = SystemDecorations.None,
- Background = Brushes.Transparent,
- TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent },
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- Width = 200,
- Height = 200,
- Content = new Border
+ menuItem.Click += (_, _) =>
{
- Background = Brushes.Red,
- CornerRadius = new CornerRadius(100),
- }
- };
-
- window.PointerPressed += (_, _) =>
- {
- window.Close();
- backgroundWindow.Close();
- };
-
- backgroundWindow.Show(this);
- window.Show(backgroundWindow);
- }
-
- private void OnShowTransparentPopup()
- {
- var popup = new Popup
- {
- WindowManagerAddShadowHint = false,
- Placement = PlacementMode.AnchorAndGravity,
- PlacementAnchor = PopupAnchor.Top,
- PlacementGravity = PopupGravity.Bottom,
- Width= 200,
- Height= 200,
- Child = new Border
- {
- Background = Brushes.Red,
- CornerRadius = new CornerRadius(100),
- }
- };
-
- // Show a background window to make sure the color behind the transparent window is
- // a known color (green).
- var backgroundWindow = new Window
- {
- Title = "Transparent Popup Background",
- Name = "TransparentPopupBackground",
- Width = 200,
- Height = 200,
- Background = Brushes.Green,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- Content = new Border
- {
- Name = "PopupContainer",
- Child = popup,
- [AutomationProperties.AccessibilityViewProperty] = AccessibilityView.Content,
- }
- };
-
- backgroundWindow.PointerPressed += (_, _) => backgroundWindow.Close();
- backgroundWindow.Show(this);
-
- popup.Open();
- }
-
- private void OnSendToBack()
- {
- var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
-
- foreach (var window in lifetime.Windows.ToArray())
- {
- window.Activate();
- }
- }
-
- private void OnRestoreAll()
- {
- var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
-
- foreach (var window in lifetime.Windows.ToArray())
- {
- window.Show();
- if (window.WindowState == WindowState.Minimized)
- window.WindowState = WindowState.Normal;
- }
- }
-
- private void OnShowTopmostWindow()
- {
- var mainWindow = new TopmostWindowTest("OwnerWindow") { Topmost = true, Title = "Owner Window"};
- var ownedWindow = new TopmostWindowTest("OwnedWindow") { WindowStartupLocation = WindowStartupLocation.CenterOwner, Title = "Owned Window"};
- mainWindow.Show();
-
- ownedWindow.Show(mainWindow);
- }
-
- private void OnToggleTrayIconVisible()
- {
- var icon = TrayIcon.GetIcons(Application.Current!)!.FirstOrDefault()!;
- icon.IsVisible = !icon.IsVisible;
- }
-
- private void InitializeGesturesTab()
- {
- var gestureBorder = GestureBorder;
- var gestureBorder2 = GestureBorder2;
- var lastGesture = LastGesture;
- var resetGestures = ResetGestures;
- gestureBorder.Tapped += (_, _) => lastGesture.Text = "Tapped";
-
- gestureBorder.DoubleTapped += (_, _) =>
- {
- lastGesture.Text = "DoubleTapped";
-
- // Testing #8733
- gestureBorder.IsVisible = false;
- gestureBorder2.IsVisible = true;
- };
-
- gestureBorder2.DoubleTapped += (_, _) =>
- {
- lastGesture.Text = "DoubleTapped2";
- };
-
- Gestures.AddRightTappedHandler(gestureBorder, (_, _) => lastGesture.Text = "RightTapped");
-
- resetGestures.Click += (_, _) =>
- {
- lastGesture.Text = string.Empty;
- gestureBorder.IsVisible = true;
- gestureBorder2.IsVisible = false;
- };
- }
-
- private void MenuClicked(object? sender, RoutedEventArgs e)
- {
- var clickedMenuItemTextBlock = ClickedMenuItem;
- clickedMenuItemTextBlock.Text = (sender as MenuItem)?.Header?.ToString();
- }
-
- private void OnButtonClick(object? sender, RoutedEventArgs e)
- {
- var source = e.Source as Button;
-
- if (source?.Name == nameof(ComboBoxSelectionClear))
- BasicComboBox.SelectedIndex = -1;
- if (source?.Name == nameof(ComboBoxSelectFirst))
- BasicComboBox.SelectedIndex = 0;
- if (source?.Name == nameof(ListBoxSelectionClear))
- BasicListBox.SelectedIndex = -1;
- if (source?.Name == nameof(MenuClickedMenuItemReset))
- ClickedMenuItem.Text = "None";
- if (source?.Name == nameof(ResetSliders))
- HorizontalSlider.Value = 50;
- if (source?.Name == nameof(ShowTransparentWindow))
- OnShowTransparentWindow();
- if (source?.Name == nameof(ShowTransparentPopup))
- OnShowTransparentPopup();
- if (source?.Name == nameof(ShowWindow))
- OnShowWindow();
- if (source?.Name == nameof(SendToBack))
- OnSendToBack();
- if (source?.Name == nameof(EnterFullscreen))
- WindowState = WindowState.FullScreen;
- if (source?.Name == nameof(ExitFullscreen))
- WindowState = WindowState.Normal;
- if (source?.Name == nameof(ShowTopmostWindow))
- OnShowTopmostWindow();
- if (source?.Name == nameof(RestoreAll))
- OnRestoreAll();
- if (source?.Name == nameof(ApplyWindowDecorations))
- OnApplyWindowDecorations(this);
- if (source?.Name == nameof(ShowNewWindowDecorations))
- OnShowNewWindowDecorations();
- if (source?.Name == nameof(ToggleTrayIconVisible))
- OnToggleTrayIconVisible();
- if (source?.Name == nameof(ScreenRefresh))
- OnScreenRefresh();
- }
-
- private void OnApplyWindowDecorations(Window window)
- {
- window.ExtendClientAreaToDecorationsHint = WindowExtendClientAreaToDecorationsHint.IsChecked!.Value;
- window.ExtendClientAreaTitleBarHeightHint =
- int.TryParse(WindowTitleBarHeightHint.Text, out var val) ? val / DesktopScaling : -1;
- window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome
- | (WindowForceSystemChrome.IsChecked == true ? ExtendClientAreaChromeHints.SystemChrome : 0)
- | (WindowPreferSystemChrome.IsChecked == true ? ExtendClientAreaChromeHints.PreferSystemChrome : 0)
- | (WindowMacThickSystemChrome.IsChecked == true ? ExtendClientAreaChromeHints.OSXThickTitleBar : 0);
- AdjustOffsets(window);
-
- window.Background = Brushes.Transparent;
- window.PropertyChanged += WindowOnPropertyChanged;
-
- void WindowOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
- {
- var window = (Window)sender!;
- if (e.Property == OffScreenMarginProperty || e.Property == WindowDecorationMarginProperty)
- {
- AdjustOffsets(window);
- }
- }
-
- void AdjustOffsets(Window window)
- {
- window.Padding = window.OffScreenMargin;
- ((Control)window.Content!).Margin = window.WindowDecorationMargin;
-
- WindowDecorationProperties.Text =
- $"{window.OffScreenMargin.Top * DesktopScaling} {window.WindowDecorationMargin.Top * DesktopScaling} {window.IsExtendedIntoWindowDecorations}";
- }
- }
-
- private void OnShowNewWindowDecorations()
- {
- var window = new ShowWindowTest();
- OnApplyWindowDecorations(window);
- window.Show();
- }
-
- private void PointerPageShowDialogPressed(object? sender, PointerPressedEventArgs e)
- {
- void CaptureLost(object? sender, PointerCaptureLostEventArgs e)
- {
- PointerCaptureStatus.Text = "None";
- ((Control)sender!).PointerCaptureLost -= CaptureLost;
- }
-
- var captured = e.Pointer.Captured as Control;
+ if (ViewModel is { } viewModel)
+ viewModel.SelectedPage = page;
+ };
- if (captured is not null)
- {
- captured.PointerCaptureLost += CaptureLost;
+ viewMenu?.Menu?.Items.Add(menuItem);
}
-
- PointerCaptureStatus.Text = captured?.ToString() ?? "None";
-
- var dialog = new Window
- {
- Width = 200,
- Height = 200,
- };
-
- dialog.Content = new Button
- {
- Content = "Close",
- Command = new DelegateCommand(() => dialog.Close()),
- };
-
- dialog.ShowDialog(this);
}
- private Screen? _lastScreen;
- private void OnScreenRefresh()
- {
- var lastScreen = _lastScreen;
- var screen = _lastScreen = Screens.ScreenFromWindow(this);
- ScreenName.Text = screen?.DisplayName;
- ScreenHandle.Text = screen?.TryGetPlatformHandle()?.ToString();
- ScreenBounds.Text = screen?.Bounds.ToString();
- ScreenWorkArea.Text = screen?.WorkingArea.ToString();
- ScreenScaling.Text = screen?.Scaling.ToString(CultureInfo.InvariantCulture);
- ScreenOrientation.Text = screen?.CurrentOrientation.ToString();
- ScreenSameReference.Text = ReferenceEquals(lastScreen, screen).ToString();
+ private void Pager_SelectionChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (Pager.SelectedItem is Page page)
+ PagerContent.Child = page.CreateContent();
+ }
+
+ private static IEnumerable CreatePages()
+ {
+ return
+ [
+ new("Automation", () => new AutomationPage()),
+ new("Button", () => new ButtonPage()),
+ new("CheckBox", () => new CheckBoxPage()),
+ new("ComboBox", () => new ComboBoxPage()),
+ new("ContextMenu", () => new ContextMenuPage()),
+ new("DesktopPage", () => new DesktopPage()),
+ new("Gestures", () => new GesturesPage()),
+ new("ListBox", () => new ListBoxPage()),
+ new("Menu", () => new MenuPage()),
+ new("Pointer", () => new PointerPage()),
+ new("RadioButton", () => new RadioButtonPage()),
+ new("Screens", () => new ScreensPage()),
+ new("ScrollBar", () => new ScrollBarPage()),
+ new("Slider", () => new SliderPage()),
+ new("Window Decorations", () => new WindowDecorationsPage()),
+ new("Window", () => new WindowPage()),
+ ];
}
}
}
diff --git a/samples/IntegrationTestApp/Models/Page.cs b/samples/IntegrationTestApp/Models/Page.cs
new file mode 100644
index 0000000000..bccf1fd3d0
--- /dev/null
+++ b/samples/IntegrationTestApp/Models/Page.cs
@@ -0,0 +1,6 @@
+using System;
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Models;
+
+internal record Page(string Name, Func CreateContent);
diff --git a/samples/IntegrationTestApp/Pages/AutomationPage.axaml b/samples/IntegrationTestApp/Pages/AutomationPage.axaml
new file mode 100644
index 0000000000..9d6ef43b2a
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/AutomationPage.axaml
@@ -0,0 +1,17 @@
+
+
+ TextBlockWithName
+
+ TextBlockWithNameAndAutomationId
+
+ Label for TextBox
+
+ Foo
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/AutomationPage.axaml.cs b/samples/IntegrationTestApp/Pages/AutomationPage.axaml.cs
new file mode 100644
index 0000000000..f79000126c
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/AutomationPage.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class AutomationPage : UserControl
+{
+ public AutomationPage()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/ButtonPage.axaml b/samples/IntegrationTestApp/Pages/ButtonPage.axaml
new file mode 100644
index 0000000000..628d89fde8
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ButtonPage.axaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/ButtonPage.axaml.cs b/samples/IntegrationTestApp/Pages/ButtonPage.axaml.cs
new file mode 100644
index 0000000000..540ce839a3
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ButtonPage.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ButtonPage : UserControl
+{
+ public ButtonPage()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml b/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml
new file mode 100644
index 0000000000..cdb61b53a4
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml
@@ -0,0 +1,12 @@
+
+
+ Unchecked
+ Checked
+ ThreeState
+
+
diff --git a/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml.cs b/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml.cs
new file mode 100644
index 0000000000..6863f62387
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/CheckBoxPage.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class CheckBoxPage : UserControl
+{
+ public CheckBoxPage()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml b/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml
new file mode 100644
index 0000000000..6068b06e85
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml
@@ -0,0 +1,16 @@
+
+
+
+ Item 0
+ Item 1
+
+ Wrap Selection
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml.cs b/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml.cs
new file mode 100644
index 0000000000..eb9b66de76
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ComboBoxPage.axaml.cs
@@ -0,0 +1,22 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ComboBoxPage : UserControl
+{
+ public ComboBoxPage()
+ {
+ InitializeComponent();
+ }
+
+ private void ComboBoxSelectionClear_Click(object? sender, RoutedEventArgs e)
+ {
+ BasicComboBox.SelectedIndex = -1;
+ }
+
+ private void ComboBoxSelectFirst_Click(object? sender, RoutedEventArgs e)
+ {
+ BasicComboBox.SelectedIndex = 0;
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml b/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml
new file mode 100644
index 0000000000..7d494bb277
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml.cs b/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml.cs
new file mode 100644
index 0000000000..e42d9f232e
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ContextMenuPage.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ContextMenuPage : UserControl
+{
+ public ContextMenuPage()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/DesktopPage.axaml b/samples/IntegrationTestApp/Pages/DesktopPage.axaml
new file mode 100644
index 0000000000..a5495bd347
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/DesktopPage.axaml
@@ -0,0 +1,14 @@
+
+
+ Tray Icon Clicked
+ Tray Icon Menu Clicked
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs b/samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs
new file mode 100644
index 0000000000..945727d48e
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs
@@ -0,0 +1,19 @@
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class DesktopPage : UserControl
+{
+ public DesktopPage()
+ {
+ InitializeComponent();
+ }
+
+ private void ToggleTrayIconVisible_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ var icon = TrayIcon.GetIcons(Application.Current!)!.FirstOrDefault()!;
+ icon.IsVisible = !icon.IsVisible;
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/GesturesPage.axaml b/samples/IntegrationTestApp/Pages/GesturesPage.axaml
new file mode 100644
index 0000000000..eb028e226f
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/GesturesPage.axaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/GesturesPage.axaml.cs b/samples/IntegrationTestApp/Pages/GesturesPage.axaml.cs
new file mode 100644
index 0000000000..907edb973c
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/GesturesPage.axaml.cs
@@ -0,0 +1,44 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class GesturesPage : UserControl
+{
+ public GesturesPage()
+ {
+ InitializeComponent();
+ }
+
+ private void GestureBorder_Tapped(object? sender, TappedEventArgs e)
+ {
+ LastGesture.Text = "Tapped";
+ }
+
+ private void GestureBorder_DoubleTapped(object? sender, TappedEventArgs e)
+ {
+ LastGesture.Text = "DoubleTapped";
+
+ // Testing #8733
+ GestureBorder.IsVisible = false;
+ GestureBorder2.IsVisible = true;
+ }
+
+ private void GestureBorder_RightTapped(object? sender, RoutedEventArgs e)
+ {
+ LastGesture.Text = "RightTapped";
+ }
+
+ private void GestureBorder2_DoubleTapped(object? sender, TappedEventArgs e)
+ {
+ LastGesture.Text = "DoubleTapped2";
+ }
+
+ private void ResetGestures_Click(object? sender, RoutedEventArgs e)
+ {
+ LastGesture.Text = string.Empty;
+ GestureBorder.IsVisible = true;
+ GestureBorder2.IsVisible = false;
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/ListBoxPage.axaml b/samples/IntegrationTestApp/Pages/ListBoxPage.axaml
new file mode 100644
index 0000000000..4e23cd8e37
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ListBoxPage.axaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/ListBoxPage.axaml.cs b/samples/IntegrationTestApp/Pages/ListBoxPage.axaml.cs
new file mode 100644
index 0000000000..3bff7d9231
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ListBoxPage.axaml.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ListBoxPage : UserControl
+{
+ public ListBoxPage()
+ {
+ InitializeComponent();
+ ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
+ DataContext = this;
+ }
+
+ public List ListBoxItems { get; }
+
+ private void ListBoxSelectionClear_Click(object? sender, RoutedEventArgs e)
+ {
+ BasicListBox.SelectedIndex = -1;
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/MenuPage.axaml b/samples/IntegrationTestApp/Pages/MenuPage.axaml
new file mode 100644
index 0000000000..36c2636f3d
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/MenuPage.axaml
@@ -0,0 +1,22 @@
+
+
+
+
+ None
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/MenuPage.axaml.cs b/samples/IntegrationTestApp/Pages/MenuPage.axaml.cs
new file mode 100644
index 0000000000..8be695f8ab
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/MenuPage.axaml.cs
@@ -0,0 +1,24 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class MenuPage : UserControl
+{
+ public MenuPage()
+ {
+ InitializeComponent();
+ }
+
+ private void MenuClicked(object? sender, RoutedEventArgs e)
+ {
+ var clickedMenuItemTextBlock = ClickedMenuItem;
+ clickedMenuItemTextBlock.Text = (sender as MenuItem)?.Header?.ToString();
+ }
+
+
+ private void MenuClickedMenuItemReset_Click(object? sender, RoutedEventArgs e)
+ {
+ ClickedMenuItem.Text = "None";
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/PointerPage.axaml b/samples/IntegrationTestApp/Pages/PointerPage.axaml
new file mode 100644
index 0000000000..ba6016c9b5
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/PointerPage.axaml
@@ -0,0 +1,19 @@
+
+
+
+
+ Show Dialog
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/PointerPage.axaml.cs b/samples/IntegrationTestApp/Pages/PointerPage.axaml.cs
new file mode 100644
index 0000000000..b34798be10
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/PointerPage.axaml.cs
@@ -0,0 +1,47 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class PointerPage : UserControl
+{
+ public PointerPage()
+ {
+ InitializeComponent();
+ }
+
+ private void PointerPageShowDialog_PointerPressed(object? sender, PointerPressedEventArgs e)
+ {
+ void CaptureLost(object? sender, PointerCaptureLostEventArgs e)
+ {
+ PointerCaptureStatus.Text = "None";
+ ((Control)sender!).PointerCaptureLost -= CaptureLost;
+ }
+
+ var window = TopLevel.GetTopLevel(this) as Window ??
+ throw new AvaloniaInternalException("PointerPage is not attached to a Window.");
+ var captured = e.Pointer.Captured as Control;
+
+ if (captured is not null)
+ {
+ captured.PointerCaptureLost += CaptureLost;
+ }
+
+ PointerCaptureStatus.Text = captured?.ToString() ?? "None";
+
+ var dialog = new Window
+ {
+ Width = 200,
+ Height = 200,
+ };
+
+ dialog.Content = new Button
+ {
+ Content = "Close",
+ Command = new DelegateCommand(() => dialog.Close()),
+ };
+
+ dialog.ShowDialog(window);
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml b/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml
new file mode 100644
index 0000000000..f66b9b9f7b
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml
@@ -0,0 +1,14 @@
+
+
+ Sample RadioButton
+
+ Three States: Option 1
+ Three States: Option 2
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml.cs b/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml.cs
new file mode 100644
index 0000000000..115ff6f2f0
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/RadioButtonPage.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class RadioButtonPage : UserControl
+{
+ public RadioButtonPage()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/ScreensPage.axaml b/samples/IntegrationTestApp/Pages/ScreensPage.axaml
new file mode 100644
index 0000000000..2d95c4719a
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ScreensPage.axaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/ScreensPage.axaml.cs b/samples/IntegrationTestApp/Pages/ScreensPage.axaml.cs
new file mode 100644
index 0000000000..a9348f86e3
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ScreensPage.axaml.cs
@@ -0,0 +1,32 @@
+using System.Globalization;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Platform;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ScreensPage : UserControl
+{
+ private Screen? _lastScreen;
+
+ public ScreensPage()
+ {
+ InitializeComponent();
+ }
+
+ private void ScreenRefresh_Click(object? sender, RoutedEventArgs e)
+ {
+ var window = TopLevel.GetTopLevel(this) as Window ??
+ throw new AvaloniaInternalException("ScreensPage is not attached to a Window.");
+ var lastScreen = _lastScreen;
+ var screen = _lastScreen = window.Screens.ScreenFromWindow(window);
+ ScreenName.Text = screen?.DisplayName;
+ ScreenHandle.Text = screen?.TryGetPlatformHandle()?.ToString();
+ ScreenBounds.Text = screen?.Bounds.ToString();
+ ScreenWorkArea.Text = screen?.WorkingArea.ToString();
+ ScreenScaling.Text = screen?.Scaling.ToString(CultureInfo.InvariantCulture);
+ ScreenOrientation.Text = screen?.CurrentOrientation.ToString();
+ ScreenSameReference.Text = ReferenceEquals(lastScreen, screen).ToString();
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/ScrollBarPage.axaml b/samples/IntegrationTestApp/Pages/ScrollBarPage.axaml
new file mode 100644
index 0000000000..6a3b7e4e37
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ScrollBarPage.axaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/ScrollBarPage.axaml.cs b/samples/IntegrationTestApp/Pages/ScrollBarPage.axaml.cs
new file mode 100644
index 0000000000..60c7eb91ad
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/ScrollBarPage.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class ScrollBarPage : UserControl
+{
+ public ScrollBarPage()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/SliderPage.axaml b/samples/IntegrationTestApp/Pages/SliderPage.axaml
new file mode 100644
index 0000000000..ae6700dfed
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/SliderPage.axaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/SliderPage.axaml.cs b/samples/IntegrationTestApp/Pages/SliderPage.axaml.cs
new file mode 100644
index 0000000000..72f0174fed
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/SliderPage.axaml.cs
@@ -0,0 +1,17 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class SliderPage : UserControl
+{
+ public SliderPage()
+ {
+ InitializeComponent();
+ }
+
+ private void ResetSliders_Click(object? sender, RoutedEventArgs e)
+ {
+ HorizontalSlider.Value = 50;
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml b/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml
new file mode 100644
index 0000000000..21a5b1d883
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml.cs b/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml.cs
new file mode 100644
index 0000000000..72fa15c14f
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/WindowDecorationsPage.axaml.cs
@@ -0,0 +1,63 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class WindowDecorationsPage : UserControl
+{
+ public WindowDecorationsPage()
+ {
+ InitializeComponent();
+ }
+
+ private void SetWindowDecorations(Window window)
+ {
+ window.ExtendClientAreaToDecorationsHint = WindowExtendClientAreaToDecorationsHint.IsChecked!.Value;
+ window.ExtendClientAreaTitleBarHeightHint =
+ int.TryParse(WindowTitleBarHeightHint.Text, out var val) ? val / window.DesktopScaling : -1;
+ window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome
+ | (WindowForceSystemChrome.IsChecked == true ? ExtendClientAreaChromeHints.SystemChrome : 0)
+ | (WindowPreferSystemChrome.IsChecked == true ? ExtendClientAreaChromeHints.PreferSystemChrome : 0)
+ | (WindowMacThickSystemChrome.IsChecked == true ? ExtendClientAreaChromeHints.OSXThickTitleBar : 0);
+ AdjustOffsets(window);
+
+ window.Background = Brushes.Transparent;
+ window.PropertyChanged += WindowOnPropertyChanged;
+
+ void WindowOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ {
+ var window = (Window)sender!;
+ if (e.Property == Window.OffScreenMarginProperty || e.Property == Window.WindowDecorationMarginProperty)
+ {
+ AdjustOffsets(window);
+ }
+ }
+
+ void AdjustOffsets(Window window)
+ {
+ var scaling = window.DesktopScaling;
+
+ window.Padding = window.OffScreenMargin;
+ ((Control)window.Content!).Margin = window.WindowDecorationMargin;
+
+ WindowDecorationProperties.Text =
+ $"{window.OffScreenMargin.Top * scaling} {window.WindowDecorationMargin.Top * scaling} {window.IsExtendedIntoWindowDecorations}";
+ }
+ }
+
+ private void ApplyWindowDecorations_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ var window = TopLevel.GetTopLevel(this) as Window ??
+ throw new AvaloniaInternalException("WindowDecorationsPage is not attached to a Window.");
+ SetWindowDecorations(window);
+ }
+
+ private void ShowNewWindowDecorations_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ var window = new ShowWindowTest();
+ SetWindowDecorations(window);
+ window.Show();
+ }
+}
diff --git a/samples/IntegrationTestApp/Pages/WindowPage.axaml b/samples/IntegrationTestApp/Pages/WindowPage.axaml
new file mode 100644
index 0000000000..db2298b06e
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/WindowPage.axaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+ NonOwned
+ Owned
+ Modal
+
+
+ Manual
+ CenterScreen
+ CenterOwner
+
+
+ Normal
+ Minimized
+ Maximized
+ FullScreen
+
+
+ None
+ BorderOnly
+ Full
+
+ ExtendClientAreaToDecorationsHint
+ Can Resize
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs b/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs
new file mode 100644
index 0000000000..5549e537d3
--- /dev/null
+++ b/samples/IntegrationTestApp/Pages/WindowPage.axaml.cs
@@ -0,0 +1,199 @@
+using System.Linq;
+using Avalonia;
+using Avalonia.Automation;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Primitives.PopupPositioning;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+
+namespace IntegrationTestApp.Pages;
+
+public partial class WindowPage : UserControl
+{
+ public WindowPage()
+ {
+ InitializeComponent();
+ }
+
+ private Window Window => TopLevel.GetTopLevel(this) as Window ??
+ throw new AvaloniaInternalException("WindowPage is not attached to a Window.");
+
+ private void ShowWindow_Click(object? sender, RoutedEventArgs e)
+ {
+ var size = !string.IsNullOrWhiteSpace(ShowWindowSize.Text) ? Size.Parse(ShowWindowSize.Text) : (Size?)null;
+ var window = new ShowWindowTest
+ {
+ WindowStartupLocation = (WindowStartupLocation)ShowWindowLocation.SelectedIndex,
+ CanResize = ShowWindowCanResize.IsChecked ?? false,
+ };
+
+ if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
+ {
+ // Make sure the windows have unique names and AutomationIds.
+ var existing = lifetime.Windows.OfType().Count();
+ if (existing > 0)
+ {
+ AutomationProperties.SetAutomationId(window, window.Name + (existing + 1));
+ window.Title += $" {existing + 1}";
+ }
+ }
+
+ if (size.HasValue)
+ {
+ window.Width = size.Value.Width;
+ window.Height = size.Value.Height;
+ }
+
+ ShowWindowSize.Text = string.Empty;
+ window.ExtendClientAreaToDecorationsHint = ShowWindowExtendClientAreaToDecorationsHint.IsChecked ?? false;
+ window.SystemDecorations = (SystemDecorations)ShowWindowSystemDecorations.SelectedIndex;
+ window.WindowState = (WindowState)ShowWindowState.SelectedIndex;
+
+ switch (ShowWindowMode.SelectedIndex)
+ {
+ case 0:
+ window.Show();
+ break;
+ case 1:
+ window.Show(Window);
+ break;
+ case 2:
+ window.ShowDialog(Window);
+ break;
+ }
+ }
+
+ private void ShowTransparentWindow_Click(object? sender, RoutedEventArgs e)
+ {
+ // Show a background window to make sure the color behind the transparent window is
+ // a known color (green).
+ var backgroundWindow = new Window
+ {
+ Title = "Transparent Window Background",
+ Name = "TransparentWindowBackground",
+ Width = 300,
+ Height = 300,
+ Background = Brushes.Green,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ };
+
+ // This is the transparent window with a red circle.
+ var window = new Window
+ {
+ Title = "Transparent Window",
+ Name = "TransparentWindow",
+ SystemDecorations = SystemDecorations.None,
+ Background = Brushes.Transparent,
+ TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent },
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ Width = 200,
+ Height = 200,
+ Content = new Border
+ {
+ Background = Brushes.Red,
+ CornerRadius = new CornerRadius(100),
+ }
+ };
+
+ window.PointerPressed += (_, _) =>
+ {
+ window.Close();
+ backgroundWindow.Close();
+ };
+
+ backgroundWindow.Show(Window);
+ window.Show(backgroundWindow);
+ }
+
+ private void ShowTransparentPopup_Click(object? sender, RoutedEventArgs e)
+ {
+ var popup = new Popup
+ {
+ WindowManagerAddShadowHint = false,
+ Placement = PlacementMode.AnchorAndGravity,
+ PlacementAnchor = PopupAnchor.Top,
+ PlacementGravity = PopupGravity.Bottom,
+ Width = 200,
+ Height = 200,
+ Child = new Border
+ {
+ Background = Brushes.Red,
+ CornerRadius = new CornerRadius(100),
+ }
+ };
+
+ // Show a background window to make sure the color behind the transparent window is
+ // a known color (green).
+ var backgroundWindow = new Window
+ {
+ Title = "Transparent Popup Background",
+ Name = "TransparentPopupBackground",
+ Width = 200,
+ Height = 200,
+ Background = Brushes.Green,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ Content = new Border
+ {
+ Name = "PopupContainer",
+ Child = popup,
+ [AutomationProperties.AccessibilityViewProperty] = AccessibilityView.Content,
+ }
+ };
+
+ backgroundWindow.PointerPressed += (_, _) => backgroundWindow.Close();
+ backgroundWindow.Show(Window);
+
+ popup.Open();
+ }
+
+ private void SendToBack_Click(object? sender, RoutedEventArgs e)
+ {
+ var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
+
+ foreach (var window in lifetime.Windows.ToArray())
+ {
+ window.Activate();
+ }
+ }
+
+ private void EnterFullscreen_Click(object? sender, RoutedEventArgs e)
+ {
+ Window.WindowState = WindowState.FullScreen;
+ }
+
+ private void ExitFullscreen_Click(object? sender, RoutedEventArgs e)
+ {
+ Window.WindowState = WindowState.Normal;
+ }
+
+ private void RestoreAll_Click(object? sender, RoutedEventArgs e)
+ {
+ var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
+
+ foreach (var window in lifetime.Windows.ToArray())
+ {
+ window.Show();
+ if (window.WindowState == WindowState.Minimized)
+ window.WindowState = WindowState.Normal;
+ }
+ }
+
+ private void ShowTopmostWindow_Click(object? sender, RoutedEventArgs e)
+ {
+ var mainWindow = new TopmostWindowTest("OwnerWindow")
+ {
+ Topmost = true,
+ Title = "Owner Window"
+ };
+ var ownedWindow = new TopmostWindowTest("OwnedWindow")
+ {
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ Title = "Owned Window"
+ };
+
+ mainWindow.Show();
+ ownedWindow.Show(mainWindow);
+ }
+}
diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs
index b1d2831997..1ec7bd374d 100644
--- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs
+++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs
@@ -2,7 +2,6 @@ using System;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
using Avalonia.Threading;
namespace IntegrationTestApp
diff --git a/samples/IntegrationTestApp/TopmostWindowTest.axaml.cs b/samples/IntegrationTestApp/TopmostWindowTest.axaml.cs
index 4bdfe0a65c..c3ee80e3d6 100644
--- a/samples/IntegrationTestApp/TopmostWindowTest.axaml.cs
+++ b/samples/IntegrationTestApp/TopmostWindowTest.axaml.cs
@@ -1,7 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
-using Avalonia.Markup.Xaml;
namespace IntegrationTestApp;
diff --git a/samples/IntegrationTestApp/ViewModels/MainWindowViewModel.cs b/samples/IntegrationTestApp/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000000..86eb13ec5a
--- /dev/null
+++ b/samples/IntegrationTestApp/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using IntegrationTestApp.Models;
+
+namespace IntegrationTestApp.ViewModels;
+
+internal class MainWindowViewModel : ViewModelBase
+{
+ private Page? _selectedPage;
+
+ public MainWindowViewModel(IEnumerable pages)
+ {
+ Pages = new(pages);
+ }
+
+ public ObservableCollection Pages { get; }
+
+ public Page? SelectedPage
+ {
+ get => _selectedPage;
+ set => RaiseAndSetIfChanged(ref _selectedPage, value);
+ }
+}
diff --git a/samples/IntegrationTestApp/ViewModels/ViewModelBase.cs b/samples/IntegrationTestApp/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000000..521382b863
--- /dev/null
+++ b/samples/IntegrationTestApp/ViewModels/ViewModelBase.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace IntegrationTestApp.ViewModels;
+
+internal class ViewModelBase : INotifyPropertyChanged
+{
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string? propertyName = null)
+ {
+ if (!EqualityComparer.Default.Equals(field, value))
+ {
+ field = value;
+ RaisePropertyChanged(propertyName);
+ return true;
+ }
+ return false;
+ }
+
+ protected void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
+ => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+}
diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs
index 852b85ff01..3518738485 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs
@@ -3,6 +3,7 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
+using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Automation.Peers;
using Avalonia.Controls.Metadata;
@@ -37,6 +38,7 @@ namespace Avalonia.Controls
(x,e) => x.DataGridCell_PointerPressed(e), handledEventsToo: true);
FocusableProperty.OverrideDefaultValue(true);
IsTabStopProperty.OverrideDefaultValue(false);
+ AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip);
}
public DataGridCell()
{ }
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
index 579c4d04ad..a3dcc433ee 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
@@ -6,6 +6,7 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
+using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Automation.Peers;
@@ -74,6 +75,7 @@ namespace Avalonia.Controls
AreSeparatorsVisibleProperty.Changed.AddClassHandler((x, e) => x.OnAreSeparatorsVisibleChanged(e));
PressedMixin.Attach();
IsTabStopProperty.OverrideDefaultValue(false);
+ AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip);
}
///
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
index 60acfb7f03..3af85223a8 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
@@ -17,6 +17,7 @@ using System;
using System.Diagnostics;
using Avalonia.Automation.Peers;
using Avalonia.Reactive;
+using Avalonia.Automation;
namespace Avalonia.Controls
{
@@ -143,6 +144,7 @@ namespace Avalonia.Controls
AreDetailsVisibleProperty.Changed.AddClassHandler((x, e) => x.OnAreDetailsVisibleChanged(e));
PointerPressedEvent.AddClassHandler((x, e) => x.DataGridRow_PointerPressed(e), handledEventsToo: true);
IsTabStopProperty.OverrideDefaultValue(false);
+ AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip);
}
///
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
index a480a7af15..ba6ad94777 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
@@ -3,6 +3,7 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
+using Avalonia.Automation;
using Avalonia.Controls.Metadata;
using Avalonia.Input;
using Avalonia.Media;
@@ -50,6 +51,11 @@ namespace Avalonia.Controls.Primitives
AddHandler(PointerPressedEvent, DataGridRowHeader_PointerPressed, handledEventsToo: true);
}
+ static DataGridRowHeader()
+ {
+ AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip);
+ }
+
internal Control Owner
{
get;
diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
index 35d6c13918..bda9a91183 100644
--- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
@@ -153,6 +153,17 @@ namespace Avalonia.Automation.Peers
///
public bool IsKeyboardFocusable() => IsKeyboardFocusableCore();
+ ///
+ /// Gets a value that indicates whether an element is off the screen.
+ ///
+ ///
+ /// This property does not indicate whether the element is visible. In some circumstances,
+ /// an element is on the screen but is still not visible. For example, if the element is
+ /// on the screen but obscured by other elements, it might not be visible. In this case,
+ /// the method returns false.
+ ///
+ public bool IsOffscreen() => IsOffscreenCore();
+
///
/// Sets the keyboard focus on the element that is associated with this automation peer.
///
@@ -245,6 +256,7 @@ namespace Avalonia.Automation.Peers
protected abstract bool IsControlElementCore();
protected abstract bool IsEnabledCore();
protected abstract bool IsKeyboardFocusableCore();
+ protected virtual bool IsOffscreenCore() => false;
protected abstract void SetFocusCore();
protected abstract bool ShowContextMenuCore();
diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
index b0dd30a878..0c043e7577 100644
--- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
+++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
+using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Automation.Peers
@@ -201,6 +202,19 @@ namespace Avalonia.Automation.Peers
return view == AccessibilityView.Default ? IsControlElementCore() : view >= AccessibilityView.Control;
}
+ protected override bool IsOffscreenCore()
+ {
+ return AutomationProperties.GetIsOffscreenBehavior(Owner) switch
+ {
+ IsOffscreenBehavior.Onscreen => false,
+ IsOffscreenBehavior.Offscreen => true,
+ IsOffscreenBehavior.FromClip => Owner.GetTransformedBounds() is not { } bounds ||
+ MathUtilities.IsZero(bounds.Clip.Width) ||
+ MathUtilities.IsZero(bounds.Clip.Height),
+ _ => !Owner.IsVisible,
+ };
+ }
+
private static Rect GetBounds(Control control)
{
var root = control.GetVisualRoot();
diff --git a/src/Avalonia.Controls/ListBoxItem.cs b/src/Avalonia.Controls/ListBoxItem.cs
index 5ee4854554..55fc81653a 100644
--- a/src/Avalonia.Controls/ListBoxItem.cs
+++ b/src/Avalonia.Controls/ListBoxItem.cs
@@ -1,3 +1,4 @@
+using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
@@ -30,6 +31,7 @@ namespace Avalonia.Controls
SelectableMixin.Attach(IsSelectedProperty);
PressedMixin.Attach();
FocusableProperty.OverrideDefaultValue(true);
+ AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip);
}
///
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index a2a54d5550..13cd78f2fc 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
+using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
@@ -141,6 +142,7 @@ namespace Avalonia.Controls
ItemsPanelProperty.OverrideDefaultValue