From bbe460c18fc8ed09001ef502a36569c510871a5b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 1 Mar 2016 18:09:01 -0600 Subject: [PATCH 1/2] Added interface and implementation for application exiting. Will automatically close all windows when called. --- src/Perspex.Application/Application.cs | 31 +++++++++++++++++-- src/Perspex.Controls/IApplicationLifecycle.cs | 24 ++++++++++++++ src/Perspex.Controls/Perspex.Controls.csproj | 1 + src/Perspex.Controls/TopLevel.cs | 17 ++++++++++ src/Perspex.Controls/Window.cs | 6 ++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/Perspex.Controls/IApplicationLifecycle.cs diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index e904207b18..817253a3c9 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -32,7 +32,7 @@ namespace Perspex /// method. /// - Tracks the lifetime of the application. /// - public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot + public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IApplicationLifecycle { static Action _platformInitializationCallback; @@ -60,6 +60,7 @@ namespace Perspex } PerspexLocator.CurrentMutable.BindToSelf(this); + OnExit += OnExiting; } /// @@ -146,10 +147,35 @@ namespace Perspex public void Run(ICloseable closable) { var source = new CancellationTokenSource(); + closable.Closed += OnExiting; closable.Closed += (s, e) => source.Cancel(); Dispatcher.UIThread.MainLoop(source.Token); } + /// + /// Exits the application + /// + public void Exit() + { + OnExit?.Invoke(this, EventArgs.Empty); + } + + /// + /// 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 Perspex. /// @@ -168,7 +194,8 @@ namespace Perspex .Bind().ToTransient() .Bind().ToConstant(_styler) .Bind().ToTransient() - .Bind().ToTransient(); + .Bind().ToTransient() + .Bind().ToConstant(this); } /// diff --git a/src/Perspex.Controls/IApplicationLifecycle.cs b/src/Perspex.Controls/IApplicationLifecycle.cs new file mode 100644 index 0000000000..38487bb9fc --- /dev/null +++ b/src/Perspex.Controls/IApplicationLifecycle.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Controls +{ + /// + /// Sends events about the application lifecycle. + /// + public interface IApplicationLifecycle + { + /// + /// Sent when the application is exiting. + /// + event EventHandler OnExit; + + /// + /// Exits the application. + /// + void Exit(); + } +} diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 58aee7f2a5..0d4a0b6c45 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -50,6 +50,7 @@ + diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index b82d3da078..cb2651c67d 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -48,6 +48,7 @@ namespace Perspex.Controls private readonly IInputManager _inputManager; private readonly IAccessKeyHandler _accessKeyHandler; private readonly IKeyboardNavigationHandler _keyboardNavigationHandler; + private readonly IApplicationLifecycle _applicationLifecycle; private Size _clientSize; private bool _isActive; @@ -92,6 +93,7 @@ namespace Perspex.Controls _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); _renderQueueManager = TryGetService(dependencyResolver); + _applicationLifecycle = TryGetService(dependencyResolver); (TryGetService(dependencyResolver) ?? new DefaultTopLevelRenderer()).Attach(this); PlatformImpl.SetInputRoot(this); @@ -112,6 +114,7 @@ namespace Perspex.Controls .Select( x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty()) .Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor)); + _applicationLifecycle.OnExit += HandleApplicationExiting; } /// @@ -347,6 +350,20 @@ namespace Perspex.Controls private void HandleClosed() { Closed?.Invoke(this, EventArgs.Empty); + _applicationLifecycle.OnExit -= OnApplicationExiting; + } + + private void OnApplicationExiting(object sender, EventArgs args) + { + + } + + /// + /// Handles the application exiting, either from the last window closing, or a call to . + /// + protected virtual void HandleApplicationExiting() + { + } /// diff --git a/src/Perspex.Controls/Window.cs b/src/Perspex.Controls/Window.cs index 22307ab97e..ae12a4d6ed 100644 --- a/src/Perspex.Controls/Window.cs +++ b/src/Perspex.Controls/Window.cs @@ -156,6 +156,12 @@ namespace Perspex.Controls PlatformImpl.Dispose(); } + protected override void HandleApplicationExiting() + { + base.HandleApplicationExiting(); + Close(); + } + /// /// Closes a dialog window with the specified result. /// From eea178500aa94e7bc6db4ce89e959f7419d7f6ce Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 2 Mar 2016 13:57:56 -0600 Subject: [PATCH 2/2] Added test and fixed implementation. --- src/Perspex.Controls/TopLevel.cs | 4 ++-- .../TopLevelTests.cs | 21 +++++++++++++++++++ .../Perspex.UnitTests/UnitTestApplication.cs | 4 +++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index cb2651c67d..8d47c90458 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -114,7 +114,7 @@ namespace Perspex.Controls .Select( x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty()) .Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor)); - _applicationLifecycle.OnExit += HandleApplicationExiting; + _applicationLifecycle.OnExit += OnApplicationExiting; } /// @@ -355,7 +355,7 @@ namespace Perspex.Controls private void OnApplicationExiting(object sender, EventArgs args) { - + HandleApplicationExiting(); } /// diff --git a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs index f03737c728..7fd080e41d 100644 --- a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs @@ -282,12 +282,33 @@ namespace Perspex.Controls.UnitTests }); } + [Fact] + public void Exiting_Application_Notifies_Top_Level() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var impl = new Mock(); + impl.SetupAllProperties(); + var target = new TestTopLevel(impl.Object); + UnitTestApplication.Current.Exit(); + Assert.True(target.IsClosed); + } + } + private class TestTopLevel : TopLevel { + public bool IsClosed { get; private set; } + public TestTopLevel(ITopLevelImpl impl) : base(impl) { } + + protected override void HandleApplicationExiting() + { + base.HandleApplicationExiting(); + IsClosed = true; + } } } } diff --git a/tests/Perspex.UnitTests/UnitTestApplication.cs b/tests/Perspex.UnitTests/UnitTestApplication.cs index 4811ce4ee2..56e9ebad82 100644 --- a/tests/Perspex.UnitTests/UnitTestApplication.cs +++ b/tests/Perspex.UnitTests/UnitTestApplication.cs @@ -6,6 +6,7 @@ using Perspex.Input; using Perspex.Layout; using Perspex.Platform; using Perspex.Styling; +using Perspex.Controls; namespace Perspex.UnitTests { @@ -41,7 +42,8 @@ namespace Perspex.UnitTests .Bind().ToConstant(Services.ThreadingInterface) .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.Styler) - .Bind().ToConstant(Services.WindowingPlatform); + .Bind().ToConstant(Services.WindowingPlatform) + .Bind().ToConstant(this); } } }