SEBWIN-219: Unified WPF application shutdown for runtime & client (and fixed window visibility issue by doing so).

This commit is contained in:
dbuechel 2018-02-21 14:01:21 +01:00
parent 2424f2f1ed
commit 2ab48cda09
18 changed files with 229 additions and 36 deletions

View file

@ -14,6 +14,7 @@ using SafeExamBrowser.Contracts.Behaviour.Operations;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
namespace SafeExamBrowser.Client.UnitTests
@ -27,6 +28,7 @@ namespace SafeExamBrowser.Client.UnitTests
private Mock<IOperationSequence> operationSequenceMock;
private Mock<IRuntimeProxy> runtimeProxyMock;
private Mock<ITaskbar> taskbarMock;
private Mock<IUserInterfaceFactory> uiFactoryMock;
private Mock<IWindowMonitor> windowMonitorMock;
private ClientController sut;
@ -40,6 +42,7 @@ namespace SafeExamBrowser.Client.UnitTests
operationSequenceMock = new Mock<IOperationSequence>();
runtimeProxyMock = new Mock<IRuntimeProxy>();
taskbarMock = new Mock<ITaskbar>();
uiFactoryMock = new Mock<IUserInterfaceFactory>();
windowMonitorMock= new Mock<IWindowMonitor>();
operationSequenceMock.Setup(o => o.TryPerform()).Returns(true);
@ -50,7 +53,9 @@ namespace SafeExamBrowser.Client.UnitTests
operationSequenceMock.Object,
processMonitorMock.Object,
runtimeProxyMock.Object,
new Action(() => { }),
taskbarMock.Object,
uiFactoryMock.Object,
windowMonitorMock.Object);
sut.TryStart();

View file

@ -55,9 +55,9 @@ namespace SafeExamBrowser.Client
{
base.OnStartup(e);
ShutdownMode = ShutdownMode.OnMainWindowClose;
ShutdownMode = ShutdownMode.OnExplicitShutdown;
instances.BuildObjectGraph();
instances.BuildObjectGraph(Shutdown);
instances.LogStartupInformation();
var success = instances.ClientController.TryStart();
@ -73,12 +73,17 @@ namespace SafeExamBrowser.Client
}
}
protected override void OnExit(ExitEventArgs e)
public new void Shutdown()
{
instances.ClientController.Terminate();
instances.LogShutdownInformation();
void shutdown()
{
instances.ClientController.Terminate();
instances.LogShutdownInformation();
base.OnExit(e);
base.Shutdown();
}
Dispatcher.BeginInvoke(new Action(shutdown));
}
}
}

View file

