Enhanced startup- and shutdown-procedure by introducing a stack of IOperations (which are automatically reverted on shutdown or if an error happens during startup).

This commit is contained in:
Damian Büchel 2017-07-21 10:04:27 +02:00
parent eb6fbf49b8
commit 1153fea091
18 changed files with 429 additions and 167 deletions

View file

@ -18,7 +18,7 @@ namespace SafeExamBrowser.Configuration
public class WorkingArea : IWorkingArea
{
private ILogger logger;
private RECT? initial;
private RECT? originalWorkingArea;
public WorkingArea(ILogger logger)
{
@ -27,9 +27,9 @@ namespace SafeExamBrowser.Configuration
public void InitializeFor(ITaskbar taskbar)
{
initial = User32.GetWorkingArea();
originalWorkingArea = User32.GetWorkingArea();
LogWorkingArea("Saved initial working area", initial.Value);
LogWorkingArea("Saved original working area", originalWorkingArea.Value);
var area = new RECT
{
@ -39,17 +39,17 @@ namespace SafeExamBrowser.Configuration
Bottom = Screen.PrimaryScreen.Bounds.Height - taskbar.GetAbsoluteHeight()
};
LogWorkingArea("Setting new working area", area);
LogWorkingArea("Trying to set new working area", area);
User32.SetWorkingArea(area);
LogWorkingArea("Working area is now set to", User32.GetWorkingArea());
}
public void Reset()
{
if (initial.HasValue)
if (originalWorkingArea.HasValue)
{
User32.SetWorkingArea(initial.Value);
LogWorkingArea("Restored initial working area", initial.Value);
User32.SetWorkingArea(originalWorkingArea.Value);
LogWorkingArea("Restored original working area", originalWorkingArea.Value);
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2017 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 SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Contracts.Behaviour
{
public interface IOperation
{
/// <summary>
/// The splash screen to be used to show status information to the user.
/// </summary>
ISplashScreen SplashScreen { set; }
/// <summary>
/// Performs the operation.
/// </summary>
void Perform();
/// <summary>
/// Reverts all changes which were made when performing the operation.
/// </summary>
void Revert();
}
}

View file

@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Collections.Generic;
namespace SafeExamBrowser.Contracts.Behaviour
{
public interface IShutdownController
@ -13,6 +15,6 @@ namespace SafeExamBrowser.Contracts.Behaviour
/// <summary>
/// Reverts any changes performed during the startup or runtime and releases all used resources.
/// </summary>
void FinalizeApplication();
void FinalizeApplication(Stack<IOperation> operations);
}
}

View file

@ -6,14 +6,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Collections.Generic;
namespace SafeExamBrowser.Contracts.Behaviour
{
public interface IStartupController
{
/// <summary>
/// Tries to initialize the application. Returns <c>true</c> if the initialization was successful,
/// <c>false</c> otherwise.
/// <c>false</c> otherwise. All operations performed during the startup procedure will be registered
/// to the given <c>out</c> parameter.
/// </summary>
bool TryInitializeApplication();
bool TryInitializeApplication(out Stack<IOperation> operations);
}
}

View file

@ -42,6 +42,7 @@
<ItemGroup>
<Compile Include="Behaviour\IApplicationController.cs" />
<Compile Include="Behaviour\INotificationController.cs" />
<Compile Include="Behaviour\IOperation.cs" />
<Compile Include="Configuration\IIconResource.cs" />
<Compile Include="Configuration\IApplicationInfo.cs" />
<Compile Include="Configuration\IApplicationInstance.cs" />

View file

@ -22,16 +22,27 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary>
void InvokeShow();
/// <summary>
/// Updates the progress bar of the splash screen according to the specified amount.
/// </summary>
void Progress(int amount = 1);
/// <summary>
/// Regresses the progress bar of the splash screen according to the specified amount.
/// </summary>
void Regress(int amount = 1);
/// <summary>
/// Sets the style of the progress bar to indeterminate, i.e. <c>Progress</c> and
/// <c>Regress</c> won't have any effect when called.
/// </summary>
void SetIndeterminate();
/// <summary>
/// Set the maximum of the splash screen's progress bar.
/// </summary>
void SetMaxProgress(int max);
/// <summary>
/// Updates the progress bar of the splash screen according to the specified amount.
/// </summary>
void UpdateProgress(int amount = 1);
/// <summary>
/// Updates the status text of the splash screen. If the busy flag is set,
/// the splash screen will show an animation to indicate a long-running operation.

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2017 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 SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour.Operations
{
class BrowserInitializationOperation : IOperation
{
private IApplicationController browserController;
private IApplicationInfo browserInfo;
private ILogger logger;
private ITaskbar taskbar;
private IUiElementFactory uiFactory;
public ISplashScreen SplashScreen { private get; set; }
public BrowserInitializationOperation(
IApplicationController browserController,
IApplicationInfo browserInfo,
ILogger logger,
ITaskbar taskbar,
IUiElementFactory uiFactory)
{
this.browserController = browserController;
this.browserInfo = browserInfo;
this.logger = logger;
this.taskbar = taskbar;
this.uiFactory = uiFactory;
}
public void Perform()
{
logger.Info("--- Initializing browser ---");
SplashScreen.UpdateText(Key.SplashScreen_InitializeBrowser);
var browserButton = uiFactory.CreateApplicationButton(browserInfo);
browserController.RegisterApplicationButton(browserButton);
taskbar.AddButton(browserButton);
}
public void Revert()
{
// Nothing to do here so far...
}
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2017 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 SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour.Operations
{
class ProcessMonitoringOperation : IOperation
{
private ILogger logger;
private IProcessMonitor processMonitor;
public ISplashScreen SplashScreen { private get; set; }
public ProcessMonitoringOperation(ILogger logger, IProcessMonitor processMonitor)
{
this.logger = logger;
this.processMonitor = processMonitor;
}
public void Perform()
{
logger.Info("--- Initializing process monitoring ---");
SplashScreen.UpdateText(Key.SplashScreen_InitializeProcessMonitoring);
// TODO
}
public void Revert()
{
logger.Info("--- Stopping process monitoring ---");
SplashScreen.UpdateText(Key.SplashScreen_StopProcessMonitoring);
// TODO
}
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2017 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 SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour.Operations
{
class TaskbarInitializationOperation : IOperation
{
private ILogger logger;
private ITaskbar taskbar;
private IUiElementFactory uiFactory;
private INotificationInfo aboutInfo;
public ISplashScreen SplashScreen { private get; set; }
public TaskbarInitializationOperation(ILogger logger, INotificationInfo aboutInfo, ITaskbar taskbar, IUiElementFactory uiFactory)
{
this.logger = logger;
this.aboutInfo = aboutInfo;
this.taskbar = taskbar;
this.uiFactory = uiFactory;
}
public void Perform()
{
logger.Info("--- Initializing taskbar ---");
SplashScreen.UpdateText(Key.SplashScreen_InitializeTaskbar);
var aboutNotification = uiFactory.CreateNotification(aboutInfo);
taskbar.AddNotification(aboutNotification);
}
public void Revert()
{
// Nothing to do here so far...
}
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2017 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 SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
namespace SafeExamBrowser.Core.Behaviour.Operations
{
class WorkingAreaOperation : IOperation
{
private ILogger logger;
private IProcessMonitor processMonitor;
private ITaskbar taskbar;
private IWorkingArea workingArea;
public ISplashScreen SplashScreen { private get; set; }
public WorkingAreaOperation(ILogger logger, IProcessMonitor processMonitor, ITaskbar taskbar, IWorkingArea workingArea)
{
this.logger = logger;
this.processMonitor = processMonitor;
this.taskbar = taskbar;
this.workingArea = workingArea;
}
public void Perform()
{
logger.Info("--- Initializing working area ---");
SplashScreen.UpdateText(Key.SplashScreen_WaitExplorerTermination, true);
processMonitor.CloseExplorerShell();
processMonitor.StartMonitoringExplorer();
// TODO
// - Minimizing all open windows
// - Emptying clipboard
SplashScreen.UpdateText(Key.SplashScreen_InitializeWorkingArea);
workingArea.InitializeFor(taskbar);
}
public void Revert()
{
logger.Info("--- Restoring working area ---");
SplashScreen.UpdateText(Key.SplashScreen_RestoreWorkingArea);
// TODO
// - Restore all windows?
// - Emptying clipboard
workingArea.Reset();
SplashScreen.UpdateText(Key.SplashScreen_WaitExplorerStartup, true);
processMonitor.StopMonitoringExplorer();
processMonitor.StartExplorerShell();
}
}
}

View file

@ -30,16 +30,6 @@ namespace SafeExamBrowser.Core.Behaviour
private IUiElementFactory uiFactory;
private IWorkingArea workingArea;
private IEnumerable<Action> ShutdownOperations
{
get
{
yield return StopProcessMonitoring;
yield return RestoreWorkingArea;
yield return FinalizeApplicationLog;
}
}
public ShutdownController(
ILogger logger,
IMessageBox messageBox,
@ -58,64 +48,62 @@ namespace SafeExamBrowser.Core.Behaviour
this.workingArea = workingArea;
}
public void FinalizeApplication()
public void FinalizeApplication(Stack<IOperation> operations)
{
try
{
InitializeSplashScreen();
foreach (var operation in ShutdownOperations)
{
operation();
splashScreen.UpdateProgress();
// TODO: Remove!
Thread.Sleep(250);
}
RevertOperations(operations);
FinalizeApplicationLog();
}
catch (Exception e)
{
logger.Error($"Failed to finalize application!", e);
messageBox.Show(text.Get(Key.MessageBox_ShutdownError), text.Get(Key.MessageBox_ShutdownErrorTitle), icon: MessageBoxIcon.Error);
LogAndShowException(e);
FinalizeApplicationLog(false);
}
}
private void RevertOperations(Stack<IOperation> operations)
{
while (operations.Any())
{
var operation = operations.Pop();
operation.SplashScreen = splashScreen;
operation.Revert();
splashScreen.Progress();
// TODO: Remove!
Thread.Sleep(250);
}
}
private void InitializeSplashScreen()
{
splashScreen = uiFactory.CreateSplashScreen(settings, text);
splashScreen.SetMaxProgress(ShutdownOperations.Count());
splashScreen.SetIndeterminate();
splashScreen.UpdateText(Key.SplashScreen_ShutdownProcedure);
splashScreen.InvokeShow();
logger.Info("--- Initiating shutdown procedure ---");
}
private void StopProcessMonitoring()
private void LogAndShowException(Exception e)
{
logger.Info("--- Stopping process monitoring ---");
splashScreen.UpdateText(Key.SplashScreen_StopProcessMonitoring);
// TODO
processMonitor.StopMonitoringExplorer();
logger.Error($"Failed to finalize application!", e);
messageBox.Show(text.Get(Key.MessageBox_ShutdownError), text.Get(Key.MessageBox_ShutdownErrorTitle), icon: MessageBoxIcon.Error);
}
private void RestoreWorkingArea()
private void FinalizeApplicationLog(bool success = true)
{
logger.Info("--- Restoring working area ---");
splashScreen.UpdateText(Key.SplashScreen_RestoreWorkingArea);
// TODO
workingArea.Reset();
splashScreen.UpdateText(Key.SplashScreen_WaitExplorerStartup, true);
processMonitor.StartExplorerShell();
}
private void FinalizeApplicationLog()
{
logger.Info("--- Application successfully finalized! ---");
logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
if (success)
{
logger.Info("--- Application successfully finalized! ---");
}
else
{
logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
}
}
}

View file

@ -16,6 +16,7 @@ using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Core.Behaviour.Operations;
namespace SafeExamBrowser.Core.Behaviour
{
@ -34,21 +35,7 @@ namespace SafeExamBrowser.Core.Behaviour
private IUiElementFactory uiFactory;
private IWorkingArea workingArea;
private IEnumerable<Action> StartupOperations
{
get
{
yield return HandleCommandLineArguments;
yield return DetectOperatingSystem;
yield return EstablishWcfServiceConnection;
yield return DeactivateWindowsFeatures;
yield return InitializeProcessMonitoring;
yield return InitializeWorkingArea;
yield return InitializeTaskbar;
yield return InitializeBrowser;
yield return FinishInitialization;
}
}
private IEnumerable<IOperation> startupOperations;
public StartupController(
IApplicationController browserController,
@ -76,33 +63,78 @@ namespace SafeExamBrowser.Core.Behaviour
this.workingArea = workingArea;
}
public bool TryInitializeApplication()
public bool TryInitializeApplication(out Stack<IOperation> operations)
{
operations = new Stack<IOperation>();
try
{
CreateStartupOperations();
InitializeApplicationLog();
InitializeSplashScreen();
foreach (var operation in StartupOperations)
{
operation();
splashScreen.UpdateProgress();
operations = PerformOperations();
// TODO: Remove!
Thread.Sleep(250);
}
FinishInitialization();
return true;
}
catch (Exception e)
{
logger.Error($"Failed to initialize application!", e);
messageBox.Show(text.Get(Key.MessageBox_StartupError), text.Get(Key.MessageBox_StartupErrorTitle), icon: MessageBoxIcon.Error);
LogAndShowException(e);
RevertOperations(operations);
FinishInitialization(false);
return false;
}
}
private Stack<IOperation> PerformOperations()
{
var operations = new Stack<IOperation>();
foreach (var operation in startupOperations)
{
operations.Push(operation);
operation.SplashScreen = splashScreen;
operation.Perform();
splashScreen.Progress();
// TODO: Remove!
Thread.Sleep(250);
}
return operations;
}
private void RevertOperations(Stack<IOperation> operations)
{
while (operations.Any())
{
var operation = operations.Pop();
operation.Revert();
splashScreen.Regress();
// TODO: Remove!
Thread.Sleep(250);
}
}
private void CreateStartupOperations()
{
startupOperations = new IOperation[]
{
new ProcessMonitoringOperation(logger, processMonitor),
new WorkingAreaOperation(logger, processMonitor, taskbar, workingArea),
new TaskbarInitializationOperation(logger, aboutInfo, taskbar, uiFactory),
new BrowserInitializationOperation(browserController, browserInfo, logger, taskbar, uiFactory)
};
}
private void InitializeApplicationLog()
{
var titleLine = $"/* {settings.ProgramTitle}, Version {settings.ProgramVersion}{Environment.NewLine}";
@ -118,84 +150,29 @@ namespace SafeExamBrowser.Core.Behaviour
private void InitializeSplashScreen()
{
splashScreen = uiFactory.CreateSplashScreen(settings, text);
splashScreen.SetMaxProgress(StartupOperations.Count());
splashScreen.SetMaxProgress(startupOperations.Count());
splashScreen.UpdateText(Key.SplashScreen_StartupProcedure);
splashScreen.InvokeShow();
}
private void HandleCommandLineArguments()
private void LogAndShowException(Exception e)
{
// TODO
logger.Error($"Failed to initialize application!", e);
messageBox.Show(text.Get(Key.MessageBox_StartupError), text.Get(Key.MessageBox_StartupErrorTitle), icon: MessageBoxIcon.Error);
logger.Info("Reverting operations...");
}
private void DetectOperatingSystem()
private void FinishInitialization(bool success = true)
{
// TODO
}
private void EstablishWcfServiceConnection()
{
// TODO
}
private void DeactivateWindowsFeatures()
{
// TODO
}
private void InitializeProcessMonitoring()
{
logger.Info("--- Initializing process monitoring ---");
splashScreen.UpdateText(Key.SplashScreen_InitializeProcessMonitoring);
// TODO
processMonitor.StartMonitoringExplorer();
}
private void InitializeWorkingArea()
{
logger.Info("--- Initializing working area ---");
splashScreen.UpdateText(Key.SplashScreen_WaitExplorerTermination, true);
processMonitor.CloseExplorerShell();
// TODO
// - Minimizing all open windows
// - Emptying clipboard
splashScreen.UpdateText(Key.SplashScreen_InitializeWorkingArea);
workingArea.InitializeFor(taskbar);
}
private void InitializeTaskbar()
{
logger.Info("--- Initializing taskbar ---");
splashScreen.UpdateText(Key.SplashScreen_InitializeTaskbar);
// TODO
var aboutNotification = uiFactory.CreateNotification(aboutInfo);
taskbar.AddNotification(aboutNotification);
}
private void InitializeBrowser()
{
logger.Info("--- Initializing browser ---");
splashScreen.UpdateText(Key.SplashScreen_InitializeBrowser);
// TODO
var browserButton = uiFactory.CreateApplicationButton(browserInfo);
browserController.RegisterApplicationButton(browserButton);
taskbar.AddButton(browserButton);
}
private void FinishInitialization()
{
logger.Info("--- Application successfully initialized! ---");
splashScreen.InvokeClose();
if (success)
{
logger.Info("--- Application successfully initialized! ---");
splashScreen.InvokeClose();
}
else
{
logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
}
}
}

View file

@ -40,6 +40,10 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Behaviour\Operations\BrowserInitializationOperation.cs" />
<Compile Include="Behaviour\Operations\ProcessMonitoringOperation.cs" />
<Compile Include="Behaviour\Operations\TaskbarInitializationOperation.cs" />
<Compile Include="Behaviour\Operations\WorkingAreaOperation.cs" />
<Compile Include="Behaviour\ShutdownController.cs" />
<Compile Include="Behaviour\StartupController.cs" />
<Compile Include="Logging\LogFileWriter.cs" />

View file

@ -22,7 +22,7 @@
<Image Grid.Column="0" Grid.ColumnSpan="2" Source="pack://application:,,,/SafeExamBrowser.UserInterface;component/Images/SplashScreen.png" />
<TextBlock x:Name="InfoTextBlock" Grid.Column="1" Foreground="White" Margin="10,75,10,10" TextWrapping="Wrap" />
</Grid>
<ProgressBar x:Name="ProgressBar" Grid.Row="1" Minimum="0" Maximum="{Binding Path=MaxProgress}" Value="{Binding Path=CurrentProgress}" Background="#00000000" BorderThickness="0" />
<ProgressBar x:Name="ProgressBar" Grid.Row="1" Minimum="0" Maximum="{Binding Path=MaxProgress}" Value="{Binding Path=CurrentProgress}" IsIndeterminate="{Binding Path=IsIndeterminate}" Background="#00000000" BorderThickness="0" />
<TextBlock x:Name="StatusTextBlock" Grid.Row="1" Text="{Binding Path=Status}" FontSize="12" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" />
</Grid>
</Border>

View file

@ -40,16 +40,26 @@ namespace SafeExamBrowser.UserInterface
Dispatcher.Invoke(Show);
}
public void Progress(int amount = 1)
{
model.CurrentProgress += amount;
}
public void Regress(int amount = 1)
{
model.CurrentProgress -= amount;
}
public void SetIndeterminate()
{
model.IsIndeterminate = true;
}
public void SetMaxProgress(int max)
{
model.MaxProgress = max;
}
public void UpdateProgress(int amount = 1)
{
model.CurrentProgress += amount;
}
public void UpdateText(Key key, bool showBusyIndication = false)
{
model.StopBusyIndication();

View file

@ -6,7 +6,7 @@
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Controls"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="Taskbar" Height="40" Width="750" WindowStyle="None" AllowsTransparency="True" Topmost="True" Icon="./Images/SafeExamBrowser.ico">
Title="Taskbar" Height="40" Width="750" WindowStyle="None" AllowsTransparency="True" Topmost="True" Visibility="Collapsed" Icon="./Images/SafeExamBrowser.ico">
<Window.Background>
<SolidColorBrush Color="Black" Opacity="0.8" />
</Window.Background>

View file

@ -14,6 +14,7 @@ namespace SafeExamBrowser.UserInterface.ViewModels
class SplashScreenViewModel : INotifyPropertyChanged
{
private int currentProgress;
private bool isIndeterminate;
private int maxProgress;
private string status;
private Timer busyTimer;
@ -33,6 +34,19 @@ namespace SafeExamBrowser.UserInterface.ViewModels
}
}
public bool IsIndeterminate
{
get
{
return isIndeterminate;
}
set
{
isIndeterminate = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsIndeterminate)));
}
}
public int MaxProgress
{
get

View file

@ -7,8 +7,10 @@
*/
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using SafeExamBrowser.Contracts.Behaviour;
namespace SafeExamBrowser
{
@ -57,12 +59,12 @@ namespace SafeExamBrowser
instances.BuildObjectGraph();
var success = instances.StartupController.TryInitializeApplication();
var success = instances.StartupController.TryInitializeApplication(out Stack<IOperation> operations);
if (success)
{
MainWindow = instances.Taskbar;
MainWindow.Closing += (o, args) => ShutdownApplication();
MainWindow.Closing += (o, args) => ShutdownApplication(operations);
MainWindow.Show();
}
else
@ -71,10 +73,10 @@ namespace SafeExamBrowser
}
}
private void ShutdownApplication()
private void ShutdownApplication(Stack<IOperation> operations)
{
MainWindow.Hide();
instances.ShutdownController.FinalizeApplication();
instances.ShutdownController.FinalizeApplication(operations);
}
}
}