From c23fdc92de962f00ec43e24f4a09fc889dff294b Mon Sep 17 00:00:00 2001 From: Michael Bosschert Date: Fri, 29 Mar 2019 14:06:10 +0100 Subject: [PATCH 01/36] Added unittests for the Select All option of the TreeView. --- .../TreeViewTests.cs | 133 +++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 519872f9f2..15081b184c 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -10,6 +10,7 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.LogicalTree; using Avalonia.UnitTests; using Xunit; @@ -425,7 +426,6 @@ namespace Avalonia.Controls.UnitTests Assert.True(called); } - [Fact] public void LogicalChildren_Should_Be_Set() { @@ -623,6 +623,135 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Pressing_SelectAll_Gesture_Should_Select_All_Nodes() + { + using (UnitTestApplication.Start()) + { + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + SelectionMode = SelectionMode.Multiple + }; + + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var rootNode = tree[0]; + + var keymap = AvaloniaLocator.Current.GetService(); + var selectAllGesture = keymap.SelectAll.First(); + + var keyEvent = new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + Key = selectAllGesture.Key, + Modifiers = selectAllGesture.Modifiers + }; + + target.RaiseEvent(keyEvent); + + TreeTestHelper.AssertChildrenSelected(target, rootNode); + } + } + + [Fact] + public void Pressing_SelectAll_Gesture_With_Downward_Range_Selected_Should_Select_All_Nodes() + { + using (UnitTestApplication.Start()) + { + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + SelectionMode = SelectionMode.Multiple + }; + + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var rootNode = tree[0]; + + var from = rootNode.Children[0]; + var to = rootNode.Children.Last(); + + var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); + var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); + + TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); + TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift); + + var keymap = AvaloniaLocator.Current.GetService(); + var selectAllGesture = keymap.SelectAll.First(); + + var keyEvent = new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + Key = selectAllGesture.Key, + Modifiers = selectAllGesture.Modifiers + }; + + target.RaiseEvent(keyEvent); + + TreeTestHelper.AssertChildrenSelected(target, rootNode); + } + } + + [Fact] + public void Pressing_SelectAll_Gesture_With_Upward_Range_Selected_Should_Select_All_Nodes() + { + using (UnitTestApplication.Start()) + { + var tree = CreateTestTreeData(); + var target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = tree, + SelectionMode = SelectionMode.Multiple + }; + + var visualRoot = new TestRoot(); + visualRoot.Child = target; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + var rootNode = tree[0]; + + var from = rootNode.Children.Last(); + var to = rootNode.Children[0]; + + var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); + var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); + + TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None); + TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift); + + var keymap = AvaloniaLocator.Current.GetService(); + var selectAllGesture = keymap.SelectAll.First(); + + var keyEvent = new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + Key = selectAllGesture.Key, + Modifiers = selectAllGesture.Modifiers + }; + + target.RaiseEvent(keyEvent); + + TreeTestHelper.AssertChildrenSelected(target, rootNode); + } + } + private void ApplyTemplates(TreeView tree) { tree.ApplyTemplate(); @@ -765,7 +894,7 @@ namespace Avalonia.Controls.UnitTests } } - private class Node : NotifyingBase + private class Node : NotifyingBase { private IAvaloniaList _children; From de801ed27edb2c980fc5b81141d50c41a41be871 Mon Sep 17 00:00:00 2001 From: Michael Bosschert Date: Tue, 2 Apr 2019 15:18:50 +0200 Subject: [PATCH 02/36] Fixed SynchronizeItems adding duplicate items. --- .../Primitives/SelectingItemsControl.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index a64dbe0546..288d751aa9 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -639,20 +639,20 @@ namespace Avalonia.Controls.Primitives /// The desired items. internal static void SynchronizeItems(IList items, IEnumerable desired) { - int index = 0; + var index = 0; - foreach (var i in desired) + foreach (object item in desired) { - if (index < items.Count) + int itemIndex = items.IndexOf(item); + + if (itemIndex == -1) { - if (items[index] != i) - { - items[index] = i; - } + items.Insert(index, item); } - else + else if(itemIndex != index) { - items.Add(i); + items.RemoveAt(itemIndex); + items.Insert(index, item); } ++index; From 5557829950b81a892818c76b765f19caf919322d Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 10 Apr 2019 18:57:35 +0200 Subject: [PATCH 03/36] Initial --- samples/ControlCatalog.Desktop/Program.cs | 8 +- samples/ControlCatalog.NetCore/Program.cs | 2 +- samples/ControlCatalog/App.xaml.cs | 9 ++ src/Avalonia.Controls/AppBuilderBase.cs | 17 ++- src/Avalonia.Controls/Application.cs | 112 +++++++++--------- .../IApplicationLifecycle.cs | 9 +- .../{ExitMode.cs => ShutdownMode.cs} | 8 +- src/Avalonia.Controls/TopLevel.cs | 4 +- src/Avalonia.Controls/Window.cs | 18 ++- src/Avalonia.Controls/WindowCollection.cs | 12 +- .../ApplicationTests.cs | 35 ++++-- .../TopLevelTests.cs | 2 +- 12 files changed, 144 insertions(+), 92 deletions(-) rename src/Avalonia.Controls/{ExitMode.cs => ShutdownMode.cs} (88%) diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index 329b2ab5a3..b809cc8c94 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -4,7 +4,6 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Logging.Serilog; using Avalonia.Platform; -using Serilog; namespace ControlCatalog { @@ -15,7 +14,12 @@ namespace ControlCatalog { // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. - BuildAvaloniaApp().Start(); + BuildAvaloniaApp().Start(AppMain, args); + } + + private static void AppMain(Application app, string[] args) + { + app.Run(); } /// diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 57c8b700df..ee4c3fe350 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -36,7 +36,7 @@ namespace ControlCatalog.NetCore static void AppMain(Application app, string[] args) { - app.Run(new MainWindow()); + app.Run(); } /// diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index d862749132..6fbcecfd6e 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -9,5 +9,14 @@ namespace ControlCatalog { AvaloniaXamlLoader.Load(this); } + + protected override void OnStartup() + { + base.OnStartup(); + + var mainWindow = new MainWindow(); + + mainWindow.Show(); + } } } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 1f6870d60d..2dfc04132a 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -147,13 +147,20 @@ namespace Avalonia.Controls public delegate void AppMainDelegate(Application app, string[] args); + public void Start() + { + Setup(); + BeforeStartCallback(Self); + Instance.Run(); + } + public void Start(AppMainDelegate main, string[] args) { Setup(); BeforeStartCallback(Self); main(Instance, args); } - + /// /// Sets up the platform-specific services for the application, but does not run it. /// @@ -219,13 +226,13 @@ namespace Avalonia.Controls /// /// Sets the shutdown mode of the application. /// - /// The shutdown mode. + /// The shutdown mode. /// - public TAppBuilder SetExitMode(ExitMode exitMode) + public TAppBuilder SetExitMode(ShutdownMode shutdownMode) { - Instance.ExitMode = exitMode; + Instance.ShutdownMode = shutdownMode; return Self; - } + } protected virtual bool CheckSetup => true; diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 1d4e4cbeaa..383b994e70 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -52,10 +52,14 @@ namespace Avalonia public Application() { Windows = new WindowCollection(this); - - OnExit += OnExiting; } + /// + public event EventHandler Startup; + + /// + public event EventHandler Exit; + /// public event EventHandler ResourcesChanged; @@ -164,14 +168,14 @@ namespace Avalonia IResourceNode IResourceNode.ResourceParent => null; /// - /// Gets or sets the . This property indicates whether the application exits explicitly or implicitly. - /// If is set to OnExplicitExit the application is only closes if Exit is called. + /// Gets or sets the . This property indicates whether the application is shutdown explicitly or implicitly. + /// If is set to OnExplicitShutdown the application is only closes if Shutdown is called. /// The default is OnLastWindowClose /// /// /// The shutdown mode. /// - public ExitMode ExitMode { get; set; } + public ShutdownMode ShutdownMode { get; set; } /// /// Gets or sets the main window of the application. @@ -190,12 +194,12 @@ namespace Avalonia public WindowCollection Windows { get; } /// - /// Gets or sets a value indicating whether this instance is existing. + /// Gets or sets a value indicating whether this instance is shutting down. /// /// - /// true if this instance is existing; otherwise, false. + /// true if this instance is shutting down; otherwise, false. /// - internal bool IsExiting { get; set; } + internal bool IsShuttingDown { get; set; } /// /// Initializes the application by loading XAML etc. @@ -204,6 +208,35 @@ namespace Avalonia { } + public void Run() + { + if (_mainLoopCancellationTokenSource != null) + { + throw new Exception("Run should only called once"); + } + + _mainLoopCancellationTokenSource = new CancellationTokenSource(); + + Dispatcher.UIThread.Post(OnStartup, DispatcherPriority.Send); + + Run(_mainLoopCancellationTokenSource.Token); + } + + /// + /// Runs the application's main loop until the is canceled. + /// + /// The token to track + public void Run(CancellationToken token) + { + Dispatcher.UIThread.MainLoop(token); + + // Make sure we call OnExit in case an error happened and OnExit() wasn't called explicitly + if (!IsShuttingDown) + { + OnExit(); + } + } + /// /// Runs the application's main loop until the is closed. /// @@ -215,17 +248,11 @@ namespace Avalonia throw new Exception("Run should only called once"); } - closable.Closed += (s, e) => Exit(); + closable.Closed += (s, e) => OnExit(); _mainLoopCancellationTokenSource = new CancellationTokenSource(); - Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); - - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); - } + Run(_mainLoopCancellationTokenSource.Token); } /// @@ -252,46 +279,31 @@ namespace Avalonia { mainWindow.Show(); } - - MainWindow = mainWindow; - } - - Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); - - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); } + + Run(_mainLoopCancellationTokenSource.Token); } - /// - /// Runs the application's main loop until the is canceled. - /// - /// The token to track - public void Run(CancellationToken token) + protected virtual void OnStartup() { - Dispatcher.UIThread.MainLoop(token); + Startup?.Invoke(this, EventArgs.Empty); + } - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); - } + protected virtual void OnExit() + { + Exit?.Invoke(this, EventArgs.Empty); } - /// - /// Exits the application - /// - public void Exit() + /// + public void Shutdown() { - IsExiting = true; + IsShuttingDown = true; Windows.Clear(); - OnExit?.Invoke(this, EventArgs.Empty); - _mainLoopCancellationTokenSource?.Cancel(); + + OnExit(); } /// @@ -302,19 +314,7 @@ namespace Avalonia Styles.TryGetResource(key, out value); } - /// - /// Sent when the application is exiting. - /// - public event EventHandler OnExit; - /// - /// Called when the application is exiting. - /// - /// - /// - protected virtual void OnExiting(object sender, EventArgs e) - { - } /// /// Register's the services needed by Avalonia. diff --git a/src/Avalonia.Controls/IApplicationLifecycle.cs b/src/Avalonia.Controls/IApplicationLifecycle.cs index 51f554c078..c088ceb0eb 100644 --- a/src/Avalonia.Controls/IApplicationLifecycle.cs +++ b/src/Avalonia.Controls/IApplicationLifecycle.cs @@ -7,14 +7,19 @@ namespace Avalonia.Controls /// public interface IApplicationLifecycle { + /// + /// Sent when the application is starting up. + /// + event EventHandler Startup; + /// /// Sent when the application is exiting. /// - event EventHandler OnExit; + event EventHandler Exit; /// /// Exits the application. /// - void Exit(); + void Shutdown(); } } diff --git a/src/Avalonia.Controls/ExitMode.cs b/src/Avalonia.Controls/ShutdownMode.cs similarity index 88% rename from src/Avalonia.Controls/ExitMode.cs rename to src/Avalonia.Controls/ShutdownMode.cs index b73fe4a963..8c52d4b70e 100644 --- a/src/Avalonia.Controls/ExitMode.cs +++ b/src/Avalonia.Controls/ShutdownMode.cs @@ -6,7 +6,7 @@ namespace Avalonia /// /// Enum for ExitMode /// - public enum ExitMode + public enum ShutdownMode { /// /// Indicates an implicit call to Application.Exit when the last window closes. @@ -19,8 +19,8 @@ namespace Avalonia OnMainWindowClose, /// - /// Indicates that the application only exits on an explicit call to Application.Exit. + /// Indicates that the application only exits on an explicit call to Application.Shutdown. /// - OnExplicitExit + OnExplicitShutdown } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 32c40847c5..d627785129 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -124,7 +124,7 @@ namespace Avalonia.Controls if (_applicationLifecycle != null) { - _applicationLifecycle.OnExit += OnApplicationExiting; + _applicationLifecycle.Exit += OnApplicationExiting; } if (((IStyleHost)this).StylingParent is IResourceProvider applicationResources) @@ -278,7 +278,7 @@ namespace Avalonia.Controls Closed?.Invoke(this, EventArgs.Empty); Renderer?.Dispose(); Renderer = null; - _applicationLifecycle.OnExit -= OnApplicationExiting; + _applicationLifecycle.Exit -= OnApplicationExiting; } /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index f5af6774b5..648256e667 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -49,6 +49,8 @@ namespace Avalonia.Controls /// public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope { + private static bool s_hasAddedFirstWindow; + /// /// Defines the property. /// @@ -250,7 +252,7 @@ namespace Avalonia.Controls /// /// Fired before a window is closed. /// - public event EventHandler Closing; + public event EventHandler Closing; private static void AddWindow(Window window) { @@ -260,6 +262,18 @@ namespace Avalonia.Controls } Application.Current.Windows.Add(window); + + if (s_hasAddedFirstWindow) + { + return; + } + + s_hasAddedFirstWindow = true; + + if (Application.Current.MainWindow == null) + { + Application.Current.MainWindow = window; + } } private static void RemoveWindow(Window window) @@ -428,7 +442,7 @@ namespace Avalonia.Controls /// public Task ShowDialog(IWindowImpl owner) { - if(owner == null) + if (owner == null) throw new ArgumentNullException(nameof(owner)); if (IsVisible) diff --git a/src/Avalonia.Controls/WindowCollection.cs b/src/Avalonia.Controls/WindowCollection.cs index df79c3e3c8..328bb9f147 100644 --- a/src/Avalonia.Controls/WindowCollection.cs +++ b/src/Avalonia.Controls/WindowCollection.cs @@ -107,24 +107,24 @@ namespace Avalonia return; } - if (_application.IsExiting) + if (_application.IsShuttingDown) { return; } - switch (_application.ExitMode) + switch (_application.ShutdownMode) { - case ExitMode.OnLastWindowClose: + case ShutdownMode.OnLastWindowClose: if (Count == 0) { - _application.Exit(); + _application.Shutdown(); } break; - case ExitMode.OnMainWindowClose: + case ShutdownMode.OnMainWindowClose: if (window == _application.MainWindow) { - _application.Exit(); + _application.Shutdown(); } break; diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index df14c808db..7cedd9b69a 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Application.Current.ExitMode = ExitMode.OnMainWindowClose; + Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose; var mainWindow = new Window(); @@ -29,7 +29,7 @@ namespace Avalonia.Controls.UnitTests mainWindow.Close(); - Assert.True(Application.Current.IsExiting); + Assert.True(Application.Current.IsShuttingDown); } } @@ -38,7 +38,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Application.Current.ExitMode = ExitMode.OnLastWindowClose; + Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose; var windowA = new Window(); @@ -50,11 +50,11 @@ namespace Avalonia.Controls.UnitTests windowA.Close(); - Assert.False(Application.Current.IsExiting); + Assert.False(Application.Current.IsShuttingDown); windowB.Close(); - Assert.True(Application.Current.IsExiting); + Assert.True(Application.Current.IsShuttingDown); } } @@ -63,7 +63,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Application.Current.ExitMode = ExitMode.OnExplicitExit; + Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; var windowA = new Window(); @@ -75,15 +75,15 @@ namespace Avalonia.Controls.UnitTests windowA.Close(); - Assert.False(Application.Current.IsExiting); + Assert.False(Application.Current.IsShuttingDown); windowB.Close(); - Assert.False(Application.Current.IsExiting); + Assert.False(Application.Current.IsShuttingDown); - Application.Current.Exit(); + Application.Current.Shutdown(); - Assert.True(Application.Current.IsExiting); + Assert.True(Application.Current.IsShuttingDown); } } @@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests window.Show(); } - Application.Current.Exit(); + Application.Current.Shutdown(); Assert.Empty(Application.Current.Windows); } @@ -129,5 +129,18 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } } + + [Fact] + public void Should_Have_MainWindow_After_First_Window_Shown() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var mainWindow = new Window(); + + mainWindow.Show(); + + Assert.Equal(mainWindow, Application.Current.MainWindow); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 014bf458ea..aa99d31cff 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -215,7 +215,7 @@ namespace Avalonia.Controls.UnitTests var impl = new Mock(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); - UnitTestApplication.Current.Exit(); + UnitTestApplication.Current.Shutdown(); Assert.True(target.IsClosed); } } From ff0228e85eb1334a506a1b0d5f4b0b2120111cbc Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 10 Apr 2019 19:10:45 +0200 Subject: [PATCH 04/36] Rename AppBuilderBase.SetExitMode --- src/Avalonia.Controls/AppBuilderBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 2dfc04132a..cda49a12b3 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -228,7 +228,7 @@ namespace Avalonia.Controls /// /// The shutdown mode. /// - public TAppBuilder SetExitMode(ShutdownMode shutdownMode) + public TAppBuilder SetShutdownMode(ShutdownMode shutdownMode) { Instance.ShutdownMode = shutdownMode; return Self; From 1bb3156950a3caa3af8395a8ab5a36cba8e7bd47 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 11 Apr 2019 17:11:09 +0200 Subject: [PATCH 05/36] Introduce StartupEventArgs and ExitEventArgs --- samples/BindingDemo/App.xaml.cs | 3 +- samples/ControlCatalog.NetCore/Program.cs | 13 ++---- samples/ControlCatalog/App.xaml.cs | 10 ++--- samples/PlatformSanityChecks/App.xaml.cs | 3 +- samples/Previewer/App.xaml.cs | 5 ++- samples/RenderDemo/App.xaml.cs | 3 +- samples/VirtualizationDemo/App.xaml.cs | 3 +- .../interop/Direct3DInteropSample/App.paml.cs | 3 +- src/Avalonia.Controls/AppBuilderBase.cs | 1 - src/Avalonia.Controls/Application.cs | 43 +++++++++---------- .../IApplicationLifecycle.cs | 12 ++++-- .../Controls/ExitEventArgs.cs | 12 ++++++ .../Controls/StartupEventArgs.cs | 30 +++++++++++++ .../App.xaml.cs | 8 +--- 14 files changed, 93 insertions(+), 56 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/ExitEventArgs.cs create mode 100644 src/Avalonia.Styling/Controls/StartupEventArgs.cs diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs index 01c52a2a49..d95241372f 100644 --- a/samples/BindingDemo/App.xaml.cs +++ b/samples/BindingDemo/App.xaml.cs @@ -3,13 +3,12 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Logging.Serilog; using Avalonia.Markup.Xaml; -using Serilog; namespace BindingDemo { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index ee4c3fe350..42be488569 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -3,13 +3,11 @@ using System.Diagnostics; using System.Linq; using System.Threading; using Avalonia; -using Avalonia.Skia; namespace ControlCatalog.NetCore { static class Program - { - + { static void Main(string[] args) { Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA); @@ -28,15 +26,10 @@ namespace ControlCatalog.NetCore AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => { tl.Content = new MainView(); - System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); + ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - BuildAvaloniaApp().Start(AppMain, args); - } - - static void AppMain(Application app, string[] args) - { - app.Run(); + BuildAvaloniaApp().Start(); } /// diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 6fbcecfd6e..1f466d9f91 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -1,18 +1,16 @@ using Avalonia; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace ControlCatalog { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { - AvaloniaXamlLoader.Load(this); - } + base.OnStartup(e); - protected override void OnStartup() - { - base.OnStartup(); + AvaloniaXamlLoader.Load(this); var mainWindow = new MainWindow(); diff --git a/samples/PlatformSanityChecks/App.xaml.cs b/samples/PlatformSanityChecks/App.xaml.cs index 508fc1e34b..b8f8350156 100644 --- a/samples/PlatformSanityChecks/App.xaml.cs +++ b/samples/PlatformSanityChecks/App.xaml.cs @@ -1,11 +1,12 @@ using Avalonia; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace PlatformSanityChecks { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs index fffa987a27..0fc42583a5 100644 --- a/samples/Previewer/App.xaml.cs +++ b/samples/Previewer/App.xaml.cs @@ -1,14 +1,15 @@ using Avalonia; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Previewer { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } } -} \ No newline at end of file +} diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index 0f627961e6..4ac844983b 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia; +using Avalonia.Controls; using Avalonia.Logging.Serilog; using Avalonia.Markup.Xaml; @@ -9,7 +10,7 @@ namespace RenderDemo { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } diff --git a/samples/VirtualizationDemo/App.xaml.cs b/samples/VirtualizationDemo/App.xaml.cs index b220807443..787bbf8d11 100644 --- a/samples/VirtualizationDemo/App.xaml.cs +++ b/samples/VirtualizationDemo/App.xaml.cs @@ -2,13 +2,14 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace VirtualizationDemo { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs index 1b6d5fd39c..b08c02509c 100644 --- a/samples/interop/Direct3DInteropSample/App.paml.cs +++ b/samples/interop/Direct3DInteropSample/App.paml.cs @@ -1,11 +1,12 @@ using Avalonia; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Direct3DInteropSample { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index cda49a12b3..568b04c78c 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -292,7 +292,6 @@ namespace Avalonia.Controls WindowingSubsystemInitializer(); RenderingSubsystemInitializer(); Instance.RegisterServices(); - Instance.Initialize(); AfterSetupCallback(Self); } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 383b994e70..2893505655 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -55,10 +55,10 @@ namespace Avalonia } /// - public event EventHandler Startup; + public event EventHandler Startup; /// - public event EventHandler Exit; + public event EventHandler Exit; /// public event EventHandler ResourcesChanged; @@ -199,14 +199,7 @@ namespace Avalonia /// /// true if this instance is shutting down; otherwise, false. /// - internal bool IsShuttingDown { get; set; } - - /// - /// Initializes the application by loading XAML etc. - /// - public virtual void Initialize() - { - } + internal bool IsShuttingDown { get; private set; } public void Run() { @@ -217,7 +210,7 @@ namespace Avalonia _mainLoopCancellationTokenSource = new CancellationTokenSource(); - Dispatcher.UIThread.Post(OnStartup, DispatcherPriority.Send); + Dispatcher.UIThread.Post(() => OnStartup(new StartupEventArgs()), DispatcherPriority.Send); Run(_mainLoopCancellationTokenSource.Token); } @@ -233,7 +226,7 @@ namespace Avalonia // Make sure we call OnExit in case an error happened and OnExit() wasn't called explicitly if (!IsShuttingDown) { - OnExit(); + OnExit(new ExitEventArgs()); } } @@ -248,10 +241,10 @@ namespace Avalonia throw new Exception("Run should only called once"); } - closable.Closed += (s, e) => OnExit(); - _mainLoopCancellationTokenSource = new CancellationTokenSource(); + closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); + Run(_mainLoopCancellationTokenSource.Token); } @@ -284,26 +277,34 @@ namespace Avalonia Run(_mainLoopCancellationTokenSource.Token); } - protected virtual void OnStartup() + protected virtual void OnStartup(StartupEventArgs e) { - Startup?.Invoke(this, EventArgs.Empty); + Startup?.Invoke(this, e); } - protected virtual void OnExit() + protected virtual void OnExit(ExitEventArgs e) { - Exit?.Invoke(this, EventArgs.Empty); + Exit?.Invoke(this, e); + + Environment.ExitCode = e.ApplicationExitCode; } /// public void Shutdown() + { + Shutdown(0); + } + + /// + public void Shutdown(int exitCode) { IsShuttingDown = true; Windows.Clear(); - _mainLoopCancellationTokenSource?.Cancel(); + OnExit(new ExitEventArgs { ApplicationExitCode = exitCode }); - OnExit(); + _mainLoopCancellationTokenSource?.Cancel(); } /// @@ -314,8 +315,6 @@ namespace Avalonia Styles.TryGetResource(key, out value); } - - /// /// Register's the services needed by Avalonia. /// diff --git a/src/Avalonia.Controls/IApplicationLifecycle.cs b/src/Avalonia.Controls/IApplicationLifecycle.cs index c088ceb0eb..958c448d3e 100644 --- a/src/Avalonia.Controls/IApplicationLifecycle.cs +++ b/src/Avalonia.Controls/IApplicationLifecycle.cs @@ -10,16 +10,22 @@ namespace Avalonia.Controls /// /// Sent when the application is starting up. /// - event EventHandler Startup; + event EventHandler Startup; /// /// Sent when the application is exiting. /// - event EventHandler Exit; + event EventHandler Exit; /// - /// Exits the application. + /// Shuts down an application that returns the specified exit code to the operating system. /// void Shutdown(); + + /// + /// Shuts down an application that returns the specified exit code to the operating system. + /// + /// An integer exit code for an application. The default exit code is 0. + void Shutdown(int exitCode); } } diff --git a/src/Avalonia.Styling/Controls/ExitEventArgs.cs b/src/Avalonia.Styling/Controls/ExitEventArgs.cs new file mode 100644 index 0000000000..1a4ee15f17 --- /dev/null +++ b/src/Avalonia.Styling/Controls/ExitEventArgs.cs @@ -0,0 +1,12 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + +namespace Avalonia.Controls +{ + public class ExitEventArgs : EventArgs + { + public int ApplicationExitCode { get; set; } + } +} diff --git a/src/Avalonia.Styling/Controls/StartupEventArgs.cs b/src/Avalonia.Styling/Controls/StartupEventArgs.cs new file mode 100644 index 0000000000..8b66ce0dc2 --- /dev/null +++ b/src/Avalonia.Styling/Controls/StartupEventArgs.cs @@ -0,0 +1,30 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Avalonia.Controls +{ + public class StartupEventArgs : EventArgs + { + private string[] _args; + + public IReadOnlyList Args => _args ?? (_args = GetArgs()); + + private static string[] GetArgs() + { + try + { + var args = Environment.GetCommandLineArgs(); + + return args.Length > 1 ? args.Skip(1).ToArray() : new string[0]; + } + catch (NotSupportedException) + { + return new string[0]; + } + } + } +} diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs index 653a51232b..e2fc6ab7a2 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Avalonia.DesignerSupport.TestApp { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } From 7e042158977c4111a67e901b21bbfe289897033e Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 12 Apr 2019 11:41:59 +0200 Subject: [PATCH 06/36] No longer set first window opened as MainWindow --- samples/ControlCatalog/App.xaml.cs | 2 ++ src/Avalonia.Controls/Application.cs | 2 ++ src/Avalonia.Controls/Window.cs | 14 -------------- .../ApplicationTests.cs | 13 ------------- 4 files changed, 4 insertions(+), 27 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 1f466d9f91..100cfa634e 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -15,6 +15,8 @@ namespace ControlCatalog var mainWindow = new MainWindow(); mainWindow.Show(); + + MainWindow = mainWindow; } } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 2893505655..b750e570b3 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -272,6 +272,8 @@ namespace Avalonia { mainWindow.Show(); } + + MainWindow = mainWindow; } Run(_mainLoopCancellationTokenSource.Token); diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 648256e667..411685800e 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -49,8 +49,6 @@ namespace Avalonia.Controls /// public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope { - private static bool s_hasAddedFirstWindow; - /// /// Defines the property. /// @@ -262,18 +260,6 @@ namespace Avalonia.Controls } Application.Current.Windows.Add(window); - - if (s_hasAddedFirstWindow) - { - return; - } - - s_hasAddedFirstWindow = true; - - if (Application.Current.MainWindow == null) - { - Application.Current.MainWindow = window; - } } private static void RemoveWindow(Window window) diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index 7cedd9b69a..f362e691dc 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -129,18 +129,5 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } } - - [Fact] - public void Should_Have_MainWindow_After_First_Window_Shown() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var mainWindow = new Window(); - - mainWindow.Show(); - - Assert.Equal(mainWindow, Application.Current.MainWindow); - } - } } } From 05978b0ba620649e547f4f778ed145d407abfc94 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 12 Apr 2019 11:50:53 +0200 Subject: [PATCH 07/36] Make sure Shutdown is only called once --- src/Avalonia.Controls/Application.cs | 33 +++++++++---------- .../Controls/ExitEventArgs.cs | 6 +++- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index b750e570b3..99fc2e7367 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -215,21 +215,6 @@ namespace Avalonia Run(_mainLoopCancellationTokenSource.Token); } - /// - /// Runs the application's main loop until the is canceled. - /// - /// The token to track - public void Run(CancellationToken token) - { - Dispatcher.UIThread.MainLoop(token); - - // Make sure we call OnExit in case an error happened and OnExit() wasn't called explicitly - if (!IsShuttingDown) - { - OnExit(new ExitEventArgs()); - } - } - /// /// Runs the application's main loop until the is closed. /// @@ -279,6 +264,17 @@ namespace Avalonia Run(_mainLoopCancellationTokenSource.Token); } + /// + /// Runs the application's main loop until the is canceled. + /// + /// The token to track + public void Run(CancellationToken token) + { + Dispatcher.UIThread.MainLoop(token); + + Shutdown(); + } + protected virtual void OnStartup(StartupEventArgs e) { Startup?.Invoke(this, e); @@ -287,8 +283,6 @@ namespace Avalonia protected virtual void OnExit(ExitEventArgs e) { Exit?.Invoke(this, e); - - Environment.ExitCode = e.ApplicationExitCode; } /// @@ -300,6 +294,11 @@ namespace Avalonia /// public void Shutdown(int exitCode) { + if (IsShuttingDown) + { + return; + } + IsShuttingDown = true; Windows.Clear(); diff --git a/src/Avalonia.Styling/Controls/ExitEventArgs.cs b/src/Avalonia.Styling/Controls/ExitEventArgs.cs index 1a4ee15f17..2e6409e296 100644 --- a/src/Avalonia.Styling/Controls/ExitEventArgs.cs +++ b/src/Avalonia.Styling/Controls/ExitEventArgs.cs @@ -7,6 +7,10 @@ namespace Avalonia.Controls { public class ExitEventArgs : EventArgs { - public int ApplicationExitCode { get; set; } + public int ApplicationExitCode + { + get => Environment.ExitCode; + set => Environment.ExitCode = value; + } } } From 21eda0db12c13b27a0671ec2df8b04c9011c461d Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sat, 20 Apr 2019 18:36:51 +0200 Subject: [PATCH 08/36] Revert removal of Application.Initialize --- samples/BindingDemo/App.xaml.cs | 2 +- samples/ControlCatalog.NetCore/Program.cs | 2 +- samples/ControlCatalog/App.xaml.cs | 11 +- samples/PlatformSanityChecks/App.xaml.cs | 2 +- samples/Previewer/App.xaml.cs | 2 +- samples/RenderDemo/App.xaml.cs | 2 +- samples/VirtualizationDemo/App.xaml.cs | 2 +- .../interop/Direct3DInteropSample/App.paml.cs | 2 +- src/Avalonia.Controls/AppBuilderBase.cs | 1 + src/Avalonia.Controls/Application.cs | 104 +++++++++++------- .../Controls/ExitEventArgs.cs | 6 +- .../ApplicationTests.cs | 25 +++++ .../App.xaml.cs | 2 +- 13 files changed, 102 insertions(+), 61 deletions(-) diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs index 88333857b8..f2f44cd502 100644 --- a/samples/BindingDemo/App.xaml.cs +++ b/samples/BindingDemo/App.xaml.cs @@ -10,7 +10,7 @@ namespace BindingDemo { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 24f88ecba0..6a8175129f 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -31,7 +31,7 @@ namespace ControlCatalog.NetCore ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - BuildAvaloniaApp().Start(); + BuildAvaloniaApp().Start(); } /// diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 100cfa634e..d862749132 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -1,22 +1,13 @@ using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace ControlCatalog { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { - base.OnStartup(e); - AvaloniaXamlLoader.Load(this); - - var mainWindow = new MainWindow(); - - mainWindow.Show(); - - MainWindow = mainWindow; } } } diff --git a/samples/PlatformSanityChecks/App.xaml.cs b/samples/PlatformSanityChecks/App.xaml.cs index b8f8350156..0f9c004630 100644 --- a/samples/PlatformSanityChecks/App.xaml.cs +++ b/samples/PlatformSanityChecks/App.xaml.cs @@ -6,7 +6,7 @@ namespace PlatformSanityChecks { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs index 0fc42583a5..6d7d051218 100644 --- a/samples/Previewer/App.xaml.cs +++ b/samples/Previewer/App.xaml.cs @@ -6,7 +6,7 @@ namespace Previewer { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index b758f887df..ce75a335d5 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -11,7 +11,7 @@ namespace RenderDemo { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/samples/VirtualizationDemo/App.xaml.cs b/samples/VirtualizationDemo/App.xaml.cs index 787bbf8d11..637c8726f6 100644 --- a/samples/VirtualizationDemo/App.xaml.cs +++ b/samples/VirtualizationDemo/App.xaml.cs @@ -9,7 +9,7 @@ namespace VirtualizationDemo { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs index b08c02509c..5269854138 100644 --- a/samples/interop/Direct3DInteropSample/App.paml.cs +++ b/samples/interop/Direct3DInteropSample/App.paml.cs @@ -6,7 +6,7 @@ namespace Direct3DInteropSample { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 697c838b6a..419064b051 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -311,6 +311,7 @@ namespace Avalonia.Controls WindowingSubsystemInitializer(); RenderingSubsystemInitializer(); Instance.RegisterServices(); + Instance.Initialize(); AfterSetupCallback(Self); } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 99fc2e7367..e0f7b84e98 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -43,8 +43,8 @@ namespace Avalonia private readonly Styler _styler = new Styler(); private Styles _styles; private IResourceDictionary _resources; - private CancellationTokenSource _mainLoopCancellationTokenSource; + private int _exitCode; /// /// Initializes a new instance of the class. @@ -201,53 +201,40 @@ namespace Avalonia /// internal bool IsShuttingDown { get; private set; } - public void Run() - { - if (_mainLoopCancellationTokenSource != null) - { - throw new Exception("Run should only called once"); - } - - _mainLoopCancellationTokenSource = new CancellationTokenSource(); - - Dispatcher.UIThread.Post(() => OnStartup(new StartupEventArgs()), DispatcherPriority.Send); + /// + /// Initializes the application by loading XAML etc. + /// + public virtual void Initialize() { } - Run(_mainLoopCancellationTokenSource.Token); + public int Run() + { + return Run(new CancellationTokenSource()); } /// /// Runs the application's main loop until the is closed. /// /// The closable to track - public void Run(ICloseable closable) + public int Run(ICloseable closable) { - if (_mainLoopCancellationTokenSource != null) - { - throw new Exception("Run should only called once"); - } - - _mainLoopCancellationTokenSource = new CancellationTokenSource(); - closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); - Run(_mainLoopCancellationTokenSource.Token); + return Run(new CancellationTokenSource()); } /// /// Runs the application's main loop until some condition occurs that is specified by ExitMode. /// /// The main window - public void Run(Window mainWindow) + public int Run(Window mainWindow) { - if (_mainLoopCancellationTokenSource != null) + Dispatcher.UIThread.Post(() => { - throw new Exception("Run should only called once"); - } - - _mainLoopCancellationTokenSource = new CancellationTokenSource(); + if (MainWindow != null) + { + return; + } - if (MainWindow == null) - { if (mainWindow == null) { throw new ArgumentNullException(nameof(mainWindow)); @@ -259,20 +246,44 @@ namespace Avalonia } MainWindow = mainWindow; - } + }); - Run(_mainLoopCancellationTokenSource.Token); + return Run(new CancellationTokenSource()); } /// /// Runs the application's main loop until the is canceled. /// /// The token to track - public void Run(CancellationToken token) + public int Run(CancellationToken token) { - Dispatcher.UIThread.MainLoop(token); + return Run(CancellationTokenSource.CreateLinkedTokenSource(token)); + } - Shutdown(); + private int Run(CancellationTokenSource tokenSource) + { + if (IsShuttingDown) + { + throw new InvalidOperationException("Application is shutting down."); + } + + if (_mainLoopCancellationTokenSource != null) + { + throw new InvalidOperationException("Application is already running."); + } + + _mainLoopCancellationTokenSource = tokenSource; + + Dispatcher.UIThread.Post(() => OnStartup(new StartupEventArgs()), DispatcherPriority.Send); + + Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); + + if (!IsShuttingDown) + { + Shutdown(_exitCode); + } + + return _exitCode; } protected virtual void OnStartup(StartupEventArgs e) @@ -296,16 +307,33 @@ namespace Avalonia { if (IsShuttingDown) { - return; + throw new InvalidOperationException("Application is already shutting down."); } - IsShuttingDown = true; + _exitCode = exitCode; + + IsShuttingDown = true; Windows.Clear(); - OnExit(new ExitEventArgs { ApplicationExitCode = exitCode }); + try + { + var e = new ExitEventArgs { ApplicationExitCode = _exitCode }; + + OnExit(e); - _mainLoopCancellationTokenSource?.Cancel(); + _exitCode = e.ApplicationExitCode; + + Environment.ExitCode = _exitCode; + } + finally + { + _mainLoopCancellationTokenSource?.Cancel(); + + _mainLoopCancellationTokenSource = null; + + IsShuttingDown = false; + } } /// diff --git a/src/Avalonia.Styling/Controls/ExitEventArgs.cs b/src/Avalonia.Styling/Controls/ExitEventArgs.cs index 2e6409e296..1a4ee15f17 100644 --- a/src/Avalonia.Styling/Controls/ExitEventArgs.cs +++ b/src/Avalonia.Styling/Controls/ExitEventArgs.cs @@ -7,10 +7,6 @@ namespace Avalonia.Controls { public class ExitEventArgs : EventArgs { - public int ApplicationExitCode - { - get => Environment.ExitCode; - set => Environment.ExitCode = value; - } + public int ApplicationExitCode { get; set; } } } diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index f362e691dc..f9b1ea251b 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Avalonia.Threading; using Avalonia.UnitTests; using Xunit; @@ -129,5 +130,29 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } } + + [Fact] + public void Throws_InvalidOperationException_On_Run_When_Application_Is_Already_Running() + { + using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + { + Application.Current.Run(); + + Assert.Throws(() => { Application.Current.Run(); }); + } + } + + [Fact] + public void Should_Set_ExitCode_After_Shutdown() + { + using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + { + Application.Current.Shutdown(1337); + + var exitCode = Application.Current.Run(); + + Assert.Equal(1337, exitCode); + } + } } } diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs index e2fc6ab7a2..caa3c1baa8 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs @@ -5,7 +5,7 @@ namespace Avalonia.DesignerSupport.TestApp { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } From c73363f76b659b52cbd96bdccb82d228f22c94fd Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sat, 20 Apr 2019 18:44:15 +0200 Subject: [PATCH 09/36] Remove extra usings --- samples/ControlCatalog.Desktop/Program.cs | 7 +------ samples/ControlCatalog.NetCore/Program.cs | 12 +++++++++--- samples/Previewer/App.xaml.cs | 1 - samples/RenderDemo/App.xaml.cs | 1 - samples/VirtualizationDemo/App.xaml.cs | 1 - samples/interop/Direct3DInteropSample/App.paml.cs | 1 - tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs | 5 ++--- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index 2bfd223bb6..b7aa34f5ba 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -16,12 +16,7 @@ namespace ControlCatalog { // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. - BuildAvaloniaApp().Start(AppMain, args); - } - - private static void AppMain(Application app, string[] args) - { - app.Run(); + BuildAvaloniaApp().Start(); } /// diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 6a8175129f..c8f3fb9921 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -9,7 +9,8 @@ using Avalonia.ReactiveUI; namespace ControlCatalog.NetCore { static class Program - { + { + static void Main(string[] args) { Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA); @@ -28,10 +29,15 @@ namespace ControlCatalog.NetCore AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => { tl.Content = new MainView(); - ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); + System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - BuildAvaloniaApp().Start(); + BuildAvaloniaApp().Start(AppMain, args); + } + + static void AppMain(Application app, string[] args) + { + app.Run(new MainWindow()); } /// diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs index 6d7d051218..4c18874b04 100644 --- a/samples/Previewer/App.xaml.cs +++ b/samples/Previewer/App.xaml.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Previewer diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index ce75a335d5..d95018520a 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia; -using Avalonia.Controls; using Avalonia.Logging.Serilog; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; diff --git a/samples/VirtualizationDemo/App.xaml.cs b/samples/VirtualizationDemo/App.xaml.cs index 637c8726f6..b220807443 100644 --- a/samples/VirtualizationDemo/App.xaml.cs +++ b/samples/VirtualizationDemo/App.xaml.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace VirtualizationDemo diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs index 5269854138..1b6d5fd39c 100644 --- a/samples/interop/Direct3DInteropSample/App.paml.cs +++ b/samples/interop/Direct3DInteropSample/App.paml.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Direct3DInteropSample diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs index caa3c1baa8..5ee3487b6b 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs @@ -1,11 +1,10 @@ -using Avalonia.Controls; -using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml; namespace Avalonia.DesignerSupport.TestApp { public class App : Application { - public override void Initialize() + public override void Initialize() { AvaloniaXamlLoader.Load(this); } From a5313ab3fb08b25050960a7d2f5c077a630e38b0 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sat, 20 Apr 2019 18:50:35 +0200 Subject: [PATCH 10/36] Revert changes --- samples/PlatformSanityChecks/App.xaml.cs | 1 - samples/Previewer/App.xaml.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/PlatformSanityChecks/App.xaml.cs b/samples/PlatformSanityChecks/App.xaml.cs index 0f9c004630..508fc1e34b 100644 --- a/samples/PlatformSanityChecks/App.xaml.cs +++ b/samples/PlatformSanityChecks/App.xaml.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace PlatformSanityChecks diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs index 4c18874b04..fffa987a27 100644 --- a/samples/Previewer/App.xaml.cs +++ b/samples/Previewer/App.xaml.cs @@ -11,4 +11,4 @@ namespace Previewer } } -} +} \ No newline at end of file From a14435f59f49c6df75a8b9f1b11dd57e8dae27e7 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sat, 27 Apr 2019 19:54:05 +0200 Subject: [PATCH 11/36] Fix unit tests --- src/Avalonia.Controls/Application.cs | 30 +++++++++--------- .../ApplicationTests.cs | 31 ++++++++++++++----- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index e0f7b84e98..6f5cc3d4d6 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -228,25 +228,23 @@ namespace Avalonia /// The main window public int Run(Window mainWindow) { - Dispatcher.UIThread.Post(() => + if (mainWindow == null) { - if (MainWindow != null) - { - return; - } - - if (mainWindow == null) - { - throw new ArgumentNullException(nameof(mainWindow)); - } + throw new ArgumentNullException(nameof(mainWindow)); + } - if (!mainWindow.IsVisible) + if (MainWindow == null) + { + Dispatcher.UIThread.Post(() => { - mainWindow.Show(); - } - - MainWindow = mainWindow; - }); + if (!mainWindow.IsVisible) + { + mainWindow.Show(); + } + + MainWindow = mainWindow; + }); + } return Run(new CancellationTokenSource()); } diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index f9b1ea251b..b3ce1a3866 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -18,6 +18,10 @@ namespace Avalonia.Controls.UnitTests { Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose; + var hasExit = false; + + Application.Current.Exit += (s, e) => hasExit = true; + var mainWindow = new Window(); mainWindow.Show(); @@ -30,7 +34,7 @@ namespace Avalonia.Controls.UnitTests mainWindow.Close(); - Assert.True(Application.Current.IsShuttingDown); + Assert.True(hasExit); } } @@ -41,6 +45,10 @@ namespace Avalonia.Controls.UnitTests { Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose; + var hasExit = false; + + Application.Current.Exit += (s, e) => hasExit = true; + var windowA = new Window(); windowA.Show(); @@ -51,11 +59,11 @@ namespace Avalonia.Controls.UnitTests windowA.Close(); - Assert.False(Application.Current.IsShuttingDown); + Assert.False(hasExit); windowB.Close(); - Assert.True(Application.Current.IsShuttingDown); + Assert.True(hasExit); } } @@ -66,6 +74,10 @@ namespace Avalonia.Controls.UnitTests { Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; + var hasExit = false; + + Application.Current.Exit += (s, e) => hasExit = true; + var windowA = new Window(); windowA.Show(); @@ -76,15 +88,15 @@ namespace Avalonia.Controls.UnitTests windowA.Close(); - Assert.False(Application.Current.IsShuttingDown); + Assert.False(hasExit); windowB.Close(); - Assert.False(Application.Current.IsShuttingDown); + Assert.False(hasExit); Application.Current.Shutdown(); - Assert.True(Application.Current.IsShuttingDown); + Assert.True(hasExit); } } @@ -136,9 +148,12 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) { - Application.Current.Run(); + Application.Current.Startup += (s, e) => + { + Assert.Throws(() => { Application.Current.Run(); }); + }; - Assert.Throws(() => { Application.Current.Run(); }); + Application.Current.Run(); } } From fa04a50f85675fc214b3913a1de8141f5c962431 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 29 Apr 2019 15:26:07 +0200 Subject: [PATCH 12/36] Remove non working test --- .../ApplicationTests.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index b3ce1a3866..b7d752dfe3 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -143,20 +143,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Throws_InvalidOperationException_On_Run_When_Application_Is_Already_Running() - { - using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) - { - Application.Current.Startup += (s, e) => - { - Assert.Throws(() => { Application.Current.Run(); }); - }; - - Application.Current.Run(); - } - } - [Fact] public void Should_Set_ExitCode_After_Shutdown() { From d25fba75e5a6702a04f6f66c243bbd8570b1215d Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sat, 11 May 2019 16:38:31 +0200 Subject: [PATCH 13/36] Add xml comments --- src/Avalonia.Controls/Application.cs | 47 +++++++++++++------ .../ExitEventArgs.cs | 6 +++ .../IApplicationLifecycle.cs | 9 +--- .../StartupEventArgs.cs | 6 +++ 4 files changed, 47 insertions(+), 21 deletions(-) rename src/{Avalonia.Styling/Controls => Avalonia.Controls}/ExitEventArgs.cs (52%) rename src/{Avalonia.Styling/Controls => Avalonia.Controls}/StartupEventArgs.cs (76%) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 6f5cc3d4d6..62f49f0f88 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -206,15 +206,25 @@ namespace Avalonia /// public virtual void Initialize() { } + /// + /// Runs the application's main loop. + /// This will return when the condition is met + /// or was called. + /// + /// The application's exit code that is passed on the operating system. public int Run() { return Run(new CancellationTokenSource()); } /// - /// Runs the application's main loop until the is closed. + /// Runs the application's main loop. + /// This will return when the condition is met + /// or was called. + /// This also returns when is closed. /// - /// The closable to track + /// The closable to track. + /// The application's exit code that is passed on the operating system. public int Run(ICloseable closable) { closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); @@ -223,9 +233,13 @@ namespace Avalonia } /// - /// Runs the application's main loop until some condition occurs that is specified by ExitMode. + /// Runs the application's main loop. + /// This will return when the condition is met + /// or was called. /// - /// The main window + /// The window that is used as + /// when the isn't already set. + /// The application's exit code that is passed on the operating system. public int Run(Window mainWindow) { if (mainWindow == null) @@ -248,11 +262,14 @@ namespace Avalonia return Run(new CancellationTokenSource()); } - /// - /// Runs the application's main loop until the is canceled. + /// Runs the application's main loop. + /// This will return when the condition is met + /// or was called. + /// This also returns when the is canceled. /// - /// The token to track + /// The application's exit code that is passed on the operating system. + /// The token to track. public int Run(CancellationToken token) { return Run(CancellationTokenSource.CreateLinkedTokenSource(token)); @@ -284,24 +301,26 @@ namespace Avalonia return _exitCode; } + /// + /// Raises the event. + /// + /// A that contains the event data. protected virtual void OnStartup(StartupEventArgs e) { Startup?.Invoke(this, e); } + /// + /// Raises the event. + /// + /// A that contains the event data. protected virtual void OnExit(ExitEventArgs e) { Exit?.Invoke(this, e); } /// - public void Shutdown() - { - Shutdown(0); - } - - /// - public void Shutdown(int exitCode) + public void Shutdown(int exitCode = 0) { if (IsShuttingDown) { diff --git a/src/Avalonia.Styling/Controls/ExitEventArgs.cs b/src/Avalonia.Controls/ExitEventArgs.cs similarity index 52% rename from src/Avalonia.Styling/Controls/ExitEventArgs.cs rename to src/Avalonia.Controls/ExitEventArgs.cs index 1a4ee15f17..c99f7fe047 100644 --- a/src/Avalonia.Styling/Controls/ExitEventArgs.cs +++ b/src/Avalonia.Controls/ExitEventArgs.cs @@ -5,8 +5,14 @@ using System; namespace Avalonia.Controls { + /// + /// Contains the arguments for the event. + /// public class ExitEventArgs : EventArgs { + /// + /// Gets or sets the exit code that an application returns to the operating system when the application exits. + /// public int ApplicationExitCode { get; set; } } } diff --git a/src/Avalonia.Controls/IApplicationLifecycle.cs b/src/Avalonia.Controls/IApplicationLifecycle.cs index 958c448d3e..a3c6599b20 100644 --- a/src/Avalonia.Controls/IApplicationLifecycle.cs +++ b/src/Avalonia.Controls/IApplicationLifecycle.cs @@ -18,14 +18,9 @@ namespace Avalonia.Controls event EventHandler Exit; /// - /// Shuts down an application that returns the specified exit code to the operating system. - /// - void Shutdown(); - - /// - /// Shuts down an application that returns the specified exit code to the operating system. + /// Shuts down the application and sets the exit code that is returned to the operating system when the application exits. /// /// An integer exit code for an application. The default exit code is 0. - void Shutdown(int exitCode); + void Shutdown(int exitCode = 0); } } diff --git a/src/Avalonia.Styling/Controls/StartupEventArgs.cs b/src/Avalonia.Controls/StartupEventArgs.cs similarity index 76% rename from src/Avalonia.Styling/Controls/StartupEventArgs.cs rename to src/Avalonia.Controls/StartupEventArgs.cs index 8b66ce0dc2..17dcbb2aa9 100644 --- a/src/Avalonia.Styling/Controls/StartupEventArgs.cs +++ b/src/Avalonia.Controls/StartupEventArgs.cs @@ -7,10 +7,16 @@ using System.Linq; namespace Avalonia.Controls { + /// + /// Contains the arguments for the event. + /// public class StartupEventArgs : EventArgs { private string[] _args; + /// + /// Gets command line arguments that were passed to the application. + /// public IReadOnlyList Args => _args ?? (_args = GetArgs()); private static string[] GetArgs() From 19593825d5f4a472a8f726a9b17fb90542d3804a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sun, 12 May 2019 11:24:09 +0200 Subject: [PATCH 14/36] Fix comments --- src/Avalonia.Controls/Application.cs | 42 +++++++++++-------- src/Avalonia.Controls/ShutdownMode.cs | 8 ++-- src/Avalonia.Controls/StartupEventArgs.cs | 2 +- .../App.xaml.cs | 7 +++- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 62f49f0f88..bbea3693cc 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -208,10 +208,12 @@ namespace Avalonia /// /// Runs the application's main loop. - /// This will return when the condition is met - /// or was called. /// - /// The application's exit code that is passed on the operating system. + /// + /// This will return when the condition is met + /// or was called. + /// + /// The application's exit code that is returned to the operating system on termination. public int Run() { return Run(new CancellationTokenSource()); @@ -219,12 +221,14 @@ namespace Avalonia /// /// Runs the application's main loop. - /// This will return when the condition is met - /// or was called. - /// This also returns when is closed. /// + /// + /// This will return when the condition is met + /// or was called. + /// This also returns when is closed. + /// /// The closable to track. - /// The application's exit code that is passed on the operating system. + /// The application's exit code that is returned to the operating system on termination. public int Run(ICloseable closable) { closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); @@ -234,12 +238,14 @@ namespace Avalonia /// /// Runs the application's main loop. - /// This will return when the condition is met - /// or was called. /// + /// + /// This will return when the condition is met + /// or was called. + /// /// The window that is used as /// when the isn't already set. - /// The application's exit code that is passed on the operating system. + /// The application's exit code that is returned to the operating system on termination. public int Run(Window mainWindow) { if (mainWindow == null) @@ -264,11 +270,13 @@ namespace Avalonia } /// /// Runs the application's main loop. - /// This will return when the condition is met - /// or was called. - /// This also returns when the is canceled. /// - /// The application's exit code that is passed on the operating system. + /// + /// This will return when the condition is met + /// or was called. + /// This also returns when the is canceled. + /// + /// The application's exit code that is returned to the operating system on termination. /// The token to track. public int Run(CancellationToken token) { @@ -339,9 +347,7 @@ namespace Avalonia OnExit(e); - _exitCode = e.ApplicationExitCode; - - Environment.ExitCode = _exitCode; + _exitCode = e.ApplicationExitCode; } finally { @@ -350,6 +356,8 @@ namespace Avalonia _mainLoopCancellationTokenSource = null; IsShuttingDown = false; + + Environment.ExitCode = _exitCode; } } diff --git a/src/Avalonia.Controls/ShutdownMode.cs b/src/Avalonia.Controls/ShutdownMode.cs index 8c52d4b70e..46e27ff4e1 100644 --- a/src/Avalonia.Controls/ShutdownMode.cs +++ b/src/Avalonia.Controls/ShutdownMode.cs @@ -1,20 +1,20 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -namespace Avalonia +namespace Avalonia.Controls { /// - /// Enum for ExitMode + /// Describes the possible values for . /// public enum ShutdownMode { /// - /// Indicates an implicit call to Application.Exit when the last window closes. + /// Indicates an implicit call to Application.Shutdown when the last window closes. /// OnLastWindowClose, /// - /// Indicates an implicit call to Application.Exit when the main window closes. + /// Indicates an implicit call to Application.Shutdown when the main window closes. /// OnMainWindowClose, diff --git a/src/Avalonia.Controls/StartupEventArgs.cs b/src/Avalonia.Controls/StartupEventArgs.cs index 17dcbb2aa9..0e12f5c01a 100644 --- a/src/Avalonia.Controls/StartupEventArgs.cs +++ b/src/Avalonia.Controls/StartupEventArgs.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls private string[] _args; /// - /// Gets command line arguments that were passed to the application. + /// Gets the command line arguments that were passed to the application. /// public IReadOnlyList Args => _args ?? (_args = GetArgs()); diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs index 5ee3487b6b..653a51232b 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs @@ -1,4 +1,9 @@ -using Avalonia.Markup.Xaml; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Markup.Xaml; namespace Avalonia.DesignerSupport.TestApp { From 1312626aed977f131c1007ba7fa5b73a373e543a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 May 2019 17:32:38 +0200 Subject: [PATCH 15/36] Added failing test for #2512. --- .../Xaml/DataTemplateTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs index ce51e7ad72..61155c3c46 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs @@ -38,6 +38,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void DataTemplate_Can_Contain_Named_UserControl() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var itemsControl = window.FindControl("itemsControl"); + + window.DataContext = new[] { "item1", "item2" }; + + window.ApplyTemplate(); + itemsControl.ApplyTemplate(); + itemsControl.Presenter.ApplyTemplate(); + + Assert.Equal(2, itemsControl.Presenter.Panel.Children.Count); + } + } + [Fact] public void Can_Set_DataContext_In_DataTemplate() { From 208361b8faa597273d853d8661119fa0ee39043c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 May 2019 17:18:51 +0200 Subject: [PATCH 16/36] Don't register controls with parent namescope. This reverts the changes in #843 because they were causing problems, as described by #2512. This should however not cause #829 to reappear because since #843 was merged we moved to Portable.Xaml and we're now registering controls with the root namescope in the XAML engine: https://github.com/AvaloniaUI/Avalonia/blob/b4577a1631755b391f3768e00264ac86c4300507/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs#L67 Fixes #2512 --- .../Primitives/TemplatedControl.cs | 2 +- src/Avalonia.Styling/StyledElement.cs | 17 -------------- .../Xaml/BasicTests.cs | 16 +++++++++++++ .../StyledElementTests.cs | 7 ++---- .../StyledElementTests_NameScope.cs | 23 ------------------- 5 files changed, 19 insertions(+), 46 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index ba4c5027d0..32e220b789 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -357,7 +357,7 @@ namespace Avalonia.Controls.Primitives if (control.TemplatedParent == this) { - foreach (IControl child in control.GetVisualChildren()) + foreach (IControl child in control.GetLogicalChildren()) { RegisterNames(child, nameScope); } diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index d314a8d44e..ae2cec5561 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -677,23 +677,6 @@ namespace Avalonia if (Name != null) { _nameScope?.Register(Name, this); - - var visualParent = Parent as StyledElement; - - if (this is INameScope && visualParent != null) - { - // If we have e.g. a named UserControl in a window then we want that control - // to be findable by name from the Window, so register with both name scopes. - // This differs from WPF's behavior in that XAML manually registers controls - // with name scopes based on the XAML file in which the name attribute appears, - // but we're trying to avoid XAML magic in Avalonia in order to made code- - // created UIs easy. This will cause problems if a UserControl declares a name - // in its XAML and that control is included multiple times in a parent control - // (as the name will be duplicated), however at the moment I'm fine with saying - // "don't do that". - var parentNameScope = NameScope.FindNameScope(visualParent); - parentNameScope?.Register(Name, this); - } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 2e67541c1f..743fc82f29 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -197,6 +197,22 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal("Foo", button.Content); } + [Fact] + public void Named_UserControl_Is_Added_To_Parent_NameScope() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + + var control = AvaloniaXamlLoader.Parse(xaml); + + Assert.NotNull(control.FindControl("foo")); + } + } + [Fact] public void Direct_Content_In_ItemsControl_Is_Operational() { diff --git a/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs index 4970addd81..7fdd70799f 100644 --- a/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs +++ b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs @@ -273,13 +273,10 @@ namespace Avalonia.Styling.UnitTests var root = new TestRoot(); var child = new Border(); - ((ISupportInitialize)child).BeginInit(); + child.BeginInit(); root.Child = child; child.Name = "foo"; - Assert.Null(root.FindControl("foo")); - ((ISupportInitialize)child).EndInit(); - - Assert.Same(root.FindControl("foo"), child); + child.EndInit(); } } diff --git a/tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs b/tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs index 47c540f44a..47c34dfd38 100644 --- a/tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs +++ b/tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs @@ -1,11 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Templates; -using Avalonia.Rendering; -using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -70,23 +65,5 @@ namespace Avalonia.Controls.UnitTests Assert.Null(NameScope.GetNameScope((StyledElement)root.Presenter).Find("foo")); } - - [Fact] - public void Control_That_Is_NameScope_Should_Register_With_Parent_NameScope() - { - UserControl userControl; - var root = new TestTemplatedRoot - { - Content = userControl = new UserControl - { - Name = "foo", - } - }; - - root.ApplyTemplate(); - - Assert.Same(userControl, root.FindControl("foo")); - Assert.Same(userControl, userControl.FindControl("foo")); - } } } From 6b0ded4fc2866e4c7ef38c702281d1e03a833e5e Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 May 2019 16:42:03 +0800 Subject: [PATCH 17/36] Track old `TransitionInstances` and dispose when its invalid. Fixes #2485 --- src/Avalonia.Animation/Animatable.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 4b0f76c5d5..445c490b4b 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Reactive.Linq; using Avalonia.Collections; using Avalonia.Data; -using Avalonia.Animation.Animators; +using Avalonia.Animation.Animators; namespace Avalonia.Animation { @@ -36,6 +36,9 @@ namespace Avalonia.Animation private Transitions _transitions; + private Dictionary _previousTransitions + = new Dictionary(); + /// /// Gets or sets the property transitions for the control. /// @@ -58,7 +61,12 @@ namespace Avalonia.Animation if (match != null) { - match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue); + if (_previousTransitions.TryGetValue(e.Property, out var dispose)) + dispose.Dispose(); + + var instance = match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue); + + _previousTransitions[e.Property] = instance; } } } From ffe56b55bcf00ec9b10a6262643b73e8ed0d3830 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 May 2019 16:44:38 +0800 Subject: [PATCH 18/36] Fix #2490 --- src/Avalonia.Animation/TransitionInstance.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index eff2c4e9f3..fe8103adb2 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -30,13 +30,22 @@ namespace Avalonia.Animation { var interpVal = (double)t.Ticks / _duration.Ticks; - if (interpVal > 1d || interpVal < 0d) + // Clamp interpolation value. + if (interpVal >= 1d) { + PublishNext(1d); PublishCompleted(); - return; } - - PublishNext(interpVal); + // Cut-off when interpolation value is negative. + else if (interpVal < 0d) + { + PublishNext(0d); + PublishCompleted(); + } + else + { + PublishNext(interpVal); + } } protected override void Unsubscribed() From 20284403816cb3b6a988958ffffdb87b7bb476bf Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 May 2019 16:48:55 +0800 Subject: [PATCH 19/36] Speedup Sidebar hover transitions. --- samples/ControlCatalog/SideBar.xaml | 2 +- samples/RenderDemo/SideBar.xaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 3bae7edb00..3047b1e519 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -56,7 +56,7 @@ - + diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml index 3af90f1844..e37b9bb5fc 100644 --- a/samples/RenderDemo/SideBar.xaml +++ b/samples/RenderDemo/SideBar.xaml @@ -47,7 +47,7 @@ - + From 3b93e72d45c11bed994142c37da525b763f633c0 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 May 2019 20:24:09 +0800 Subject: [PATCH 20/36] Dont initialize transition instance dictionary on objects without Transitions on it. --- src/Avalonia.Animation/Animatable.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 445c490b4b..3a3d00b94a 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -36,16 +36,27 @@ namespace Avalonia.Animation private Transitions _transitions; - private Dictionary _previousTransitions - = new Dictionary(); + private Dictionary _previousTransitions; /// /// Gets or sets the property transitions for the control. /// public Transitions Transitions { - get { return _transitions ?? (_transitions = new Transitions()); } - set { SetAndRaise(TransitionsProperty, ref _transitions, value); } + get + { + if (_transitions == null) + _transitions = new Transitions(); + + if (_previousTransitions == null) + _previousTransitions = new Dictionary(); + + return _transitions; + } + set + { + SetAndRaise(TransitionsProperty, ref _transitions, value); + } } /// @@ -55,7 +66,7 @@ namespace Avalonia.Animation /// The event args. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) { - if (e.Priority != BindingPriority.Animation && Transitions != null) + if (e.Priority != BindingPriority.Animation && Transitions != null && _previousTransitions != null) { var match = Transitions.FirstOrDefault(x => x.Property == e.Property); From 4a386d1b84e879bec43f89c084b2ed073c4f4484 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 14 May 2019 14:36:59 +0200 Subject: [PATCH 21/36] Added skipped failing leak test for transitions. It's leaking on master too, so address this separately. --- .../Properties/AssemblyInfo.cs | 5 +- tests/Avalonia.LeakTests/TransitionTests.cs | 60 +++++++++++++++++++ tests/Avalonia.UnitTests/MockGlobalClock.cs | 10 ++++ tests/Avalonia.UnitTests/TestServices.cs | 6 ++ .../Avalonia.UnitTests/UnitTestApplication.cs | 2 + 5 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.LeakTests/TransitionTests.cs create mode 100644 tests/Avalonia.UnitTests/MockGlobalClock.cs diff --git a/src/Avalonia.Animation/Properties/AssemblyInfo.cs b/src/Avalonia.Animation/Properties/AssemblyInfo.cs index 985a8e5bfe..eb38a66a84 100644 --- a/src/Avalonia.Animation/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Animation/Properties/AssemblyInfo.cs @@ -3,7 +3,10 @@ using Avalonia.Metadata; using System.Reflection; +using System.Runtime.CompilerServices; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")] -[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")] \ No newline at end of file +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")] + +[assembly: InternalsVisibleTo("Avalonia.LeakTests")] diff --git a/tests/Avalonia.LeakTests/TransitionTests.cs b/tests/Avalonia.LeakTests/TransitionTests.cs new file mode 100644 index 0000000000..c7add1fe11 --- /dev/null +++ b/tests/Avalonia.LeakTests/TransitionTests.cs @@ -0,0 +1,60 @@ +using System; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.UnitTests; +using JetBrains.dotMemoryUnit; +using Xunit; +using Xunit.Abstractions; + +namespace Avalonia.LeakTests +{ + [DotMemoryUnit(FailIfRunWithoutSupport = false)] + public class TransitionTests + { + public TransitionTests(ITestOutputHelper atr) + { + DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine); + } + + [Fact(Skip = "TODO: Fix this leak")] + public void Transition_On_StyledProperty_Is_Freed() + { + var clock = new MockGlobalClock(); + + using (UnitTestApplication.Start(new TestServices(globalClock: clock))) + { + Func run = () => + { + var border = new Border + { + Transitions = + { + new DoubleTransition + { + Duration = TimeSpan.FromSeconds(1), + Property = Border.OpacityProperty, + } + } + }; + + border.Opacity = 0; + + clock.Pulse(TimeSpan.FromSeconds(0)); + clock.Pulse(TimeSpan.FromSeconds(0.5)); + + Assert.Equal(0.5, border.Opacity); + + clock.Pulse(TimeSpan.FromSeconds(1)); + + Assert.Equal(0, border.Opacity); + return border; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + } + } +} diff --git a/tests/Avalonia.UnitTests/MockGlobalClock.cs b/tests/Avalonia.UnitTests/MockGlobalClock.cs new file mode 100644 index 0000000000..b53e5acc01 --- /dev/null +++ b/tests/Avalonia.UnitTests/MockGlobalClock.cs @@ -0,0 +1,10 @@ +using System; +using Avalonia.Animation; + +namespace Avalonia.UnitTests +{ + public class MockGlobalClock : ClockBase, IGlobalClock + { + public new void Pulse(TimeSpan systemTime) => base.Pulse(systemTime); + } +} diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index d68f1d167a..f7a878feba 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -16,6 +16,7 @@ using System.Reactive.Concurrency; using System.Collections.Generic; using Avalonia.Controls; using System.Reflection; +using Avalonia.Animation; namespace Avalonia.UnitTests { @@ -58,6 +59,7 @@ namespace Avalonia.UnitTests public TestServices( IAssetLoader assetLoader = null, IFocusManager focusManager = null, + IGlobalClock globalClock = null, IInputManager inputManager = null, Func keyboardDevice = null, IKeyboardNavigationHandler keyboardNavigation = null, @@ -75,6 +77,7 @@ namespace Avalonia.UnitTests { AssetLoader = assetLoader; FocusManager = focusManager; + GlobalClock = globalClock; InputManager = inputManager; KeyboardDevice = keyboardDevice; KeyboardNavigation = keyboardNavigation; @@ -93,6 +96,7 @@ namespace Avalonia.UnitTests public IAssetLoader AssetLoader { get; } public IInputManager InputManager { get; } public IFocusManager FocusManager { get; } + public IGlobalClock GlobalClock { get; } public Func KeyboardDevice { get; } public IKeyboardNavigationHandler KeyboardNavigation { get; } public Func MouseDevice { get; } @@ -109,6 +113,7 @@ namespace Avalonia.UnitTests public TestServices With( IAssetLoader assetLoader = null, IFocusManager focusManager = null, + IGlobalClock globalClock = null, IInputManager inputManager = null, Func keyboardDevice = null, IKeyboardNavigationHandler keyboardNavigation = null, @@ -127,6 +132,7 @@ namespace Avalonia.UnitTests return new TestServices( assetLoader: assetLoader ?? AssetLoader, focusManager: focusManager ?? FocusManager, + globalClock: globalClock ?? GlobalClock, inputManager: inputManager ?? InputManager, keyboardDevice: keyboardDevice ?? KeyboardDevice, keyboardNavigation: keyboardNavigation ?? KeyboardNavigation, diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 4802278c1e..3578471397 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -12,6 +12,7 @@ using Avalonia.Threading; using System.Reactive.Disposables; using System.Reactive.Concurrency; using Avalonia.Input.Platform; +using Avalonia.Animation; namespace Avalonia.UnitTests { @@ -52,6 +53,7 @@ namespace Avalonia.UnitTests AvaloniaLocator.CurrentMutable .Bind().ToConstant(Services.AssetLoader) .Bind().ToConstant(Services.FocusManager) + .Bind().ToConstant(Services.GlobalClock) .BindToSelf(this) .Bind().ToConstant(Services.InputManager) .Bind().ToConstant(Services.KeyboardDevice?.Invoke()) From e06a9dffc32733a58c7dcfa5ba2e8e0b7f49561c Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 May 2019 21:19:35 +0800 Subject: [PATCH 22/36] Partially revert Transition interpolation clamp. --- src/Avalonia.Animation/TransitionInstance.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index fe8103adb2..39dc36aa33 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -31,17 +31,11 @@ namespace Avalonia.Animation var interpVal = (double)t.Ticks / _duration.Ticks; // Clamp interpolation value. - if (interpVal >= 1d) + if (interpVal >= 1d | (interpVal < 0d)) { PublishNext(1d); PublishCompleted(); } - // Cut-off when interpolation value is negative. - else if (interpVal < 0d) - { - PublishNext(0d); - PublishCompleted(); - } else { PublishNext(interpVal); From b94f5975eb7d0f338b2983628df175a975230b9e Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 May 2019 21:21:19 +0800 Subject: [PATCH 23/36] Remove extra space. --- src/Avalonia.Animation/TransitionInstance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index 39dc36aa33..ebb6e2ca68 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -31,7 +31,7 @@ namespace Avalonia.Animation var interpVal = (double)t.Ticks / _duration.Ticks; // Clamp interpolation value. - if (interpVal >= 1d | (interpVal < 0d)) + if (interpVal >= 1d | (interpVal < 0d)) { PublishNext(1d); PublishCompleted(); From 26b8fd6a056a44b2c69e9a463c45e1971eae4782 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 May 2019 21:31:08 +0800 Subject: [PATCH 24/36] Add transitions unit test. --- src/Avalonia.Animation/TransitionInstance.cs | 2 +- .../AnimationTransitionsTest.cs | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Animation.UnitTests/AnimationTransitionsTest.cs diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index ebb6e2ca68..10ea6bf523 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -31,7 +31,7 @@ namespace Avalonia.Animation var interpVal = (double)t.Ticks / _duration.Ticks; // Clamp interpolation value. - if (interpVal >= 1d | (interpVal < 0d)) + if (interpVal >= 1d | interpVal < 0d) { PublishNext(1d); PublishCompleted(); diff --git a/tests/Avalonia.Animation.UnitTests/AnimationTransitionsTest.cs b/tests/Avalonia.Animation.UnitTests/AnimationTransitionsTest.cs new file mode 100644 index 0000000000..9014c40299 --- /dev/null +++ b/tests/Avalonia.Animation.UnitTests/AnimationTransitionsTest.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Avalonia.Data; +using Xunit; +using Avalonia.Animation.Easings; + +namespace Avalonia.Animation.UnitTests +{ + public class AnimationTransitionsTests + { + [Fact] + public void Check_Transitions_Interpolation_Negative_Bounds_Clamp() + { + var clock = new MockGlobalClock(); + + using (UnitTestApplication.Start(new TestServices(globalClock: clock))) + { + var border = new Border + { + Transitions = + { + new DoubleTransition + { + Duration = TimeSpan.FromSeconds(1), + Property = Border.OpacityProperty, + } + } + }; + + border.Opacity = 0; + + clock.Pulse(TimeSpan.FromSeconds(0)); + clock.Pulse(TimeSpan.FromSeconds(-0.5)); + + Assert.Equal(0, border.Opacity); + } + } + + [Fact] + public void Check_Transitions_Interpolation_Positive_Bounds_Clamp() + { + var clock = new MockGlobalClock(); + + using (UnitTestApplication.Start(new TestServices(globalClock: clock))) + { + var border = new Border + { + Transitions = + { + new DoubleTransition + { + Duration = TimeSpan.FromSeconds(1), + Property = Border.OpacityProperty, + } + } + }; + + border.Opacity = 0; + + clock.Pulse(TimeSpan.FromSeconds(0)); + clock.Pulse(TimeSpan.FromMilliseconds(1001)); + + Assert.Equal(0, border.Opacity); + } + } + } +} From 66371bfa477770423fcb2b0c82bc22bac95899ed Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 May 2019 21:46:28 +0800 Subject: [PATCH 25/36] Rename to TransitionsTest --- .../{AnimationTransitionsTest.cs => TransitionsTest.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/Avalonia.Animation.UnitTests/{AnimationTransitionsTest.cs => TransitionsTest.cs} (97%) diff --git a/tests/Avalonia.Animation.UnitTests/AnimationTransitionsTest.cs b/tests/Avalonia.Animation.UnitTests/TransitionsTest.cs similarity index 97% rename from tests/Avalonia.Animation.UnitTests/AnimationTransitionsTest.cs rename to tests/Avalonia.Animation.UnitTests/TransitionsTest.cs index 9014c40299..8f2ccb9cad 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimationTransitionsTest.cs +++ b/tests/Avalonia.Animation.UnitTests/TransitionsTest.cs @@ -12,7 +12,7 @@ using Avalonia.Animation.Easings; namespace Avalonia.Animation.UnitTests { - public class AnimationTransitionsTests + public class TransitionsTest { [Fact] public void Check_Transitions_Interpolation_Negative_Bounds_Clamp() From 066f608ca27ddd76ab5fdf9a95c309c0763fc966 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 15 May 2019 20:44:12 +0800 Subject: [PATCH 26/36] Rename to plural form. --- .../{TransitionsTest.cs => TransitionsTests.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/Avalonia.Animation.UnitTests/{TransitionsTest.cs => TransitionsTests.cs} (98%) diff --git a/tests/Avalonia.Animation.UnitTests/TransitionsTest.cs b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs similarity index 98% rename from tests/Avalonia.Animation.UnitTests/TransitionsTest.cs rename to tests/Avalonia.Animation.UnitTests/TransitionsTests.cs index 8f2ccb9cad..f1b4b0d071 100644 --- a/tests/Avalonia.Animation.UnitTests/TransitionsTest.cs +++ b/tests/Avalonia.Animation.UnitTests/TransitionsTests.cs @@ -12,7 +12,7 @@ using Avalonia.Animation.Easings; namespace Avalonia.Animation.UnitTests { - public class TransitionsTest + public class TransitionsTests { [Fact] public void Check_Transitions_Interpolation_Negative_Bounds_Clamp() From fbd7ee59635ee5c39cd3114fdd85bcfd30a33ac5 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 15 May 2019 23:12:38 +0800 Subject: [PATCH 27/36] Remove `UseDataGrid` since DataGrid works without that now. --- samples/ControlCatalog.Desktop/Program.cs | 3 +-- samples/ControlCatalog.NetCore/Program.cs | 3 +-- samples/interop/WindowsInteropTest/Program.cs | 2 +- .../AppBuilderExtensions.cs | 20 ------------------- 4 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index b7aa34f5ba..2a8d288614 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -26,8 +26,7 @@ namespace ControlCatalog => AppBuilder.Configure() .LogToDebug() .UsePlatformDetect() - .UseReactiveUI() - .UseDataGrid(); + .UseReactiveUI(); private static void ConfigureAssetAssembly(AppBuilder builder) { diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index c8f3fb9921..93f5611ec4 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -47,8 +47,7 @@ namespace ControlCatalog.NetCore => AppBuilder.Configure() .UsePlatformDetect() .UseSkia() - .UseReactiveUI() - .UseDataGrid(); + .UseReactiveUI(); static void ConsoleSilencer() { diff --git a/samples/interop/WindowsInteropTest/Program.cs b/samples/interop/WindowsInteropTest/Program.cs index 9f005b942d..fac06d74b0 100644 --- a/samples/interop/WindowsInteropTest/Program.cs +++ b/samples/interop/WindowsInteropTest/Program.cs @@ -15,7 +15,7 @@ namespace WindowsInteropTest { System.Windows.Forms.Application.EnableVisualStyles(); System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); - AppBuilder.Configure().UseWin32().UseDirect2D1().UseDataGrid().SetupWithoutStarting(); + AppBuilder.Configure().UseWin32().UseDirect2D1().SetupWithoutStarting(); System.Windows.Forms.Application.Run(new SelectorForm()); } } diff --git a/src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs b/src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs deleted file mode 100644 index bdb9bf182c..0000000000 --- a/src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using Avalonia.Controls; -using Avalonia.Threading; - -namespace Avalonia -{ - public static class AppBuilderExtensions - { - public static TAppBuilder UseDataGrid(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() - { - // Portable.Xaml doesn't correctly load referenced assemblies and so doesn't - // find `DataGrid` when loading XAML. Call this method from AppBuilder as a - // temporary workaround until we fix XAML. - return builder; - } - } -} From ed6251fbe8eba23bf80da73f3758d3574fdc06a6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 May 2019 09:19:40 +0200 Subject: [PATCH 28/36] Use styled properties in Expander. `ContentTransition` was defined as a direct property, but was also getting set in a style, causing #2508. Changed it to be a styled property, along with `ExpandDirection`. Fixes #2508 --- src/Avalonia.Controls/Expander.cs | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index f0ba2b5578..1fa9798784 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -13,18 +13,11 @@ namespace Avalonia.Controls public class Expander : HeaderedContentControl { - public static readonly DirectProperty ContentTransitionProperty = - AvaloniaProperty.RegisterDirect( - nameof(ContentTransition), - o => o.ContentTransition, - (o, v) => o.ContentTransition = v); + public static readonly StyledProperty ContentTransitionProperty = + AvaloniaProperty.Register(nameof(ContentTransition)); - public static readonly DirectProperty ExpandDirectionProperty = - AvaloniaProperty.RegisterDirect( - nameof(ExpandDirection), - o => o.ExpandDirection, - (o, v) => o.ExpandDirection = v, - ExpandDirection.Down); + public static readonly StyledProperty ExpandDirectionProperty = + AvaloniaProperty.Register(nameof(ExpandDirection), ExpandDirection.Down); public static readonly DirectProperty IsExpandedProperty = AvaloniaProperty.RegisterDirect( @@ -33,6 +26,8 @@ namespace Avalonia.Controls (o, v) => o.IsExpanded = v, defaultBindingMode: Data.BindingMode.TwoWay); + private bool _isExpanded; + static Expander() { PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Down, ":down"); @@ -47,14 +42,14 @@ namespace Avalonia.Controls public IPageTransition ContentTransition { - get { return _contentTransition; } - set { SetAndRaise(ContentTransitionProperty, ref _contentTransition, value); } + get => GetValue(ContentTransitionProperty); + set => SetValue(ContentTransitionProperty, value); } public ExpandDirection ExpandDirection { - get { return _expandDirection; } - set { SetAndRaise(ExpandDirectionProperty, ref _expandDirection, value); } + get => GetValue(ExpandDirectionProperty); + set => SetValue(ExpandDirectionProperty, value); } public bool IsExpanded @@ -79,9 +74,5 @@ namespace Avalonia.Controls } } } - - private IPageTransition _contentTransition; - private ExpandDirection _expandDirection; - private bool _isExpanded; } } From 9d99cf699a1b53b814879d4dff1cf0b0805fd4b4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 May 2019 09:28:14 +0200 Subject: [PATCH 29/36] Remove test that is no longer true. `UserControl`s should no longer be added to parent namescope. --- .../Xaml/BasicTests.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 7bd659b65f..359d2521e0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -208,22 +208,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal("Foo", button.Content); } - [Fact] - public void Named_UserControl_Is_Added_To_Parent_NameScope() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var xaml = @" - - -"; - - var control = AvaloniaXamlLoader.Parse(xaml); - - Assert.NotNull(control.FindControl("foo")); - } - } - [Fact] public void Direct_Content_In_ItemsControl_Is_Operational() { From 4ad4ba4a9ed45415601d5355aa76158cd7b9349d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 17 May 2019 17:00:20 +0200 Subject: [PATCH 30/36] Set InputModifiers on PointerEnter/Leave. Note that these will not be set when a pointer enter/leave occurs because of a control moving or appearing/disappearing. Fixes #2495 --- src/Avalonia.Input/MouseDevice.cs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index d3e62ece6f..c195209305 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -108,11 +108,11 @@ namespace Avalonia.Input { if (Captured == null) { - SetPointerOver(this, root, clientPoint); + SetPointerOver(this, root, clientPoint, InputModifiers.None); } else { - SetPointerOver(this, root, Captured); + SetPointerOver(this, root, Captured, InputModifiers.None); } } } @@ -128,7 +128,7 @@ namespace Avalonia.Input switch (e.Type) { case RawMouseEventType.LeaveWindow: - LeaveWindow(mouse, e.Root); + LeaveWindow(mouse, e.Root, e.InputModifiers); break; case RawMouseEventType.LeftButtonDown: case RawMouseEventType.RightButtonDown: @@ -157,12 +157,12 @@ namespace Avalonia.Input } } - private void LeaveWindow(IMouseDevice device, IInputRoot root) + private void LeaveWindow(IMouseDevice device, IInputRoot root, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); - ClearPointerOver(this, root); + ClearPointerOver(this, root, inputModifiers); } private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers) @@ -218,11 +218,11 @@ namespace Avalonia.Input if (Captured == null) { - source = SetPointerOver(this, root, p); + source = SetPointerOver(this, root, p, inputModifiers); } else { - SetPointerOver(this, root, Captured); + SetPointerOver(this, root, Captured, inputModifiers); source = Captured; } @@ -306,7 +306,7 @@ namespace Avalonia.Input return Captured ?? root.InputHitTest(p); } - private void ClearPointerOver(IPointerDevice device, IInputRoot root) + private void ClearPointerOver(IPointerDevice device, IInputRoot root, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); @@ -316,6 +316,7 @@ namespace Avalonia.Input { RoutedEvent = InputElement.PointerLeaveEvent, Device = device, + InputModifiers = inputModifiers }; if (element!=null && !element.IsAttachedToVisualTree) @@ -353,7 +354,7 @@ namespace Avalonia.Input } } - private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p) + private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); @@ -364,18 +365,18 @@ namespace Avalonia.Input { if (element != null) { - SetPointerOver(device, root, element); + SetPointerOver(device, root, element, inputModifiers); } else { - ClearPointerOver(device, root); + ClearPointerOver(device, root, inputModifiers); } } return element; } - private void SetPointerOver(IPointerDevice device, IInputRoot root, IInputElement element) + private void SetPointerOver(IPointerDevice device, IInputRoot root, IInputElement element, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); @@ -383,7 +384,7 @@ namespace Avalonia.Input IInputElement branch = null; - var e = new PointerEventArgs { Device = device, }; + var e = new PointerEventArgs { Device = device, InputModifiers = inputModifiers }; var el = element; while (el != null) From 0f25e0548fa8d4d2d1fcec576e6f3ea8129ef53a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 17 May 2019 17:31:58 +0200 Subject: [PATCH 31/36] Use object for resource keys. `IResourceDictionary` was defined as an `IDictionary` but in various places we only accepted a `string` as the resource key. Fix this inconsistency and always use `object` as a resource key. Fixes #2456 --- src/Avalonia.Controls/Application.cs | 2 +- src/Avalonia.Styling/Controls/IResourceProvider.cs | 2 +- src/Avalonia.Styling/Controls/ResourceDictionary.cs | 2 +- .../Controls/ResourceProviderExtensions.cs | 10 +++++----- src/Avalonia.Styling/StyledElement.cs | 2 +- src/Avalonia.Styling/Styling/Style.cs | 2 +- src/Avalonia.Styling/Styling/Styles.cs | 2 +- .../MarkupExtensions/DynamicResourceExtension.cs | 2 +- .../MarkupExtensions/ResourceInclude.cs | 2 +- .../Avalonia.Markup.Xaml/Styling/StyleInclude.cs | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index bbea3693cc..0e696e0199 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -362,7 +362,7 @@ namespace Avalonia } /// - bool IResourceProvider.TryGetResource(string key, out object value) + bool IResourceProvider.TryGetResource(object key, out object value) { value = null; return (_resources?.TryGetResource(key, out value) ?? false) || diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index eec783623c..cbaacee012 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -28,6 +28,6 @@ namespace Avalonia.Controls /// /// True if the resource if found, otherwise false. /// - bool TryGetResource(string key, out object value); + bool TryGetResource(object key, out object value); } } diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 74a861b36b..901e27b7b7 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -69,7 +69,7 @@ namespace Avalonia.Controls } /// - public bool TryGetResource(string key, out object value) + public bool TryGetResource(object key, out object value) { if (TryGetValue(key, out value)) { diff --git a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs index 52309b87a2..01112eaf2c 100644 --- a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs +++ b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs @@ -11,7 +11,7 @@ namespace Avalonia.Controls /// The control. /// The resource key. /// The resource, or if not found. - public static object FindResource(this IResourceNode control, string key) + public static object FindResource(this IResourceNode control, object key) { if (control.TryFindResource(key, out var value)) { @@ -28,7 +28,7 @@ namespace Avalonia.Controls /// The resource key. /// On return, contains the resource if found, otherwise null. /// True if the resource was found; otherwise false. - public static bool TryFindResource(this IResourceNode control, string key, out object value) + public static bool TryFindResource(this IResourceNode control, object key, out object value) { Contract.Requires(control != null); Contract.Requires(key != null); @@ -52,7 +52,7 @@ namespace Avalonia.Controls return false; } - public static IObservable GetResourceObservable(this IResourceNode target, string key) + public static IObservable GetResourceObservable(this IResourceNode target, object key) { return new ResourceObservable(target, key); } @@ -60,9 +60,9 @@ namespace Avalonia.Controls private class ResourceObservable : LightweightObservableBase { private readonly IResourceNode _target; - private readonly string _key; + private readonly object _key; - public ResourceObservable(IResourceNode target, string key) + public ResourceObservable(IResourceNode target, object key) { _target = target; _key = key; diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index d314a8d44e..6361763614 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -415,7 +415,7 @@ namespace Avalonia } /// - bool IResourceProvider.TryGetResource(string key, out object value) + bool IResourceProvider.TryGetResource(object key, out object value) { value = null; return (_resources?.TryGetResource(key, out value) ?? false) || diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index d799df7ac9..3ce82b4160 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -171,7 +171,7 @@ namespace Avalonia.Styling } /// - public bool TryGetResource(string key, out object result) + public bool TryGetResource(object key, out object result) { result = null; return _resources?.TryGetResource(key, out result) ?? false; diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 288cf35d08..789bb6ffd3 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -178,7 +178,7 @@ namespace Avalonia.Styling } /// - public bool TryGetResource(string key, out object value) + public bool TryGetResource(object key, out object value) { if (_resources != null && _resources.TryGetValue(key, out value)) { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 5f0e84c63a..48e55dc251 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -26,7 +26,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions ResourceKey = resourceKey; } - public string ResourceKey { get; set; } + public object ResourceKey { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) => ProvideTypedValue(serviceProvider); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs index 827f58a909..323a341f6a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs @@ -47,7 +47,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions bool IResourceProvider.HasResources => Loaded.HasResources; /// - bool IResourceProvider.TryGetResource(string key, out object value) + bool IResourceProvider.TryGetResource(object key, out object value) { return Loaded.TryGetResource(key, out value); } diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 01ec9753bd..7acee50d80 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -86,7 +86,7 @@ namespace Avalonia.Markup.Xaml.Styling } /// - public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value); + public bool TryGetResource(object key, out object value) => Loaded.TryGetResource(key, out value); /// void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) From 38bd934c4ab4c7a7e4a225073fd3a01f54756bf6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 17 May 2019 17:40:28 +0200 Subject: [PATCH 32/36] Added Window.OnClosing. --- src/Avalonia.Controls/Window.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 01c9a3a110..01614ba87b 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -330,8 +330,7 @@ namespace Avalonia.Controls protected virtual bool HandleClosing() { var args = new CancelEventArgs(); - Closing?.Invoke(this, args); - + OnClosing(args); return args.Cancel; } @@ -576,6 +575,17 @@ namespace Avalonia.Controls base.HandleResized(clientSize); } + + /// + /// Raises the event. + /// + /// The event args. + /// + /// A type that derives from may override . The + /// overridden method must call on the base class if the + /// event needs to be raised. + /// + protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); } } From 57af0d55625f10639eafbaf958c6f28f324fd052 Mon Sep 17 00:00:00 2001 From: lindexi Date: Sat, 18 May 2019 19:49:35 +0800 Subject: [PATCH 33/36] Add ignore file --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2b2c9c3d0d..9fe4507b1b 100644 --- a/.gitignore +++ b/.gitignore @@ -196,3 +196,5 @@ ModuleCache.noindex/ Build/Intermediates.noindex/ info.plist build-intermediate +/tests/Avalonia.RenderTests/obj-Direct2D1 +/tests/Avalonia.RenderTests/obj-Skia From 15aa377769628f233742a39c6765735620bc7cad Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 21 May 2019 08:52:08 +0800 Subject: [PATCH 34/36] ignore obj folder --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9fe4507b1b..971c945246 100644 --- a/.gitignore +++ b/.gitignore @@ -196,5 +196,5 @@ ModuleCache.noindex/ Build/Intermediates.noindex/ info.plist build-intermediate -/tests/Avalonia.RenderTests/obj-Direct2D1 -/tests/Avalonia.RenderTests/obj-Skia +obj-Direct2D1/ +obj-Skia/ From 67ea597d82e14a6e9feeeb83d6d34fbda15b215a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 22 May 2019 12:14:37 +0300 Subject: [PATCH 35/36] Properly pass root object instance to templates, fixes #2147 #2527 --- .../XamlIl/Runtime/XamlIlRuntimeHelpers.cs | 18 +++++-- .../Avalonia.Markup.Xaml/XamlIl/xamlil.github | 2 +- .../Xaml/XamlIlTests.cs | 53 +++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index f91e221ac0..70b7fe6aec 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using Avalonia.Controls; using Avalonia.Data; +using Portable.Xaml; using Portable.Xaml.Markup; // ReSharper disable UnusedMember.Global // ReSharper disable UnusedParameter.Global @@ -17,19 +18,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime { var resourceNodes = provider.GetService().Parents .OfType().ToList(); - - return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes)); + var rootObject = provider.GetService().RootObject; + return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject)); } - class DeferredParentServiceProvider : IAvaloniaXamlIlParentStackProvider, IServiceProvider + class DeferredParentServiceProvider : + IAvaloniaXamlIlParentStackProvider, + IServiceProvider, + IRootObjectProvider { private readonly IServiceProvider _parentProvider; private readonly List _parentResourceNodes; - public DeferredParentServiceProvider(IServiceProvider parentProvider, List parentResourceNodes) + public DeferredParentServiceProvider(IServiceProvider parentProvider, List parentResourceNodes, + object rootObject) { _parentProvider = parentProvider; _parentResourceNodes = parentResourceNodes; + RootObject = rootObject; } public IEnumerable Parents => GetParents(); @@ -46,8 +52,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime { if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider)) return this; + if (serviceType == typeof(IRootObjectProvider)) + return this; return _parentProvider?.GetService(serviceType); } + + public object RootObject { get; } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index 3b3c1f93a5..50920ece52 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit 3b3c1f93a566080d417b9782f9cc4ea67cd62344 +Subproject commit 50920ece52647b19760f65b417940da125101365 diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index a584027768..175479e3ff 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -5,8 +5,12 @@ using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Data.Converters; +using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Media; +using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; using JetBrains.Annotations; @@ -117,6 +121,55 @@ namespace Avalonia.Markup.Xaml.UnitTests Assert.Equal(Brushes.Red.Color, ((ISolidColorBrush)canvas.Background).Color); } } + + [Fact] + public void Event_Handlers_Should_Work_For_Templates() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var w =new XamlIlBugTestsEventHandlerCodeBehind(); + w.ApplyTemplate(); + w.Show(); + + Dispatcher.UIThread.RunJobs(); + var itemsPresenter = ((ItemsControl)w.Content).GetVisualChildren().FirstOrDefault(); + var item = itemsPresenter + .GetVisualChildren().First() + .GetVisualChildren().First() + .GetVisualChildren().First(); + ((Control)item).RaiseEvent(new PointerPressedEventArgs {ClickCount = 20}); + Assert.Equal(20, w.Args.ClickCount); + } + } + } + + public class XamlIlBugTestsEventHandlerCodeBehind : Window + { + public PointerPressedEventArgs Args; + public void HandlePointerPressed(object sender, PointerPressedEventArgs args) + { + Args = args; + } + + public XamlIlBugTestsEventHandlerCodeBehind() + { + new AvaloniaXamlLoader().Load(@" + + + + +