@ -10,8 +10,11 @@ using System;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Behaviour.Operations;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
namespace SafeExamBrowser.Client.Behaviour
@ -23,10 +26,29 @@ namespace SafeExamBrowser.Client.Behaviour
private IOperationSequence operations;
private IProcessMonitor processMonitor;
private IRuntimeProxy runtime;
private Action shutdown;
private ISplashScreen splashScreen;
private ITaskbar taskbar;
private IUserInterfaceFactory uiFactory;
private IWindowMonitor windowMonitor;
private RuntimeInfo runtimeInfo;
public IClientHost ClientHost { private get; set; }
public Guid SessionId { private get; set; }
public Settings Settings { private get; set; }
public RuntimeInfo RuntimeInfo
{
set
{
runtimeInfo = value;
if (splashScreen != null)
{
splashScreen.RuntimeInfo = value;
}
}
}
public ClientController(
IDisplayMonitor displayMonitor,
@ -34,7 +56,9 @@ namespace SafeExamBrowser.Client.Behaviour
IOperationSequence operations,
IProcessMonitor processMonitor,
IRuntimeProxy runtime,
Action shutdown,
ITaskbar taskbar,
IUserInterfaceFactory uiFactory,
IWindowMonitor windowMonitor)
{
this.displayMonitor = displayMonitor;
@ -42,12 +66,17 @@ namespace SafeExamBrowser.Client.Behaviour
this.operations = operations;
this.processMonitor = processMonitor;
this.runtime = runtime;
this.shutdown = shutdown;
this.taskbar = taskbar;
this.uiFactory = uiFactory;
this.windowMonitor = windowMonitor;
}
public bool TryStart()
{
splashScreen = uiFactory.CreateSplashScreen();
operations.ProgressIndicator = splashScreen;
var success = operations.TryPerform();
// TODO
@ -56,6 +85,7 @@ namespace SafeExamBrowser.Client.Behaviour
{
RegisterEvents();
runtime.InformClientReady();
splashScreen.Hide();
}
return success;
@ -63,11 +93,15 @@ namespace SafeExamBrowser.Client.Behaviour
public void Terminate()
{
DeregisterEvents();
splashScreen.Show();
splashScreen.BringToForeground();
// TODO
DeregisterEvents();
operations.TryRevert();
splashScreen?.Close();
}
private void RegisterEvents()
@ -110,8 +144,8 @@ namespace SafeExamBrowser.Client.Behaviour
private void ClientHost_Shutdown()
{
// TODO: Better use callback to Application.Shutdown() as in runtime?
taskbar.Close();
shutdown.Invoke();
}
private void Taskbar_QuitButtonClicked()

View file

@ -16,6 +16,7 @@ using SafeExamBrowser.Client.Communication;
using SafeExamBrowser.Configuration;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Behaviour.Operations;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
@ -43,6 +44,7 @@ namespace SafeExamBrowser.Client
private Guid startupToken;
private ClientConfiguration configuration;
private IClientHost clientHost;
private ILogger logger;
private INativeMethods nativeMethods;
private ISystemInfo systemInfo;
@ -52,7 +54,7 @@ namespace SafeExamBrowser.Client
internal IClientController ClientController { get; private set; }
internal Taskbar Taskbar { get; private set; }
internal void BuildObjectGraph()
internal void BuildObjectGraph(Action shutdown)
{
ValidateCommandLineArguments();
@ -79,6 +81,7 @@ namespace SafeExamBrowser.Client
operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, startupToken));
operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy));
operations.Enqueue(new DelayedInitializationOperation(BuildCommunicationHostOperation));
operations.Enqueue(new DelegateOperation(UpdateClientControllerDependencies));
// TODO
//operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation));
//operations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
@ -91,7 +94,7 @@ namespace SafeExamBrowser.Client
var sequence = new OperationSequence(logger, operations);
ClientController = new ClientController(displayMonitor, logger, sequence, processMonitor, runtimeProxy, Taskbar, windowMonitor);
ClientController = new ClientController(displayMonitor, logger, sequence, processMonitor, runtimeProxy, shutdown, Taskbar, uiFactory, windowMonitor);
}
internal void LogStartupInformation()
@ -153,8 +156,8 @@ namespace SafeExamBrowser.Client
var host = new ClientHost(configuration.RuntimeInfo.ClientAddress, new ModuleLogger(logger, typeof(ClientHost)), processId);
var operation = new CommunicationOperation(host, logger);
host.StartupToken = startupToken;
ClientController.ClientHost = host;
clientHost = host;
clientHost.StartupToken = startupToken;
return operation;
}
@ -184,5 +187,13 @@ namespace SafeExamBrowser.Client
return operation;
}
private void UpdateClientControllerDependencies()
{
ClientController.ClientHost = clientHost;
ClientController.RuntimeInfo = configuration.RuntimeInfo;
ClientController.SessionId = configuration.SessionId;
ClientController.Settings = configuration.Settings;
}
}
}

View file

