From 7bc2686560adf9703b5679e972d406ee4fc22822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20B=C3=BCchel?= Date: Mon, 10 Jul 2017 15:47:12 +0200 Subject: [PATCH] Finished first draft of application scaffolding and splash screen. --- .../Configuration/ISettings.cs | 2 + .../Configuration/IStartupController.cs | 4 +- .../Logging/ILogMessage.cs | 2 +- .../Logging/IThreadInfo.cs | 19 +++++ .../SafeExamBrowser.Contracts.csproj | 1 + .../UserInterface/ISplashScreen.cs | 4 +- .../UserInterface/ITaskbar.cs | 1 + .../Configuration/Settings.cs | 37 +++++++--- .../Configuration/ShutdownController.cs | 2 + .../Configuration/StartupController.cs | 32 ++++++--- SafeExamBrowser.Core/I18n/Text.xml | 2 - SafeExamBrowser.Core/Logging/LogFileWriter.cs | 3 +- SafeExamBrowser.Core/Logging/LogMessage.cs | 15 ++-- SafeExamBrowser.Core/Logging/Logger.cs | 4 +- SafeExamBrowser.Core/Logging/ThreadInfo.cs | 35 +++++++++ .../SafeExamBrowser.Core.csproj | 1 + .../Images/SplashScreen.png | Bin 0 -> 40053 bytes .../SafeExamBrowser.UserInterface.csproj | 4 ++ .../SplashScreen.xaml | 34 +++++---- .../SplashScreen.xaml.cs | 30 +++++++- SafeExamBrowser.UserInterface/Taskbar.xaml | 9 ++- .../ViewModels/SplashScreenViewModel.cs | 60 ++++++++++++++++ SafeExamBrowser/App.cs | 68 ++++++++++++------ SafeExamBrowser/CompositionRoot.cs | 11 ++- 24 files changed, 301 insertions(+), 79 deletions(-) create mode 100644 SafeExamBrowser.Contracts/Logging/IThreadInfo.cs create mode 100644 SafeExamBrowser.Core/Logging/ThreadInfo.cs create mode 100644 SafeExamBrowser.UserInterface/Images/SplashScreen.png create mode 100644 SafeExamBrowser.UserInterface/ViewModels/SplashScreenViewModel.cs diff --git a/SafeExamBrowser.Contracts/Configuration/ISettings.cs b/SafeExamBrowser.Contracts/Configuration/ISettings.cs index 9eefa75a..70ed699f 100644 --- a/SafeExamBrowser.Contracts/Configuration/ISettings.cs +++ b/SafeExamBrowser.Contracts/Configuration/ISettings.cs @@ -10,7 +10,9 @@ namespace SafeExamBrowser.Contracts.Configuration { public interface ISettings { + string CopyrightInfo { get; } string LogFolderPath { get; } string LogHeader { get; } + string ProgramVersion { get; } } } diff --git a/SafeExamBrowser.Contracts/Configuration/IStartupController.cs b/SafeExamBrowser.Contracts/Configuration/IStartupController.cs index fc700b16..f04ae701 100644 --- a/SafeExamBrowser.Contracts/Configuration/IStartupController.cs +++ b/SafeExamBrowser.Contracts/Configuration/IStartupController.cs @@ -6,12 +6,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using System; - namespace SafeExamBrowser.Contracts.Configuration { public interface IStartupController { - void InitializeApplication(Action terminationCallback); + bool TryInitializeApplication(); } } diff --git a/SafeExamBrowser.Contracts/Logging/ILogMessage.cs b/SafeExamBrowser.Contracts/Logging/ILogMessage.cs index d2a7e5e8..5fc62eba 100644 --- a/SafeExamBrowser.Contracts/Logging/ILogMessage.cs +++ b/SafeExamBrowser.Contracts/Logging/ILogMessage.cs @@ -15,6 +15,6 @@ namespace SafeExamBrowser.Contracts.Logging DateTime DateTime { get; } LogLevel Severity { get; } string Message { get; } - int ThreadId { get; } + IThreadInfo ThreadInfo { get; } } } diff --git a/SafeExamBrowser.Contracts/Logging/IThreadInfo.cs b/SafeExamBrowser.Contracts/Logging/IThreadInfo.cs new file mode 100644 index 00000000..1dc23ff3 --- /dev/null +++ b/SafeExamBrowser.Contracts/Logging/IThreadInfo.cs @@ -0,0 +1,19 @@ +/* + * 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 System; + +namespace SafeExamBrowser.Contracts.Logging +{ + public interface IThreadInfo : ICloneable + { + int Id { get; } + string Name { get; } + bool HasName { get; } + } +} diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index c044e09c..e45dd046 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -50,6 +50,7 @@ + diff --git a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs index 9e9a5205..67396d7e 100644 --- a/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs +++ b/SafeExamBrowser.Contracts/UserInterface/ISplashScreen.cs @@ -12,7 +12,9 @@ namespace SafeExamBrowser.Contracts.UserInterface { public interface ISplashScreen : ILogObserver { - void Show(); void Close(); + void SetMaxProgress(int max); + void Show(); + void UpdateProgress(); } } diff --git a/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs b/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs index 9458bf7a..abbaf869 100644 --- a/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs +++ b/SafeExamBrowser.Contracts/UserInterface/ITaskbar.cs @@ -12,5 +12,6 @@ namespace SafeExamBrowser.Contracts.UserInterface { void SetPosition(int x, int y); void SetSize(int widht, int height); + void Show(); } } diff --git a/SafeExamBrowser.Core/Configuration/Settings.cs b/SafeExamBrowser.Core/Configuration/Settings.cs index 111f08da..b55e8a76 100644 --- a/SafeExamBrowser.Core/Configuration/Settings.cs +++ b/SafeExamBrowser.Core/Configuration/Settings.cs @@ -15,6 +15,17 @@ namespace SafeExamBrowser.Core.Configuration { public class Settings : ISettings { + public string CopyrightInfo + { + get + { + var executable = Assembly.GetEntryAssembly(); + var copyright = executable.GetCustomAttribute().Copyright; + + return copyright; + } + } + public string LogFolderPath { get @@ -27,21 +38,27 @@ namespace SafeExamBrowser.Core.Configuration { get { - var executable = Assembly.GetEntryAssembly(); var newline = Environment.NewLine; - var version = executable.GetCustomAttribute().InformationalVersion; + var executable = Assembly.GetEntryAssembly(); var title = executable.GetCustomAttribute().Title; - var copyright = executable.GetCustomAttribute().Copyright; - var titleLine = $"/* {title}, Version {version}{newline}"; - var copyrightLine = $"/* {copyright}{newline}"; + var titleLine = $"/* {title}, Version {ProgramVersion}{newline}"; + var copyrightLine = $"/* {CopyrightInfo}{newline}"; var emptyLine = $"/* {newline}"; - var license1 = $"/* The source code of this application is subject to the terms of the Mozilla Public{newline}"; - var license2 = $"/* License, v. 2.0. If a copy of the MPL was not distributed with this software, You{newline}"; - var license3 = $"/* can obtain one at http://mozilla.org/MPL/2.0/.{newline}"; - var github = $"/* For more information or to issue bug reports, see https://github.com/SafeExamBrowser.{newline}"; + var githubLine = $"/* Please visit https://github.com/SafeExamBrowser for more information."; - return $"{titleLine}{copyrightLine}{emptyLine}{license1}{license2}{license3}{emptyLine}{github}"; + return $"{titleLine}{copyrightLine}{emptyLine}{githubLine}"; + } + } + + public string ProgramVersion + { + get + { + var executable = Assembly.GetEntryAssembly(); + var version = executable.GetCustomAttribute().InformationalVersion; + + return version; } } } diff --git a/SafeExamBrowser.Core/Configuration/ShutdownController.cs b/SafeExamBrowser.Core/Configuration/ShutdownController.cs index c46bb32e..643fba22 100644 --- a/SafeExamBrowser.Core/Configuration/ShutdownController.cs +++ b/SafeExamBrowser.Core/Configuration/ShutdownController.cs @@ -33,6 +33,8 @@ namespace SafeExamBrowser.Core.Configuration { // TODO: // - Gather TODOs! + + logger.Log($"{Environment.NewLine}# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); } catch (Exception e) { diff --git a/SafeExamBrowser.Core/Configuration/StartupController.cs b/SafeExamBrowser.Core/Configuration/StartupController.cs index 5d579f88..3faf63b9 100644 --- a/SafeExamBrowser.Core/Configuration/StartupController.cs +++ b/SafeExamBrowser.Core/Configuration/StartupController.cs @@ -19,31 +19,40 @@ namespace SafeExamBrowser.Core.Configuration { private ILogger logger; private IMessageBox messageBox; + private ISettings settings; private ISplashScreen splashScreen; + private ITaskbar taskbar; private IText text; - public StartupController(ILogger logger, IMessageBox messageBox, ISplashScreen splashScreen, IText text) + public StartupController(ILogger logger, IMessageBox messageBox, ISettings settings, ISplashScreen splashScreen, ITaskbar taskbar, IText text) { this.logger = logger; this.messageBox = messageBox; + this.settings = settings; this.splashScreen = splashScreen; + this.taskbar = taskbar; this.text = text; } - public void InitializeApplication(Action terminationCallback) + public bool TryInitializeApplication() { try { - logger.Info("Rendering splash screen."); + logger.Log(settings.LogHeader); + logger.Log($"{Environment.NewLine}# Application started at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}{Environment.NewLine}"); + logger.Info("Initiating startup procedure."); + logger.Subscribe(splashScreen); - splashScreen.Show(); + + splashScreen.SetMaxProgress(4); + splashScreen.UpdateProgress(); // TODO (depending on specification): // - WCF service connection, termination if not available // TODO: // - Parse command line arguments - // - Detecting operating system and logging information + // - Detecting operating system and log that information // - Logging of all running processes // - Setting of wallpaper // - Initialization of taskbar @@ -54,21 +63,28 @@ namespace SafeExamBrowser.Core.Configuration Thread.Sleep(3000); + splashScreen.UpdateProgress(); logger.Info("Baapa-dee boopa-dee!"); Thread.Sleep(3000); + splashScreen.UpdateProgress(); logger.Info("Closing splash screen."); - logger.Unsubscribe(splashScreen); - splashScreen.Close(); + Thread.Sleep(3000); + + splashScreen.UpdateProgress(); + logger.Unsubscribe(splashScreen); logger.Info("Application successfully initialized!"); + + 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); - terminationCallback?.Invoke(); + + return false; } } } diff --git a/SafeExamBrowser.Core/I18n/Text.xml b/SafeExamBrowser.Core/I18n/Text.xml index f25c4eb0..bd495c9e 100644 --- a/SafeExamBrowser.Core/I18n/Text.xml +++ b/SafeExamBrowser.Core/I18n/Text.xml @@ -2,8 +2,6 @@ An unexpected error occurred during the shutdown procedure! Please consult the application log for more information... Shutdown Error - You can only run one instance of SEB at a time. - Startup Not Allowed An unexpected error occurred during the startup procedure! Please consult the application log for more information... Startup Error \ No newline at end of file diff --git a/SafeExamBrowser.Core/Logging/LogFileWriter.cs b/SafeExamBrowser.Core/Logging/LogFileWriter.cs index 0ac5dc7a..a5e78d6b 100644 --- a/SafeExamBrowser.Core/Logging/LogFileWriter.cs +++ b/SafeExamBrowser.Core/Logging/LogFileWriter.cs @@ -53,8 +53,9 @@ namespace SafeExamBrowser.Core.Logging { var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"); var severity = message.Severity.ToString().ToUpper(); + var threadInfo = $"{message.ThreadInfo.Id}{(message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty)}"; - Write($"{date} [{message.ThreadId}] - {severity}: {message.Message}"); + Write($"{date} [{threadInfo}] - {severity}: {message.Message}"); } private void Write(string content) diff --git a/SafeExamBrowser.Core/Logging/LogMessage.cs b/SafeExamBrowser.Core/Logging/LogMessage.cs index e4301f8a..5f35ad37 100644 --- a/SafeExamBrowser.Core/Logging/LogMessage.cs +++ b/SafeExamBrowser.Core/Logging/LogMessage.cs @@ -11,24 +11,29 @@ using SafeExamBrowser.Contracts.Logging; namespace SafeExamBrowser.Core.Entities { - public class LogMessage : ILogMessage + class LogMessage : ILogMessage { public DateTime DateTime { get; private set; } public LogLevel Severity { get; private set; } public string Message { get; private set; } - public int ThreadId { get; private set; } + public IThreadInfo ThreadInfo { get; private set; } - public LogMessage(DateTime dateTime, LogLevel severity, int threadId, string message) + public LogMessage(DateTime dateTime, LogLevel severity, string message, IThreadInfo threadInfo) { + if (threadInfo == null) + { + throw new ArgumentNullException(nameof(threadInfo)); + } + DateTime = dateTime; Severity = severity; Message = message; - ThreadId = threadId; + ThreadInfo = threadInfo; } public object Clone() { - return new LogMessage(DateTime, Severity, ThreadId, Message); + return new LogMessage(DateTime, Severity, Message, ThreadInfo.Clone() as IThreadInfo); } } } diff --git a/SafeExamBrowser.Core/Logging/Logger.cs b/SafeExamBrowser.Core/Logging/Logger.cs index edae921e..5660c700 100644 --- a/SafeExamBrowser.Core/Logging/Logger.cs +++ b/SafeExamBrowser.Core/Logging/Logger.cs @@ -106,8 +106,10 @@ namespace SafeExamBrowser.Core.Logging private void Add(LogLevel severity, string message) { var threadId = Thread.CurrentThread.ManagedThreadId; + var threadName = Thread.CurrentThread.Name; + var threadInfo = new ThreadInfo(threadId, threadName); - Add(new LogMessage(DateTime.Now, severity, threadId, message)); + Add(new LogMessage(DateTime.Now, severity, message, threadInfo)); } private void Add(ILogContent content) diff --git a/SafeExamBrowser.Core/Logging/ThreadInfo.cs b/SafeExamBrowser.Core/Logging/ThreadInfo.cs new file mode 100644 index 00000000..d7b7a666 --- /dev/null +++ b/SafeExamBrowser.Core/Logging/ThreadInfo.cs @@ -0,0 +1,35 @@ +/* + * 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 System; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.Core.Logging +{ + class ThreadInfo : IThreadInfo + { + public int Id { get; private set; } + public string Name { get; private set; } + + public bool HasName + { + get { return !String.IsNullOrWhiteSpace(Name); } + } + + public ThreadInfo(int id, string name = null) + { + Id = id; + Name = name; + } + + public object Clone() + { + return new ThreadInfo(Id, Name); + } + } +} diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj index c7e4f48a..f05e7753 100644 --- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj +++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj @@ -49,6 +49,7 @@ + diff --git a/SafeExamBrowser.UserInterface/Images/SplashScreen.png b/SafeExamBrowser.UserInterface/Images/SplashScreen.png new file mode 100644 index 0000000000000000000000000000000000000000..c56dd2b0dbb3fb7f0a9aa43fc4b40e32edba8d9f GIT binary patch literal 40053 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 Taskbar.xaml + ResXFileCodeGenerator @@ -92,5 +93,8 @@ SafeExamBrowser.Contracts + + + \ No newline at end of file diff --git a/SafeExamBrowser.UserInterface/SplashScreen.xaml b/SafeExamBrowser.UserInterface/SplashScreen.xaml index 6f04d7f1..f8727931 100644 --- a/SafeExamBrowser.UserInterface/SplashScreen.xaml +++ b/SafeExamBrowser.UserInterface/SplashScreen.xaml @@ -3,23 +3,27 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:local="clr-namespace:SafeExamBrowser.UserInterface" mc:Ignorable="d" - Title="SplashScreen" Height="200" Width="350" WindowStyle="None" AllowsTransparency="True" Topmost="True" - WindowStartupLocation="CenterScreen" Cursor="Wait"> + Title="SplashScreen" Height="200" Width="350" WindowStyle="None" AllowsTransparency="True" WindowStartupLocation="CenterScreen" Cursor="Wait"> - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs b/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs index 9cbd9209..583bb8b7 100644 --- a/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs +++ b/SafeExamBrowser.UserInterface/SplashScreen.xaml.cs @@ -7,21 +7,47 @@ */ using System.Windows; +using System.Windows.Documents; +using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.UserInterface; +using SafeExamBrowser.UserInterface.ViewModels; namespace SafeExamBrowser.UserInterface { public partial class SplashScreen : Window, ISplashScreen { - public SplashScreen() + private SplashScreenViewModel model = new SplashScreenViewModel(); + + public SplashScreen(ISettings settings) { InitializeComponent(); + + StatusTextBlock.DataContext = model; + ProgressBar.DataContext = model; + + InfoTextBlock.Inlines.Add(new Run($"Version {settings.ProgramVersion}") { FontStyle = FontStyles.Italic }); + InfoTextBlock.Inlines.Add(new LineBreak()); + InfoTextBlock.Inlines.Add(new LineBreak()); + InfoTextBlock.Inlines.Add(new Run(settings.CopyrightInfo) { FontSize = 10 }); } public void Notify(ILogContent content) { - // TODO + if (content is ILogMessage) + { + model.Status = (content as ILogMessage).Message; + } + } + + public void SetMaxProgress(int max) + { + model.MaxProgress = max; + } + + public void UpdateProgress() + { + model.CurrentProgress += 1; } } } diff --git a/SafeExamBrowser.UserInterface/Taskbar.xaml b/SafeExamBrowser.UserInterface/Taskbar.xaml index df082d53..46fa7371 100644 --- a/SafeExamBrowser.UserInterface/Taskbar.xaml +++ b/SafeExamBrowser.UserInterface/Taskbar.xaml @@ -5,11 +5,16 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface" mc:Ignorable="d" - Title="Taskbar" Height="40" Width="300" WindowStyle="None" AllowsTransparency="True" Topmost="True"> + Title="Taskbar" Height="40" Width="750" WindowStyle="None" AllowsTransparency="True" Topmost="True"> - + + + + + + diff --git a/SafeExamBrowser.UserInterface/ViewModels/SplashScreenViewModel.cs b/SafeExamBrowser.UserInterface/ViewModels/SplashScreenViewModel.cs new file mode 100644 index 00000000..e3d858c2 --- /dev/null +++ b/SafeExamBrowser.UserInterface/ViewModels/SplashScreenViewModel.cs @@ -0,0 +1,60 @@ +/* + * 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 System.ComponentModel; + +namespace SafeExamBrowser.UserInterface.ViewModels +{ + class SplashScreenViewModel : INotifyPropertyChanged + { + private int currentProgress; + private int maxProgress; + private string status; + + public event PropertyChangedEventHandler PropertyChanged; + + public int CurrentProgress + { + get + { + return currentProgress; + } + set + { + currentProgress = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentProgress))); + } + } + + public int MaxProgress + { + get + { + return maxProgress; + } + set + { + maxProgress = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MaxProgress))); + } + } + + public string Status + { + get + { + return status; + } + set + { + status = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status))); + } + } + } +} diff --git a/SafeExamBrowser/App.cs b/SafeExamBrowser/App.cs index 64d61499..4eb077df 100644 --- a/SafeExamBrowser/App.cs +++ b/SafeExamBrowser/App.cs @@ -9,7 +9,6 @@ using System; using System.Threading; using System.Windows; -using SafeExamBrowser.Contracts.I18n; namespace SafeExamBrowser { @@ -17,6 +16,8 @@ namespace SafeExamBrowser { private static readonly Mutex mutex = new Mutex(true, "safe_exam_browser_single_instance_mutex"); + private CompositionRoot instances; + [STAThread] public static void Main() { @@ -32,36 +33,59 @@ namespace SafeExamBrowser private static void StartApplication() { - var root = new CompositionRoot(); - - root.BuildObjectGraph(); - - root.Logger.Log(root.Settings.LogHeader); - root.Logger.Log($"# Application started at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}{Environment.NewLine}"); - - if (NoInstanceRunning(root)) + if (NoInstanceRunning()) { - var app = new App(); - - root.Logger.Info("No instance is running, initiating startup procedure."); - - app.Startup += (o, args) => root.StartupController.InitializeApplication(app.Shutdown); - app.Exit += (o, args) => root.ShutdownController.FinalizeApplication(); - - app.Run(root.Taskbar); + new App().Run(); } else { - root.Logger.Info("Could not start because of an already running instance."); - root.MessageBox.Show(root.Text.Get(Key.MessageBox_SingleInstance), root.Text.Get(Key.MessageBox_SingleInstanceTitle)); + MessageBox.Show("You can only run one instance of SEB at a time.", "Startup Not Allowed"); } - - root.Logger.Log($"# Application terminating normally at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); } - private static bool NoInstanceRunning(CompositionRoot root) + private static bool NoInstanceRunning() { return mutex.WaitOne(TimeSpan.Zero, true); } + + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + + var initializationThread = new Thread(Initialize); + + instances = new CompositionRoot(); + instances.BuildObjectGraph(); + instances.SplashScreen.Show(); + + MainWindow = instances.SplashScreen; + + initializationThread.SetApartmentState(ApartmentState.STA); + initializationThread.Name = "Initialization Thread"; + initializationThread.Start(); + } + + protected override void OnExit(ExitEventArgs e) + { + instances.ShutdownController.FinalizeApplication(); + + base.OnExit(e); + } + + private void Initialize() + { + var success = instances.StartupController.TryInitializeApplication(); + + if (success) + { + instances.Taskbar.Dispatcher.Invoke(() => + { + MainWindow = instances.Taskbar; + MainWindow.Show(); + }); + } + + //instances.SplashScreen.Dispatcher.Invoke(instances.SplashScreen.Close); + } } } diff --git a/SafeExamBrowser/CompositionRoot.cs b/SafeExamBrowser/CompositionRoot.cs index edf88c98..d5ed86fb 100644 --- a/SafeExamBrowser/CompositionRoot.cs +++ b/SafeExamBrowser/CompositionRoot.cs @@ -6,7 +6,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using System.Windows; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; @@ -24,24 +23,24 @@ namespace SafeExamBrowser public IMessageBox MessageBox { get; private set; } public ISettings Settings { get; private set; } public IShutdownController ShutdownController { get; set; } - public ISplashScreen SplashScreen { get; private set; } public IStartupController StartupController { get; private set; } public IText Text { get; private set; } - public Window Taskbar { get; private set; } + public SplashScreen SplashScreen { get; private set; } + public Taskbar Taskbar { get; private set; } public void BuildObjectGraph() { MessageBox = new WpfMessageBox(); Settings = new Settings(); - SplashScreen = new UserInterface.SplashScreen(); Taskbar = new Taskbar(); - Text = new Text(new XmlTextResource()); Logger = new Logger(); Logger.Subscribe(new LogFileWriter(Settings)); + Text = new Text(new XmlTextResource()); + SplashScreen = new SplashScreen(Settings); ShutdownController = new ShutdownController(Logger, MessageBox, Text); - StartupController = new StartupController(Logger, MessageBox, SplashScreen, Text); + StartupController = new StartupController(Logger, MessageBox, Settings, SplashScreen, Taskbar, Text); } } }