diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.cs b/samples/XamlTestApplicationPcl/Views/MainWindow.cs
index d18985a898..5003af3ecd 100644
--- a/samples/XamlTestApplicationPcl/Views/MainWindow.cs
+++ b/samples/XamlTestApplicationPcl/Views/MainWindow.cs
@@ -1,6 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using Perspex;
using Perspex.Controls;
using Perspex.Diagnostics;
using Perspex.Markup.Xaml;
@@ -10,6 +11,8 @@ namespace XamlTestApplication.Views
{
public class MainWindow : Window
{
+ private MenuItem _exitMenu;
+
public MainWindow()
{
InitializeComponent();
@@ -20,6 +23,8 @@ namespace XamlTestApplication.Views
private void InitializeComponent()
{
PerspexXamlLoader.Load(this);
+ _exitMenu = this.FindControl
diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs
index 1a245a8d9f..06f0b6ddeb 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;
@@ -56,6 +56,7 @@ namespace Perspex
}
PerspexLocator.CurrentMutable.BindToSelf(this);
+ OnExit += OnExiting;
}
///
@@ -138,10 +139,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.
///
@@ -160,7 +186,8 @@ namespace Perspex
.Bind().ToTransient()
.Bind().ToConstant(_styler)
.Bind().ToSingleton()
- .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 3d4b29e489..ee90633945 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..24d3770f98 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,11 @@ namespace Perspex.Controls
.Select(
x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty())
.Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor));
+
+ if (_applicationLifecycle != null)
+ {
+ _applicationLifecycle.OnExit += OnApplicationExiting;
+ }
}
///
@@ -347,6 +354,19 @@ namespace Perspex.Controls
private void HandleClosed()
{
Closed?.Invoke(this, EventArgs.Empty);
+ _applicationLifecycle.OnExit -= OnApplicationExiting;
+ }
+
+ private void OnApplicationExiting(object sender, EventArgs args)
+ {
+ HandleApplicationExiting();
+ }
+
+ ///
+ /// 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.
///
diff --git a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs
index f03737c728..d121ad06ff 100644
--- a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs
+++ b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs
@@ -272,6 +272,19 @@ 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 FuncControlTemplate CreateTemplate()
{
return new FuncControlTemplate(x =>
@@ -284,10 +297,18 @@ namespace Perspex.Controls.UnitTests
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 3fe4af0b59..04fd5daf68 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
{
@@ -47,7 +48,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);
}
}
}