@ -6,7 +6,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
namespace SafeExamBrowser.Contracts.Behaviour
{
@ -18,6 +21,21 @@ namespace SafeExamBrowser.Contracts.Behaviour
/// </summary>
IClientHost ClientHost { set; }
/// <summary>
/// The runtime information to be used during application execution.
/// </summary>
RuntimeInfo RuntimeInfo { set; }
/// <summary>
/// The session identifier of the currently running session.
/// </summary>
Guid SessionId { set; }
/// <summary>
/// The settings to be used during application execution.
/// </summary>
Settings Settings { set; }
/// <summary>
/// Reverts any changes, releases all used resources and terminates the client.
/// </summary>

View file

@ -6,9 +6,15 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Contracts.UserInterface
{
public interface ISplashScreen : IProgressIndicator, IWindow
{
/// <summary>
/// The runtime information used to display version and copyright information.
/// </summary>
RuntimeInfo RuntimeInfo { set; }
}
}

View file

@ -59,7 +59,7 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// <summary>
/// Creates a new splash screen which runs on its own thread.
/// </summary>
ISplashScreen CreateSplashScreen(RuntimeInfo runtimeInfo);
ISplashScreen CreateSplashScreen(RuntimeInfo runtimeInfo = null);
/// <summary>
/// Creates a system control which allows to change the wireless network connection of the computer.

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SafeExamBrowser.Core.UnitTests.Behaviour.Operations
{
[TestClass]
public class DelegateOperationTests
{
[TestMethod]
public void TODO()
{
Assert.Fail();
}
}
}

View file

@ -81,6 +81,7 @@
<Compile Include="Behaviour\Operations\CommunicationOperationTests.cs" />
<Compile Include="Behaviour\Operations\DelayedInitializationOperationTests.cs" />
<Compile Include="Behaviour\Operations\I18nOperationTests.cs" />
<Compile Include="Behaviour\Operations\DelegateOperationTests.cs" />
<Compile Include="Behaviour\Operations\OperationSequenceTests.cs" />
<Compile Include="I18n\TextTests.cs" />
<Compile Include="I18n\XmlTextResourceTests.cs" />

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Behaviour.Operations;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour.Operations
{
public class DelegateOperation : IOperation
{
private Action perform;
private Action repeat;
private Action revert;
public bool Abort { get; private set; }
public IProgressIndicator ProgressIndicator { private get; set; }
public DelegateOperation(Action perform, Action repeat = null, Action revert = null)
{
this.perform = perform;
this.repeat = repeat;
this.revert = revert;
}
public void Perform()
{
perform?.Invoke();
}
public void Repeat()
{
repeat?.Invoke();
}
public void Revert()
{
revert?.Invoke();
}
}
}

View file

@ -58,6 +58,7 @@
<Compile Include="Behaviour\Operations\CommunicationOperation.cs" />
<Compile Include="Behaviour\Operations\DelayedInitializationOperation.cs" />
<Compile Include="Behaviour\Operations\I18nOperation.cs" />
<Compile Include="Behaviour\Operations\DelegateOperation.cs" />
<Compile Include="Behaviour\Operations\OperationSequence.cs" />
<Compile Include="Communication\BaseProxy.cs" />
<Compile Include="Communication\BaseHost.cs" />

View file

@ -57,7 +57,7 @@ namespace SafeExamBrowser.Runtime
ShutdownMode = ShutdownMode.OnExplicitShutdown;
instances.BuildObjectGraph();
instances.BuildObjectGraph(Shutdown);
instances.LogStartupInformation();
var success = instances.RuntimeController.TryStart();
@ -68,12 +68,17 @@ namespace SafeExamBrowser.Runtime
}
}
protected override void OnExit(ExitEventArgs e)
public new void Shutdown()
{
instances.RuntimeController.Terminate();
instances.LogShutdownInformation();
void shutdown()
{
instances.RuntimeController.Terminate();
instances.LogShutdownInformation();
base.OnExit(e);
base.Shutdown();
}
Dispatcher.BeginInvoke(new Action(shutdown));
}
}
}

View file

@ -103,6 +103,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
logger.Unsubscribe(runtimeWindow);
runtimeWindow?.Close();
splashScreen?.Show();
splashScreen?.BringToForeground();
logger.Log(string.Empty);
logger.Info("--- Initiating shutdown procedure ---");

View file

