diff --git a/Avalonia.sln b/Avalonia.sln
index 7037a9721b..6c065a12e0 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -237,15 +237,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "sampl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestApp", "samples\IntegrationTestApp\IntegrationTestApp.csproj", "{D3867680-B9C7-43D6-BF2C-697EC9CF1151}"
+EndProject
Global
- GlobalSection(SharedMSBuildProjectFiles) = preSolution
- src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
- src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
- src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5
- src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
- src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
- src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
- EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
Ad-Hoc|iPhone = Ad-Hoc|iPhone
@@ -2195,6 +2189,30 @@ Global
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhone.Build.0 = Release|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhone.Build.0 = Release|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2258,8 +2276,17 @@ Global
{C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {D3867680-B9C7-43D6-BF2C-697EC9CF1151} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
EndGlobalSection
+ GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
+ src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
+ src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5
+ src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
+ src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
+ src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
+ EndGlobalSection
EndGlobal
diff --git a/samples/IntegrationTestApp/App.axaml b/samples/IntegrationTestApp/App.axaml
new file mode 100644
index 0000000000..a833e096df
--- /dev/null
+++ b/samples/IntegrationTestApp/App.axaml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/App.axaml.cs b/samples/IntegrationTestApp/App.axaml.cs
new file mode 100644
index 0000000000..022931366d
--- /dev/null
+++ b/samples/IntegrationTestApp/App.axaml.cs
@@ -0,0 +1,24 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+namespace IntegrationTestApp
+{
+ public class App : Application
+ {
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.MainWindow = new MainWindow();
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+ }
+}
diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj
new file mode 100644
index 0000000000..4284399357
--- /dev/null
+++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj
@@ -0,0 +1,28 @@
+
+
+ WinExe
+ net6.0
+ enable
+
+
+
+ IntegrationTestApp
+ net.avaloniaui.avalonia.integrationtestapp
+ true
+ 1.0.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
new file mode 100644
index 0000000000..f4d6ad3ace
--- /dev/null
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TextBlockWithName
+
+ TextBlockWithNameAndAutomationId
+
+ Label for TextBox
+
+ Foo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Unchecked
+ Checked
+ ThreeState
+
+
+
+
+
+
+ Item 0
+ Item 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+
+
+
+
+
+
+
+
+
+
+ NonOwned
+ Owned
+ Modal
+
+
+ Manual
+ CenterScreen
+ CenterOwner
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
new file mode 100644
index 0000000000..9e180b12c5
--- /dev/null
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using Avalonia.VisualTree;
+
+namespace IntegrationTestApp
+{
+ public class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+ InitializeViewMenu();
+ this.AttachDevTools();
+ AddHandler(Button.ClickEvent, OnButtonClick);
+ ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
+ DataContext = this;
+ }
+
+ public List ListBoxItems { get; }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void InitializeViewMenu()
+ {
+ var mainTabs = this.FindControl("MainTabs");
+ var viewMenu = (NativeMenuItem)NativeMenu.GetMenu(this).Items[1];
+
+ foreach (TabItem tabItem in mainTabs.Items)
+ {
+ var menuItem = new NativeMenuItem
+ {
+ Header = (string)tabItem.Header!,
+ IsChecked = tabItem.IsSelected,
+ ToggleType = NativeMenuItemToggleType.Radio,
+ };
+
+ menuItem.Click += (s, e) => tabItem.IsSelected = true;
+ viewMenu.Menu.Items.Add(menuItem);
+ }
+ }
+
+ private void ShowWindow()
+ {
+ var sizeTextBox = this.GetControl("ShowWindowSize");
+ var modeComboBox = this.GetControl("ShowWindowMode");
+ var locationComboBox = this.GetControl("ShowWindowLocation");
+ var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
+ var owner = (Window)this.GetVisualRoot()!;
+
+ var window = new ShowWindowTest
+ {
+ WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
+ };
+
+ if (size.HasValue)
+ {
+ window.Width = size.Value.Width;
+ window.Height = size.Value.Height;
+ }
+
+ sizeTextBox.Text = string.Empty;
+
+ switch (modeComboBox.SelectedIndex)
+ {
+ case 0:
+ window.Show();
+ break;
+ case 1:
+ window.Show(owner);
+ break;
+ case 2:
+ window.ShowDialog(owner);
+ break;
+ }
+ }
+
+ private void SendToBack()
+ {
+ var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
+
+ foreach (var window in lifetime.Windows)
+ {
+ window.Activate();
+ }
+ }
+
+ private void RestoreAll()
+ {
+ var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
+
+ foreach (var window in lifetime.Windows)
+ {
+ if (window.WindowState == WindowState.Minimized)
+ window.WindowState = WindowState.Normal;
+ }
+ }
+
+ private void MenuClicked(object? sender, RoutedEventArgs e)
+ {
+ var clickedMenuItemTextBlock = this.FindControl("ClickedMenuItem");
+ clickedMenuItemTextBlock.Text = ((MenuItem)sender!).Header.ToString();
+ }
+
+ private void OnButtonClick(object? sender, RoutedEventArgs e)
+ {
+ var source = e.Source as Button;
+
+ if (source?.Name == "ComboBoxSelectionClear")
+ this.FindControl("BasicComboBox").SelectedIndex = -1;
+ if (source?.Name == "ComboBoxSelectFirst")
+ this.FindControl("BasicComboBox").SelectedIndex = 0;
+ if (source?.Name == "ListBoxSelectionClear")
+ this.FindControl("BasicListBox").SelectedIndex = -1;
+ if (source?.Name == "MenuClickedMenuItemReset")
+ this.FindControl("ClickedMenuItem").Text = "None";
+ if (source?.Name == "ShowWindow")
+ ShowWindow();
+ if (source?.Name == "SendToBack")
+ SendToBack();
+ if (source?.Name == "ExitFullscreen")
+ WindowState = WindowState.Normal;
+ if (source?.Name == "RestoreAll")
+ RestoreAll();
+ }
+ }
+}
diff --git a/samples/IntegrationTestApp/Program.cs b/samples/IntegrationTestApp/Program.cs
new file mode 100644
index 0000000000..c09b249cfa
--- /dev/null
+++ b/samples/IntegrationTestApp/Program.cs
@@ -0,0 +1,22 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+
+namespace IntegrationTestApp
+{
+ class Program
+ {
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .LogToTrace();
+ }
+}
diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml
new file mode 100644
index 0000000000..40c1642e91
--- /dev/null
+++ b/samples/IntegrationTestApp/ShowWindowTest.axaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Normal
+ Minimized
+ Maximized
+ Fullscreen
+
+
+
diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs
new file mode 100644
index 0000000000..001f186761
--- /dev/null
+++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs
@@ -0,0 +1,40 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using Avalonia.Rendering;
+
+namespace IntegrationTestApp
+{
+ public class ShowWindowTest : Window
+ {
+ public ShowWindowTest()
+ {
+ InitializeComponent();
+ DataContext = this;
+ PositionChanged += (s, e) => this.GetControl("Position").Text = $"{Position}";
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ protected override void OnOpened(EventArgs e)
+ {
+ base.OnOpened(e);
+ var scaling = PlatformImpl!.DesktopScaling;
+ this.GetControl("Position").Text = $"{Position}";
+ this.GetControl("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}";
+ this.GetControl("Scaling").Text = $"{scaling}";
+
+ if (Owner is not null)
+ {
+ var ownerRect = this.GetControl("OwnerRect");
+ var owner = (Window)Owner;
+ ownerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}";
+ }
+ }
+ }
+}
diff --git a/samples/IntegrationTestApp/bundle.sh b/samples/IntegrationTestApp/bundle.sh
new file mode 100644
index 0000000000..505991582e
--- /dev/null
+++ b/samples/IntegrationTestApp/bundle.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+cd $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
+dotnet restore -r osx-arm64
+dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-arm64 -p:_AvaloniaUseExternalMSBuild=false
\ No newline at end of file
diff --git a/samples/IntegrationTestApp/nuget.config b/samples/IntegrationTestApp/nuget.config
new file mode 100644
index 0000000000..6c273ab3d9
--- /dev/null
+++ b/samples/IntegrationTestApp/nuget.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs
index c3cab0a729..86ad335e26 100644
--- a/src/Avalonia.Controls/ControlExtensions.cs
+++ b/src/Avalonia.Controls/ControlExtensions.cs
@@ -66,6 +66,29 @@ namespace Avalonia.Controls
return nameScope.Find(name);
}
+ ///
+ /// Finds the named control in the scope of the specified control and throws if not found.
+ ///
+ /// The type of the control to find.
+ /// The control to look in.
+ /// The name of the control to find.
+ /// The control.
+ public static T GetControl(this IControl control, string name) where T : class, IControl
+ {
+ _ = control ?? throw new ArgumentNullException(nameof(control));
+ _ = name ?? throw new ArgumentNullException(nameof(name));
+
+ var nameScope = control.FindNameScope();
+
+ if (nameScope == null)
+ {
+ throw new InvalidOperationException("Could not find parent name scope.");
+ }
+
+ return nameScope.Find(name) ??
+ throw new ArgumentException($"Could not find control named '{name}'.");
+ }
+
///
/// Sets a pseudoclass depending on an observable trigger.
///