Finished first draft of application scaffolding and splash screen.

This commit is contained in:
Damian Büchel 2017-07-10 15:47:12 +02:00
parent 6e44da9497
commit 7bc2686560
24 changed files with 301 additions and 79 deletions

View file

@ -10,7 +10,9 @@ namespace SafeExamBrowser.Contracts.Configuration
{
public interface ISettings
{
string CopyrightInfo { get; }
string LogFolderPath { get; }
string LogHeader { get; }
string ProgramVersion { get; }
}
}

View file

@ -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();
}
}

View file

@ -15,6 +15,6 @@ namespace SafeExamBrowser.Contracts.Logging
DateTime DateTime { get; }
LogLevel Severity { get; }
string Message { get; }
int ThreadId { get; }
IThreadInfo ThreadInfo { get; }
}
}

View file

@ -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; }
}
}

View file

@ -50,6 +50,7 @@
<Compile Include="Logging\ILogMessage.cs" />
<Compile Include="Logging\ILogObserver.cs" />
<Compile Include="Logging\ILogText.cs" />
<Compile Include="Logging\IThreadInfo.cs" />
<Compile Include="Logging\LogLevel.cs" />
<Compile Include="UserInterface\IMessageBox.cs" />
<Compile Include="UserInterface\ISplashScreen.cs" />

View file

@ -12,7 +12,9 @@ namespace SafeExamBrowser.Contracts.UserInterface
{
public interface ISplashScreen : ILogObserver
{
void Show();
void Close();
void SetMaxProgress(int max);
void Show();
void UpdateProgress();
}
}

View file

@ -12,5 +12,6 @@ namespace SafeExamBrowser.Contracts.UserInterface
{
void SetPosition(int x, int y);
void SetSize(int widht, int height);
void Show();
}
}

View file

@ -15,6 +15,17 @@ namespace SafeExamBrowser.Core.Configuration
{
public class Settings : ISettings
{
public string CopyrightInfo
{
get
{
var executable = Assembly.GetEntryAssembly();
var copyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().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<AssemblyInformationalVersionAttribute>().InformationalVersion;
var executable = Assembly.GetEntryAssembly();
var title = executable.GetCustomAttribute<AssemblyTitleAttribute>().Title;
var copyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().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<AssemblyInformationalVersionAttribute>().InformationalVersion;
return version;
}
}
}

View file

@ -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)
{

View file

@ -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;
}
}
}

View file

@ -2,8 +2,6 @@
<Text>
<MessageBox_ShutdownError>An unexpected error occurred during the shutdown procedure! Please consult the application log for more information...</MessageBox_ShutdownError>
<MessageBox_ShutdownErrorTitle>Shutdown Error</MessageBox_ShutdownErrorTitle>
<MessageBox_SingleInstance>You can only run one instance of SEB at a time.</MessageBox_SingleInstance>
<MessageBox_SingleInstanceTitle>Startup Not Allowed</MessageBox_SingleInstanceTitle>
<MessageBox_StartupError>An unexpected error occurred during the startup procedure! Please consult the application log for more information...</MessageBox_StartupError>
<MessageBox_StartupErrorTitle>Startup Error</MessageBox_StartupErrorTitle>
</Text>

View file

@ -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)

View file

@ -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);
}
}
}

View file

@ -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)

View file

@ -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);
}
}
}

View file

@ -49,6 +49,7 @@
<Compile Include="I18n\XmlTextResource.cs" />
<Compile Include="Logging\Logger.cs" />
<Compile Include="Logging\LogText.cs" />
<Compile Include="Logging\ThreadInfo.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -66,6 +66,7 @@
<Compile Include="Taskbar.xaml.cs">
<DependentUpon>Taskbar.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModels\SplashScreenViewModel.cs" />
<Compile Include="WpfMessageBox.cs" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
@ -92,5 +93,8 @@
<Name>SafeExamBrowser.Contracts</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Resource Include="Images\SplashScreen.png" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -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">
<Window.Background>
<SolidColorBrush Color="Black" Opacity="0.8" />
</Window.Background>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="180" />
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Logo etc. goes here..." Foreground="White" />
<ProgressBar Grid.Column="0" Grid.Row="1" Minimum="0" Maximum="100" IsIndeterminate="True" IsEnabled="True" Height="20" Background="#00000000" BorderThickness="0" />
<TextBlock Grid.Column="0" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Text="..." Foreground="White" Background="#00000000" />
</Grid>
<Border BorderBrush="CadetBlue" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="155" />
</Grid.ColumnDefinitions>
<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" />
<TextBlock x:Name="StatusTextBlock" Grid.Row="1" Text="{Binding Path=Status}" FontSize="12" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" />
</Grid>
</Border>
</Window>

View file

@ -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;
}
}
}

View file

@ -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">
<Window.Background>
<SolidColorBrush Color="Black" Opacity="0.8" />
</Window.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="75" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
</Grid>
</Window>

View file

@ -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)));
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}