@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
using System.Windows;
using SafeExamBrowser.Configuration;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Behaviour.Operations;
@ -34,12 +33,11 @@ namespace SafeExamBrowser.Runtime
internal IRuntimeController RuntimeController { get; private set; }
internal void BuildObjectGraph()
internal void BuildObjectGraph(Action shutdown)
{
var args = Environment.GetCommandLineArgs();
var configuration = new ConfigurationRepository();
var nativeMethods = new NativeMethods();
void shutdown() => Application.Current.Dispatcher.BeginInvoke(new Action(Application.Current.Shutdown));
logger = new Logger();
runtimeInfo = configuration.RuntimeInfo;

View file

@ -23,13 +23,25 @@ namespace SafeExamBrowser.UserInterface.Classic
private IText text;
private WindowClosingEventHandler closing;
public RuntimeInfo RuntimeInfo
{
set
{
Dispatcher.Invoke(() =>
{
runtimeInfo = value;
UpdateRuntimeInfo();
});
}
}
event WindowClosingEventHandler IWindow.Closing
{
add { closing += value; }
remove { closing -= value; }
}
public SplashScreen(RuntimeInfo runtimeInfo, IText text)
public SplashScreen(IText text, RuntimeInfo runtimeInfo = null)
{
this.runtimeInfo = runtimeInfo;
this.text = text;
@ -100,10 +112,7 @@ namespace SafeExamBrowser.UserInterface.Classic
private void InitializeSplashScreen()
{
InfoTextBlock.Inlines.Add(new Run($"Version {runtimeInfo.ProgramVersion}") { FontStyle = FontStyles.Italic });
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new Run(runtimeInfo.ProgramCopyright) { FontSize = 10 });
UpdateRuntimeInfo();
StatusTextBlock.DataContext = model;
ProgressBar.DataContext = model;
@ -113,5 +122,16 @@ namespace SafeExamBrowser.UserInterface.Classic
Closing += (o, args) => args.Cancel = !allowClose;
}
private void UpdateRuntimeInfo()
{
if (runtimeInfo != null)
{
InfoTextBlock.Inlines.Add(new Run($"Version {runtimeInfo.ProgramVersion}") { FontStyle = FontStyles.Italic });
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new Run(runtimeInfo.ProgramCopyright) { FontSize = 10 });
}
}
}
}

View file

@ -108,13 +108,13 @@ namespace SafeExamBrowser.UserInterface.Classic
return runtimeWindow;
}
public ISplashScreen CreateSplashScreen(RuntimeInfo runtimeInfo)
public ISplashScreen CreateSplashScreen(RuntimeInfo runtimeInfo = null)
{
SplashScreen splashScreen = null;
var splashReadyEvent = new AutoResetEvent(false);
var splashScreenThread = new Thread(() =>
{
splashScreen = new SplashScreen(runtimeInfo, text);
splashScreen = new SplashScreen(text, runtimeInfo);
splashScreen.Closed += (o, args) => splashScreen.Dispatcher.InvokeShutdown();
splashScreen.Show();

View file

@ -23,13 +23,25 @@ namespace SafeExamBrowser.UserInterface.Windows10
private IText text;
private WindowClosingEventHandler closing;
public RuntimeInfo RuntimeInfo
{
set
{
Dispatcher.Invoke(() =>
{
runtimeInfo = value;
UpdateRuntimeInfo();
});
}
}
event WindowClosingEventHandler IWindow.Closing
{
add { closing += value; }
remove { closing -= value; }
}
public SplashScreen(RuntimeInfo runtimeInfo, IText text)
public SplashScreen(IText text, RuntimeInfo runtimeInfo = null)
{
this.runtimeInfo = runtimeInfo;
this.text = text;
@ -100,11 +112,8 @@ namespace SafeExamBrowser.UserInterface.Windows10
private void InitializeSplashScreen()
{
InfoTextBlock.Inlines.Add(new Run($"Version {runtimeInfo.ProgramVersion}") { FontStyle = FontStyles.Italic });
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new Run(runtimeInfo.ProgramCopyright) { FontSize = 10 });
UpdateRuntimeInfo();
StatusTextBlock.DataContext = model;
ProgressBar.DataContext = model;
@ -113,5 +122,16 @@ namespace SafeExamBrowser.UserInterface.Windows10
Closing += (o, args) => args.Cancel = !allowClose;
}
private void UpdateRuntimeInfo()
{
if (runtimeInfo != null)
{
InfoTextBlock.Inlines.Add(new Run($"Version {runtimeInfo.ProgramVersion}") { FontStyle = FontStyles.Italic });
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new LineBreak());
InfoTextBlock.Inlines.Add(new Run(runtimeInfo.ProgramCopyright) { FontSize = 10 });
}
}
}
}

View file

@ -90,13 +90,13 @@ namespace SafeExamBrowser.UserInterface.Windows10
throw new System.NotImplementedException();
}
public ISplashScreen CreateSplashScreen(RuntimeInfo runtimeInfo)
public ISplashScreen CreateSplashScreen(RuntimeInfo runtimeInfo = null)
{
SplashScreen splashScreen = null;
var splashReadyEvent = new AutoResetEvent(false);
var splashScreenThread = new Thread(() =>
{
splashScreen = new SplashScreen(runtimeInfo, text);
splashScreen = new SplashScreen(text, runtimeInfo);
splashScreen.Closed += (o, args) => splashScreen.Dispatcher.InvokeShutdown();
splashScreen.Show();