SEBWIN-312: Removed IApplicationInstance from API as it is irrelevant to the shell (which cares only about applications and their windows).

This commit is contained in:
dbuechel 2019-11-28 17:22:04 +01:00
parent fa9b84ba68
commit 4503cbe778
36 changed files with 374 additions and 488 deletions

View file

@ -11,7 +11,7 @@ using SafeExamBrowser.Core.Contracts;
namespace SafeExamBrowser.Applications.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that the icon of an <see cref="IApplicationInstance"/> has changed.
/// Event handler used to indicate that an icon has changed.
/// </summary>
public delegate void IconChangedEventHandler(IconResource icon);
}

View file

@ -9,7 +9,7 @@
namespace SafeExamBrowser.Applications.Contracts.Events
{
/// <summary>
/// Event handler used to inform about the existence of a new <see cref="IApplicationInstance"/>.
/// Event handler used to indicate that a title has changed to a new value.
/// </summary>
public delegate void InstanceStartedEventHandler(IApplicationInstance instance);
public delegate void TitleChangedEventHandler(string title);
}

View file

@ -9,7 +9,7 @@
namespace SafeExamBrowser.Applications.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that a name has changed to a new value.
/// Event handler used to indicate that the windows of an application have changed.
/// </summary>
public delegate void NameChangedEventHandler(string name);
public delegate void WindowsChangedEventHandler();
}

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Collections.Generic;
using SafeExamBrowser.Applications.Contracts.Events;
namespace SafeExamBrowser.Applications.Contracts
@ -21,9 +22,14 @@ namespace SafeExamBrowser.Applications.Contracts
ApplicationInfo Info { get; }
/// <summary>
/// Fired when a new <see cref="IApplicationInstance"/> has started.
/// Event fired when the windows of the application have changed.
/// </summary>
event InstanceStartedEventHandler InstanceStarted;
event WindowsChangedEventHandler WindowsChanged;
/// <summary>
/// Returns all windows of the application.
/// </summary>
IEnumerable<IApplicationWindow> GetWindows();
/// <summary>
/// Performs any initialization work, if necessary.

View file

@ -1,64 +0,0 @@
/*
* Copyright (c) 2019 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.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts;
namespace SafeExamBrowser.Applications.Contracts
{
/// <summary>
/// Defines an instance of an application which can be accessed via the shell.
/// </summary>
public interface IApplicationInstance
{
/// <summary>
/// The icon resource for this instance.
/// </summary>
IconResource Icon { get; }
/// <summary>
/// The unique identifier for the application instance.
/// </summary>
InstanceIdentifier Id { get; }
/// <summary>
/// The name or document title of the application instance.
/// </summary>
string Name { get; }
/// <summary>
/// Event fired when the icon of the application instance has changed.
/// </summary>
event IconChangedEventHandler IconChanged;
/// <summary>
/// Event fired when the name or (document) title of the application instance has changed.
/// </summary>
event NameChangedEventHandler NameChanged;
/// <summary>
/// Event fired when the application instance has been terminated.
/// </summary>
event InstanceTerminatedEventHandler Terminated;
/// <summary>
/// Makes this instance the currently active one and brings it to the foreground.
/// </summary>
void Activate();
/// <summary>
/// Initializes the application instance.
/// </summary>
void Initialize();
/// <summary>
/// Terminates the application instance.
/// </summary>
void Terminate();
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2019 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.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts;
namespace SafeExamBrowser.Applications.Contracts
{
/// <summary>
/// Defines a window of an <see cref="IApplication"/>.
/// </summary>
public interface IApplicationWindow
{
/// <summary>
/// The icon of the window.
/// </summary>
IconResource Icon { get; }
/// <summary>
/// The title of the window.
/// </summary>
string Title { get; }
/// <summary>
/// Event fired when the icon of the window has changed.
/// </summary>
event IconChangedEventHandler IconChanged;
/// <summary>
/// Event fired when the title of the window has changed.
/// </summary>
event TitleChangedEventHandler TitleChanged;
/// <summary>
/// Brings the window to the foreground and activates it.
/// </summary>
void Activate();
}
}

View file

@ -55,14 +55,13 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Events\IconChangedEventHandler.cs" />
<Compile Include="Events\InstanceStartedEventHandler.cs" />
<Compile Include="Events\InstanceTerminatedEventHandler.cs" />
<Compile Include="Events\NameChangedEventHandler.cs" />
<Compile Include="Events\TitleChangedEventHandler.cs" />
<Compile Include="Events\WindowsChangedEventHandler.cs" />
<Compile Include="FactoryResult.cs" />
<Compile Include="IApplication.cs" />
<Compile Include="IApplicationFactory.cs" />
<Compile Include="ApplicationInfo.cs" />
<Compile Include="IApplicationInstance.cs" />
<Compile Include="IApplicationWindow.cs" />
<Compile Include="InstanceIdentifier.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View file

@ -20,22 +20,27 @@ namespace SafeExamBrowser.Applications
{
private string executablePath;
private IModuleLogger logger;
private IList<IApplicationInstance> instances;
private IList<ExternalApplicationInstance> instances;
private IProcessFactory processFactory;
public ApplicationInfo Info { get; }
public event WindowsChangedEventHandler WindowsChanged;
public event InstanceStartedEventHandler InstanceStarted;
public ApplicationInfo Info { get; }
internal ExternalApplication(string executablePath, ApplicationInfo info, IModuleLogger logger, IProcessFactory processFactory)
{
this.executablePath = executablePath;
this.Info = info;
this.logger = logger;
this.instances = new List<IApplicationInstance>();
this.instances = new List<ExternalApplicationInstance>();
this.processFactory = processFactory;
}
public IEnumerable<IApplicationWindow> GetWindows()
{
return Enumerable.Empty<IApplicationWindow>();
}
public void Initialize()
{
// Nothing to do here for now.
@ -53,7 +58,6 @@ namespace SafeExamBrowser.Applications
instance.Initialize();
instances.Add(instance);
InstanceStarted?.Invoke(instance);
}
catch (Exception e)
{

View file

@ -6,57 +6,34 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Timers;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
internal class ExternalApplicationInstance : IApplicationInstance
internal class ExternalApplicationInstance
{
private const int ONE_SECOND = 1000;
private IconResource icon;
private InstanceIdentifier id;
private ILogger logger;
private IProcess process;
private Timer timer;
public IconResource Icon { get; }
public InstanceIdentifier Id { get; }
public string Name { get; private set; }
public event IconChangedEventHandler IconChanged { add { } remove { } }
public event NameChangedEventHandler NameChanged;
public event InstanceTerminatedEventHandler Terminated;
public ExternalApplicationInstance(IconResource icon, InstanceIdentifier id, ILogger logger, IProcess process)
internal ExternalApplicationInstance(IconResource icon, InstanceIdentifier id, ILogger logger, IProcess process)
{
this.Icon = icon;
this.Id = id;
this.icon = icon;
this.id = id;
this.logger = logger;
this.process = process;
}
public void Activate()
internal void Initialize()
{
var success = process.TryActivate();
if (!success)
{
logger.Warn("Failed to activate instance!");
}
process.Terminated += Process_Terminated;
}
public void Initialize()
{
InitializeEvents();
logger.Info("Initialized application instance.");
}
public void Terminate()
internal void Terminate()
{
const int MAX_ATTEMPTS = 5;
const int TIMEOUT_MS = 500;
@ -65,7 +42,7 @@ namespace SafeExamBrowser.Applications
if (!terminated)
{
FinalizeEvents();
process.Terminated -= Process_Terminated;
for (var attempt = 0; attempt < MAX_ATTEMPTS && !terminated; attempt++)
{
@ -91,37 +68,7 @@ namespace SafeExamBrowser.Applications
private void Process_Terminated(int exitCode)
{
logger.Info($"Application instance has terminated with exit code {exitCode}.");
FinalizeEvents();
Terminated?.Invoke(Id);
}
private void InitializeEvents()
{
timer = new Timer(ONE_SECOND);
timer.Elapsed += Timer_Elapsed;
timer.Start();
process.Terminated += Process_Terminated;
}
private void FinalizeEvents()
{
timer.Elapsed -= Timer_Elapsed;
timer.Stop();
process.Terminated -= Process_Terminated;
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
var success = process.TryGetWindowTitle(out var title);
var hasChanged = Name?.Equals(title, StringComparison.Ordinal) != true;
if (success && hasChanged)
{
Name = title;
NameChanged?.Invoke(Name);
}
timer.Start();
// TODO: Terminated?.Invoke(Id); -> Remove from application!
}
}
}

View file

@ -41,7 +41,7 @@ namespace SafeExamBrowser.Browser
public ApplicationInfo Info { get; private set; }
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event InstanceStartedEventHandler InstanceStarted;
public event WindowsChangedEventHandler WindowsChanged;
public BrowserApplication(
AppConfig appConfig,
@ -60,6 +60,11 @@ namespace SafeExamBrowser.Browser
this.uiFactory = uiFactory;
}
public IEnumerable<IApplicationWindow> GetWindows()
{
return new List<IApplicationWindow>(instances);
}
public void Initialize()
{
var cefSettings = InitializeCefSettings();
@ -119,9 +124,9 @@ namespace SafeExamBrowser.Browser
instance.Initialize();
instances.Add(instance);
InstanceStarted?.Invoke(instance);
logger.Info($"Created browser instance {instance.Id}.");
WindowsChanged?.Invoke();
}
private CefSettings InitializeCefSettings()
@ -156,6 +161,7 @@ namespace SafeExamBrowser.Browser
{
instances.Remove(instances.FirstOrDefault(i => i.Id == id));
logger.Info($"Browser instance {id} was terminated.");
WindowsChanged?.Invoke();
}
/// <summary>

View file

@ -27,7 +27,7 @@ using SafeExamBrowser.UserInterface.Contracts.MessageBox;
namespace SafeExamBrowser.Browser
{
internal class BrowserApplicationInstance : IApplicationInstance
internal class BrowserApplicationInstance : IApplicationWindow
{
private const double ZOOM_FACTOR = 0.2;
@ -49,20 +49,22 @@ namespace SafeExamBrowser.Browser
get { return isMainInstance ? settings.MainWindow : settings.AdditionalWindow; }
}
public IconResource Icon { get; private set; }
public InstanceIdentifier Id { get; private set; }
public string Name { get; private set; }
internal BrowserInstanceIdentifier Id { get; private set; }
public IconResource Icon { get; private set; }
public string Title { get; private set; }
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
internal event PopupRequestedEventHandler PopupRequested;
internal event InstanceTerminatedEventHandler Terminated;
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event IconChangedEventHandler IconChanged;
public event NameChangedEventHandler NameChanged;
public event PopupRequestedEventHandler PopupRequested;
public event InstanceTerminatedEventHandler Terminated;
public event TitleChangedEventHandler TitleChanged;
public BrowserApplicationInstance(
AppConfig appConfig,
BrowserSettings settings,
InstanceIdentifier id,
BrowserInstanceIdentifier id,
bool isMainInstance,
IMessageBox messageBox,
IModuleLogger logger,
@ -84,18 +86,18 @@ namespace SafeExamBrowser.Browser
public void Activate()
{
window?.BringToForeground();
window.BringToForeground();
}
public void Initialize()
internal void Initialize()
{
InitializeControl();
InitializeWindow();
}
public void Terminate()
internal void Terminate()
{
window?.Close();
window.Close();
}
private void InitializeControl()
@ -194,9 +196,9 @@ namespace SafeExamBrowser.Browser
private void Control_TitleChanged(string title)
{
Name = title;
window.UpdateTitle(Name);
NameChanged?.Invoke(Name);
Title = title;
window.UpdateTitle(Title);
TitleChanged?.Invoke(Title);
}
private void DisplayHandler_FaviconChanged(string uri)
@ -267,7 +269,7 @@ namespace SafeExamBrowser.Browser
if (url == this.url)
{
window.UpdateTitle($"*** {title} ***");
NameChanged?.Invoke($"*** {title} ***");
TitleChanged?.Invoke($"*** {title} ***");
}
messageBox.Show(message, title, parent: window);

View file

@ -6,12 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Core.Contracts;
namespace SafeExamBrowser.Applications.Contracts.Events
namespace SafeExamBrowser.Browser.Events
{
/// <summary>
/// Event handler used to indicate that an <see cref="IApplicationInstance"/> has terminated.
/// </summary>
public delegate void InstanceTerminatedEventHandler(InstanceIdentifier id);
internal delegate void InstanceTerminatedEventHandler(BrowserInstanceIdentifier id);
}

View file

@ -65,6 +65,7 @@
<Compile Include="BrowserApplicationInstance.cs" />
<Compile Include="BrowserInstanceIdentifier.cs" />
<Compile Include="Events\FaviconChangedEventHandler.cs" />
<Compile Include="Events\InstanceTerminatedEventHandler.cs" />
<Compile Include="Events\PopupRequestedEventArgs.cs" />
<Compile Include="Events\PopupRequestedEventHandler.cs" />
<Compile Include="Events\ProgressChangedEventHandler.cs" />

View file

@ -17,14 +17,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
public partial class ActionCenterApplicationButton : UserControl
{
private ApplicationInfo info;
private IApplicationInstance instance;
private IApplicationWindow window;
internal event EventHandler Clicked;
public ActionCenterApplicationButton(ApplicationInfo info, IApplicationInstance instance = null)
public ActionCenterApplicationButton(ApplicationInfo info, IApplicationWindow window = null)
{
this.info = info;
this.instance = instance;
this.window = window;
InitializeComponent();
InitializeApplicationInstanceButton();
@ -33,28 +33,28 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
private void InitializeApplicationInstanceButton()
{
Icon.Content = IconResourceLoader.Load(info.Icon);
Text.Text = instance?.Name ?? info.Name;
Text.Text = window?.Title ?? info.Name;
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
Button.ToolTip = instance?.Name ?? info.Tooltip;
Button.ToolTip = window?.Title ?? info.Tooltip;
if (instance != null)
if (window != null)
{
instance.IconChanged += Instance_IconChanged;
instance.NameChanged += Instance_NameChanged;
window.IconChanged += Window_IconChanged;
window.TitleChanged += Window_TitleChanged;
}
}
private void Instance_IconChanged(IconResource icon)
private void Window_IconChanged(IconResource icon)
{
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
}
private void Instance_NameChanged(string name)
private void Window_TitleChanged(string title)
{
Dispatcher.InvokeAsync(() =>
{
Text.Text = name;
Button.ToolTip = name;
Text.Text = title;
Button.ToolTip = title;
});
}
}

View file

@ -22,6 +22,6 @@
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="ApplicationName" Background="{StaticResource BackgroundTransparentEmphasisBrush}" FontWeight="Bold" Padding="5" TextAlignment="Center" />
<ContentControl Grid.Row="1" x:Name="ApplicationButton" FontWeight="Bold" />
<StackPanel Grid.Row="2" x:Name="InstancePanel" Background="{StaticResource BackgroundTransparentEmphasisBrush}" Orientation="Vertical" />
<StackPanel Grid.Row="2" x:Name="WindowPanel" Background="{StaticResource BackgroundTransparentEmphasisBrush}" Orientation="Vertical" />
</Grid>
</UserControl>

View file

@ -29,40 +29,42 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
var button = new ActionCenterApplicationButton(application.Info);
application.InstanceStarted += Application_InstanceStarted;
application.WindowsChanged += Application_WindowsChanged;
button.Clicked += (o, args) => application.Start();
ApplicationName.Text = application.Info.Name;
ApplicationName.Visibility = Visibility.Collapsed;
ApplicationButton.Content = button;
}
private void Application_InstanceStarted(IApplicationInstance instance)
private void Application_WindowsChanged()
{
Dispatcher.InvokeAsync(() =>
{
var button = new ActionCenterApplicationButton(application.Info, instance);
button.Clicked += (o, args) => instance.Activate();
instance.Terminated += (_) => RemoveInstance(button);
InstancePanel.Children.Add(button);
ApplicationName.Visibility = Visibility.Visible;
ApplicationButton.Visibility = Visibility.Collapsed;
});
Dispatcher.InvokeAsync(Update);
}
private void RemoveInstance(ActionCenterApplicationButton button)
private void Update()
{
Dispatcher.InvokeAsync(() =>
{
InstancePanel.Children.Remove(button);
var windows = application.GetWindows();
if (InstancePanel.Children.Count == 0)
{
ApplicationName.Visibility = Visibility.Collapsed;
ApplicationButton.Visibility = Visibility.Visible;
}
});
WindowPanel.Children.Clear();
foreach (var window in windows)
{
var button = new ActionCenterApplicationButton(application.Info, window);
button.Clicked += (o, args) => window.Activate();
WindowPanel.Children.Add(button);
}
if (WindowPanel.Children.Count == 0)
{
ApplicationName.Visibility = Visibility.Collapsed;
ApplicationButton.Visibility = Visibility.Visible;
}
else
{
ApplicationName.Visibility = Visibility.Visible;
ApplicationButton.Visibility = Visibility.Collapsed;
}
}
}
}

View file

@ -1,4 +1,4 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.TaskViewInstanceControl"
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.TaskViewWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View file

@ -14,15 +14,13 @@ using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
public partial class TaskViewInstanceControl : UserControl
public partial class TaskViewWindowControl : UserControl
{
private IApplicationInstance instance;
private IApplicationWindow window;
internal InstanceIdentifier Id => instance.Id;
public TaskViewInstanceControl(IApplicationInstance instance)
public TaskViewWindowControl(IApplicationWindow window)
{
this.instance = instance;
this.window = window;
InitializeComponent();
InitializeControl();
@ -30,7 +28,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
internal void Activate()
{
instance.Activate();
window.Activate();
}
internal void Deselect()
@ -49,21 +47,21 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
private void InitializeControl()
{
Icon.Content = IconResourceLoader.Load(instance.Icon);
Title.Text = instance.Name;
Icon.Content = IconResourceLoader.Load(window.Icon);
Title.Text = window.Title;
instance.IconChanged += Instance_IconChanged;
instance.NameChanged += Instance_NameChanged;
window.IconChanged += Instance_IconChanged;
window.TitleChanged += Instance_TitleChanged;
}
private void Instance_NameChanged(string name)
private void Instance_TitleChanged(string title)
{
Dispatcher.InvokeAsync(() => Title.Text = name);
Dispatcher.InvokeAsync(() => Title.Text = title);
}
private void Instance_IconChanged(IconResource icon)
{
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(instance.Icon));
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(window.Icon));
}
}
}

View file

@ -16,10 +16,10 @@
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Popup x:Name="InstancePopup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
<Popup x:Name="WindowPopup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.75,0.75,0.75,0">
<ScrollViewer x:Name="InstanceScrollViewer" MaxHeight="400" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
<StackPanel x:Name="InstanceStackPanel" />
<ScrollViewer MaxHeight="400" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
<StackPanel x:Name="WindowStackPanel" />
</ScrollViewer>
</Border>
</Popup>

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@ -20,7 +21,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
public partial class TaskbarApplicationControl : UserControl, IApplicationControl
{
private IApplication application;
private IApplicationInstance single;
private IApplicationWindow single;
public TaskbarApplicationControl(IApplication application)
{
@ -34,71 +35,64 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
var originalBrush = Button.Background;
application.InstanceStarted += Application_InstanceStarted;
application.WindowsChanged += Application_WindowsChanged;
Button.Click += Button_Click;
Button.Content = IconResourceLoader.Load(application.Info.Icon);
Button.MouseEnter += (o, args) => InstancePopup.IsOpen = InstanceStackPanel.Children.Count > 1;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => InstancePopup.IsOpen = InstancePopup.IsMouseOver));
Button.MouseEnter += (o, args) => WindowPopup.IsOpen = WindowStackPanel.Children.Count > 0;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = WindowPopup.IsMouseOver));
Button.ToolTip = application.Info.Tooltip;
InstancePopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => InstancePopup.IsOpen = IsMouseOver));
WindowPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = IsMouseOver));
InstancePopup.Opened += (o, args) =>
WindowPopup.Opened += (o, args) =>
{
Background = Brushes.LightGray;
Button.Background = Brushes.LightGray;
};
InstancePopup.Closed += (o, args) =>
WindowPopup.Closed += (o, args) =>
{
Background = originalBrush;
Button.Background = originalBrush;
};
}
private void Application_InstanceStarted(IApplicationInstance instance)
private void Application_WindowsChanged()
{
Dispatcher.Invoke(() =>
{
var button = new TaskbarApplicationInstanceButton(instance, application.Info);
instance.Terminated += (_) => RemoveInstance(button);
InstanceStackPanel.Children.Add(button);
if (single == default(IApplicationInstance))
{
single = instance;
}
});
Dispatcher.InvokeAsync(Update);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (InstanceStackPanel.Children.Count == 0)
if (WindowStackPanel.Children.Count == 0)
{
application.Start();
}
else if (InstanceStackPanel.Children.Count == 1)
else if (WindowStackPanel.Children.Count == 1)
{
single.Activate();
single?.Activate();
}
}
private void Update()
{
var windows = application.GetWindows();
WindowStackPanel.Children.Clear();
foreach (var window in windows)
{
WindowStackPanel.Children.Add(new TaskbarApplicationWindowButton(window));
}
if (WindowStackPanel.Children.Count == 1)
{
single = windows.First();
}
else
{
InstancePopup.IsOpen = true;
single = default(IApplicationWindow);
}
}
private void RemoveInstance(TaskbarApplicationInstanceButton button)
{
Dispatcher.InvokeAsync(() =>
{
InstanceStackPanel.Children.Remove(button);
if (InstanceStackPanel.Children.Count == 0)
{
single = default(IApplicationInstance);
}
});
}
}
}

View file

@ -1,4 +1,4 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.TaskbarApplicationInstanceButton"
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.TaskbarApplicationWindowButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View file

@ -14,15 +14,13 @@ using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Desktop.Controls
{
public partial class TaskbarApplicationInstanceButton : UserControl
public partial class TaskbarApplicationWindowButton : UserControl
{
private ApplicationInfo info;
private IApplicationInstance instance;
private IApplicationWindow window;
public TaskbarApplicationInstanceButton(IApplicationInstance instance, ApplicationInfo info)
public TaskbarApplicationWindowButton(IApplicationWindow window)
{
this.info = info;
this.instance = instance;
this.window = window;
InitializeComponent();
InitializeApplicationInstanceButton();
@ -31,16 +29,16 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
private void InitializeApplicationInstanceButton()
{
Button.Click += Button_Click;
Button.ToolTip = instance.Name;
Icon.Content = IconResourceLoader.Load(info.Icon);
instance.IconChanged += Instance_IconChanged;
instance.NameChanged += Instance_NameChanged;
Text.Text = instance.Name;
Button.ToolTip = window.Title;
Icon.Content = IconResourceLoader.Load(window.Icon);
window.IconChanged += Instance_IconChanged;
window.TitleChanged += Window_TitleChanged;
Text.Text = window.Title;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
instance.Activate();
window.Activate();
}
private void Instance_IconChanged(IconResource icon)
@ -48,12 +46,12 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
}
private void Instance_NameChanged(string name)
private void Window_TitleChanged(string title)
{
Dispatcher.Invoke(() =>
Dispatcher.InvokeAsync(() =>
{
Text.Text = name;
Button.ToolTip = name;
Text.Text = title;
Button.ToolTip = title;
});
}
}

View file

@ -112,8 +112,8 @@
<Compile Include="Controls\TaskbarApplicationControl.xaml.cs">
<DependentUpon>TaskbarApplicationControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarApplicationInstanceButton.xaml.cs">
<DependentUpon>TaskbarApplicationInstanceButton.xaml</DependentUpon>
<Compile Include="Controls\TaskbarApplicationWindowButton.xaml.cs">
<DependentUpon>TaskbarApplicationWindowButton.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarAudioControl.xaml.cs">
<DependentUpon>TaskbarAudioControl.xaml</DependentUpon>
@ -142,8 +142,8 @@
<Compile Include="Controls\TaskbarWirelessNetworkControl.xaml.cs">
<DependentUpon>TaskbarWirelessNetworkControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskViewInstanceControl.xaml.cs">
<DependentUpon>TaskViewInstanceControl.xaml</DependentUpon>
<Compile Include="Controls\TaskViewWindowControl.xaml.cs">
<DependentUpon>TaskViewWindowControl.xaml</DependentUpon>
</Compile>
<Compile Include="LockScreen.xaml.cs">
<DependentUpon>LockScreen.xaml</DependentUpon>
@ -229,7 +229,7 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\TaskbarApplicationInstanceButton.xaml">
<Page Include="Controls\TaskbarApplicationWindowButton.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@ -317,7 +317,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Page Include="Controls\TaskViewInstanceControl.xaml">
<Page Include="Controls\TaskViewWindowControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>

View file

@ -19,21 +19,22 @@ namespace SafeExamBrowser.UserInterface.Desktop
{
public partial class TaskView : Window, ITaskView
{
private LinkedListNode<TaskViewInstanceControl> current;
private LinkedList<TaskViewInstanceControl> controls;
private List<IApplicationInstance> instances;
private IList<IApplication> applications;
private LinkedListNode<TaskViewWindowControl> current;
private LinkedList<TaskViewWindowControl> controls;
public TaskView()
{
controls = new LinkedList<TaskViewInstanceControl>();
instances = new List<IApplicationInstance>();
applications = new List<IApplication>();
controls = new LinkedList<TaskViewWindowControl>();
InitializeComponent();
}
public void Add(IApplication application)
{
application.InstanceStarted += Application_InstanceStarted;
application.WindowsChanged += Application_WindowsChanged;
applications.Add(application);
}
public void Register(ITaskViewActivator activator)
@ -43,9 +44,9 @@ namespace SafeExamBrowser.UserInterface.Desktop
activator.PreviousActivated += Activator_Previous;
}
private void Application_InstanceStarted(IApplicationInstance instance)
private void Application_WindowsChanged()
{
Dispatcher.InvokeAsync(() => Add(instance));
Dispatcher.InvokeAsync(Update);
}
private void Activator_Deactivated()
@ -63,11 +64,6 @@ namespace SafeExamBrowser.UserInterface.Desktop
Dispatcher.InvokeAsync(SelectPrevious);
}
private void Instance_Terminated(InstanceIdentifier id)
{
Dispatcher.InvokeAsync(() => Remove(id));
}
private void ActivateAndHide()
{
Activate();
@ -75,24 +71,6 @@ namespace SafeExamBrowser.UserInterface.Desktop
Hide();
}
private void Add(IApplicationInstance instance)
{
instance.Terminated += Instance_Terminated;
instances.Add(instance);
Update();
}
private void Remove(InstanceIdentifier id)
{
var instance = instances.FirstOrDefault(i => i.Id == id);
if (instance != default(IApplicationInstance))
{
instances.Remove(instance);
Update();
}
}
private void SelectNext()
{
ShowConditional();
@ -119,7 +97,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
private void ShowConditional()
{
if (instances.Any() && Visibility != Visibility.Visible)
if (controls.Any() && Visibility != Visibility.Visible)
{
Show();
Activate();
@ -128,22 +106,31 @@ namespace SafeExamBrowser.UserInterface.Desktop
private void Update()
{
var max = Math.Ceiling(Math.Sqrt(instances.Count));
var stack = new Stack<IApplicationInstance>(instances);
var windows = new Stack<IApplicationWindow>();
foreach (var application in applications)
{
foreach (var window in application.GetWindows())
{
windows.Push(window);
}
}
var max = Math.Ceiling(Math.Sqrt(windows.Count));
controls.Clear();
Rows.Children.Clear();
for (var rowCount = 0; rowCount < max && stack.Any(); rowCount++)
for (var rowCount = 0; rowCount < max && windows.Any(); rowCount++)
{
var row = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
Rows.Children.Add(row);
for (var columnIndex = 0; columnIndex < max && stack.Any(); columnIndex++)
for (var columnIndex = 0; columnIndex < max && windows.Any(); columnIndex++)
{
var instance = stack.Pop();
var control = new TaskViewInstanceControl(instance);
var window = windows.Pop();
var control = new TaskViewWindowControl(window);
controls.AddLast(control);
row.Children.Add(control);
@ -158,7 +145,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left;
Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top;
if (!instances.Any())
if (!windows.Any())
{
Hide();
}

View file

@ -17,14 +17,14 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
public partial class ActionCenterApplicationButton : UserControl
{
private ApplicationInfo info;
private IApplicationInstance instance;
private IApplicationWindow window;
internal event EventHandler Clicked;
public ActionCenterApplicationButton(ApplicationInfo info, IApplicationInstance instance = null)
public ActionCenterApplicationButton(ApplicationInfo info, IApplicationWindow window = null)
{
this.info = info;
this.instance = instance;
this.window = window;
InitializeComponent();
InitializeApplicationInstanceButton();
@ -33,28 +33,28 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
private void InitializeApplicationInstanceButton()
{
Icon.Content = IconResourceLoader.Load(info.Icon);
Text.Text = instance?.Name ?? info.Name;
Text.Text = window?.Title ?? info.Name;
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
Button.ToolTip = instance?.Name ?? info.Tooltip;
Button.ToolTip = window?.Title ?? info.Tooltip;
if (instance != null)
if (window != null)
{
instance.IconChanged += Instance_IconChanged;
instance.NameChanged += Instance_NameChanged;
window.IconChanged += Window_IconChanged;
window.TitleChanged += Window_TitleChanged;
}
}
private void Instance_IconChanged(IconResource icon)
private void Window_IconChanged(IconResource icon)
{
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
}
private void Instance_NameChanged(string name)
private void Window_TitleChanged(string title)
{
Dispatcher.InvokeAsync(() =>
{
Text.Text = name;
Button.ToolTip = name;
Text.Text = title;
Button.ToolTip = title;
});
}
}

View file

@ -22,6 +22,6 @@
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="ApplicationName" Background="{StaticResource BackgroundTransparentEmphasisBrush}" FontWeight="Bold" Padding="5" TextAlignment="Center" />
<ContentControl Grid.Row="1" x:Name="ApplicationButton" FontWeight="Bold" />
<StackPanel Grid.Row="2" x:Name="InstancePanel" Background="{StaticResource BackgroundTransparentEmphasisBrush}" Orientation="Vertical" />
<StackPanel Grid.Row="2" x:Name="WindowPanel" Background="{StaticResource BackgroundTransparentEmphasisBrush}" Orientation="Vertical" />
</Grid>
</UserControl>

View file

@ -29,40 +29,42 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
{
var button = new ActionCenterApplicationButton(application.Info);
application.InstanceStarted += Application_InstanceStarted;
application.WindowsChanged += Application_WindowsChanged;
button.Clicked += (o, args) => application.Start();
ApplicationName.Text = application.Info.Name;
ApplicationName.Visibility = Visibility.Collapsed;
ApplicationButton.Content = button;
}
private void Application_InstanceStarted(IApplicationInstance instance)
private void Application_WindowsChanged()
{
Dispatcher.InvokeAsync(() =>
{
var button = new ActionCenterApplicationButton(application.Info, instance);
button.Clicked += (o, args) => instance.Activate();
instance.Terminated += (_) => RemoveInstance(button);
InstancePanel.Children.Add(button);
ApplicationName.Visibility = Visibility.Visible;
ApplicationButton.Visibility = Visibility.Collapsed;
});
Dispatcher.InvokeAsync(Update);
}
private void RemoveInstance(ActionCenterApplicationButton button)
private void Update()
{
Dispatcher.InvokeAsync(() =>
{
InstancePanel.Children.Remove(button);
var windows = application.GetWindows();
if (InstancePanel.Children.Count == 0)
{
ApplicationName.Visibility = Visibility.Collapsed;
ApplicationButton.Visibility = Visibility.Visible;
}
});
WindowPanel.Children.Clear();
foreach (var window in windows)
{
var button = new ActionCenterApplicationButton(application.Info, window);
button.Clicked += (o, args) => window.Activate();
WindowPanel.Children.Add(button);
}
if (WindowPanel.Children.Count == 0)
{
ApplicationName.Visibility = Visibility.Collapsed;
ApplicationButton.Visibility = Visibility.Visible;
}
else
{
ApplicationName.Visibility = Visibility.Visible;
ApplicationButton.Visibility = Visibility.Collapsed;
}
}
}
}

View file

@ -1,4 +1,4 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Mobile.Controls.TaskViewInstanceControl"
<UserControl x:Class="SafeExamBrowser.UserInterface.Mobile.Controls.TaskViewWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View file

@ -14,15 +14,13 @@ using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Mobile.Controls
{
public partial class TaskViewInstanceControl : UserControl
public partial class TaskViewWindowControl : UserControl
{
private IApplicationInstance instance;
private IApplicationWindow window;
internal InstanceIdentifier Id => instance.Id;
public TaskViewInstanceControl(IApplicationInstance instance)
public TaskViewWindowControl(IApplicationWindow window)
{
this.instance = instance;
this.window = window;
InitializeComponent();
InitializeControl();
@ -30,7 +28,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
internal void Activate()
{
instance.Activate();
window.Activate();
}
internal void Deselect()
@ -49,21 +47,21 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
private void InitializeControl()
{
Icon.Content = IconResourceLoader.Load(instance.Icon);
Title.Text = instance.Name;
Icon.Content = IconResourceLoader.Load(window.Icon);
Title.Text = window.Title;
instance.IconChanged += Instance_IconChanged;
instance.NameChanged += Instance_NameChanged;
window.IconChanged += Instance_IconChanged;
window.TitleChanged += Instance_TitleChanged;
}
private void Instance_NameChanged(string name)
private void Instance_TitleChanged(string title)
{
Dispatcher.InvokeAsync(() => Title.Text = name);
Dispatcher.InvokeAsync(() => Title.Text = title);
}
private void Instance_IconChanged(IconResource icon)
{
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(instance.Icon));
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(window.Icon));
}
}
}

View file

@ -16,10 +16,10 @@
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Popup x:Name="InstancePopup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
<Popup x:Name="WindowPopup" IsOpen="False" Placement="Top" PlacementTarget="{Binding ElementName=Button}">
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.75,0.75,0.75,0">
<ScrollViewer x:Name="InstanceScrollViewer" MaxHeight="400" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
<StackPanel x:Name="InstanceStackPanel" />
<ScrollViewer MaxHeight="400" VerticalScrollBarVisibility="Auto" Template="{StaticResource SmallBarScrollViewer}">
<StackPanel x:Name="WindowStackPanel" />
</ScrollViewer>
</Border>
</Popup>

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@ -20,7 +21,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
public partial class TaskbarApplicationControl : UserControl, IApplicationControl
{
private IApplication application;
private IApplicationInstance single;
private IApplicationWindow single;
public TaskbarApplicationControl(IApplication application)
{
@ -34,71 +35,64 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
{
var originalBrush = Button.Background;
application.InstanceStarted += Application_InstanceStarted;
application.WindowsChanged += Application_WindowsChanged;
Button.Click += Button_Click;
Button.Content = IconResourceLoader.Load(application.Info.Icon);
Button.MouseEnter += (o, args) => InstancePopup.IsOpen = InstanceStackPanel.Children.Count > 1;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => InstancePopup.IsOpen = InstancePopup.IsMouseOver));
Button.MouseEnter += (o, args) => WindowPopup.IsOpen = WindowStackPanel.Children.Count > 0;
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = WindowPopup.IsMouseOver));
Button.ToolTip = application.Info.Tooltip;
InstancePopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => InstancePopup.IsOpen = IsMouseOver));
WindowPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = IsMouseOver));
InstancePopup.Opened += (o, args) =>
WindowPopup.Opened += (o, args) =>
{
Background = Brushes.LightGray;
Button.Background = Brushes.LightGray;
};
InstancePopup.Closed += (o, args) =>
WindowPopup.Closed += (o, args) =>
{
Background = originalBrush;
Button.Background = originalBrush;
};
}
private void Application_InstanceStarted(IApplicationInstance instance)
private void Application_WindowsChanged()
{
Dispatcher.Invoke(() =>
{
var button = new TaskbarApplicationInstanceButton(instance, application.Info);
instance.Terminated += (_) => RemoveInstance(button);
InstanceStackPanel.Children.Add(button);
if (single == default(IApplicationInstance))
{
single = instance;
}
});
Dispatcher.InvokeAsync(Update);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (InstanceStackPanel.Children.Count == 0)
if (WindowStackPanel.Children.Count == 0)
{
application.Start();
}
else if (InstanceStackPanel.Children.Count == 1)
else if (WindowStackPanel.Children.Count == 1)
{
single.Activate();
single?.Activate();
}
}
private void Update()
{
var windows = application.GetWindows();
WindowStackPanel.Children.Clear();
foreach (var window in windows)
{
WindowStackPanel.Children.Add(new TaskbarApplicationWindowButton(window));
}
if (WindowStackPanel.Children.Count == 1)
{
single = windows.First();
}
else
{
InstancePopup.IsOpen = true;
single = default(IApplicationWindow);
}
}
private void RemoveInstance(TaskbarApplicationInstanceButton button)
{
Dispatcher.InvokeAsync(() =>
{
InstanceStackPanel.Children.Remove(button);
if (InstanceStackPanel.Children.Count == 0)
{
single = default(IApplicationInstance);
}
});
}
}
}

View file

@ -1,4 +1,4 @@
<UserControl x:Class="SafeExamBrowser.UserInterface.Mobile.Controls.TaskbarApplicationInstanceButton"
<UserControl x:Class="SafeExamBrowser.UserInterface.Mobile.Controls.TaskbarApplicationWindowButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View file

@ -14,15 +14,13 @@ using SafeExamBrowser.UserInterface.Shared.Utilities;
namespace SafeExamBrowser.UserInterface.Mobile.Controls
{
public partial class TaskbarApplicationInstanceButton : UserControl
public partial class TaskbarApplicationWindowButton : UserControl
{
private ApplicationInfo info;
private IApplicationInstance instance;
private IApplicationWindow window;
public TaskbarApplicationInstanceButton(IApplicationInstance instance, ApplicationInfo info)
public TaskbarApplicationWindowButton(IApplicationWindow window)
{
this.info = info;
this.instance = instance;
this.window = window;
InitializeComponent();
InitializeApplicationInstanceButton();
@ -31,16 +29,16 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
private void InitializeApplicationInstanceButton()
{
Button.Click += Button_Click;
Button.ToolTip = instance.Name;
Icon.Content = IconResourceLoader.Load(info.Icon);
instance.IconChanged += Instance_IconChanged;
instance.NameChanged += Instance_NameChanged;
Text.Text = instance.Name;
Button.ToolTip = window.Title;
Icon.Content = IconResourceLoader.Load(window.Icon);
window.IconChanged += Instance_IconChanged;
window.TitleChanged += Window_TitleChanged;
Text.Text = window.Title;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
instance.Activate();
window.Activate();
}
private void Instance_IconChanged(IconResource icon)
@ -48,12 +46,12 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(icon));
}
private void Instance_NameChanged(string name)
private void Window_TitleChanged(string title)
{
Dispatcher.Invoke(() =>
Dispatcher.InvokeAsync(() =>
{
Text.Text = name;
Button.ToolTip = name;
Text.Text = title;
Button.ToolTip = title;
});
}
}

View file

@ -113,8 +113,8 @@
<Compile Include="Controls\TaskbarApplicationControl.xaml.cs">
<DependentUpon>TaskbarApplicationControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarApplicationInstanceButton.xaml.cs">
<DependentUpon>TaskbarApplicationInstanceButton.xaml</DependentUpon>
<Compile Include="Controls\TaskbarApplicationWindowButton.xaml.cs">
<DependentUpon>TaskbarApplicationWindowButton.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarAudioControl.xaml.cs">
<DependentUpon>TaskbarAudioControl.xaml</DependentUpon>
@ -143,8 +143,8 @@
<Compile Include="Controls\TaskbarWirelessNetworkControl.xaml.cs">
<DependentUpon>TaskbarWirelessNetworkControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskViewInstanceControl.xaml.cs">
<DependentUpon>TaskViewInstanceControl.xaml</DependentUpon>
<Compile Include="Controls\TaskViewWindowControl.xaml.cs">
<DependentUpon>TaskViewWindowControl.xaml</DependentUpon>
</Compile>
<Compile Include="LockScreen.xaml.cs">
<DependentUpon>LockScreen.xaml</DependentUpon>
@ -283,7 +283,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Controls\TaskbarApplicationInstanceButton.xaml">
<Page Include="Controls\TaskbarApplicationWindowButton.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
@ -359,7 +359,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Page Include="Controls\TaskViewInstanceControl.xaml">
<Page Include="Controls\TaskViewWindowControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>

View file

@ -19,21 +19,22 @@ namespace SafeExamBrowser.UserInterface.Mobile
{
public partial class TaskView : Window, ITaskView
{
private LinkedListNode<TaskViewInstanceControl> current;
private LinkedList<TaskViewInstanceControl> controls;
private List<IApplicationInstance> instances;
private IList<IApplication> applications;
private LinkedListNode<TaskViewWindowControl> current;
private LinkedList<TaskViewWindowControl> controls;
public TaskView()
{
controls = new LinkedList<TaskViewInstanceControl>();
instances = new List<IApplicationInstance>();
applications = new List<IApplication>();
controls = new LinkedList<TaskViewWindowControl>();
InitializeComponent();
}
public void Add(IApplication application)
{
application.InstanceStarted += Application_InstanceStarted;
application.WindowsChanged += Application_WindowsChanged;
applications.Add(application);
}
public void Register(ITaskViewActivator activator)
@ -43,9 +44,9 @@ namespace SafeExamBrowser.UserInterface.Mobile
activator.PreviousActivated += Activator_Previous;
}
private void Application_InstanceStarted(IApplicationInstance instance)
private void Application_WindowsChanged()
{
Dispatcher.InvokeAsync(() => Add(instance));
Dispatcher.InvokeAsync(Update);
}
private void Activator_Deactivated()
@ -63,11 +64,6 @@ namespace SafeExamBrowser.UserInterface.Mobile
Dispatcher.InvokeAsync(SelectPrevious);
}
private void Instance_Terminated(InstanceIdentifier id)
{
Dispatcher.InvokeAsync(() => Remove(id));
}
private void ActivateAndHide()
{
Activate();
@ -75,24 +71,6 @@ namespace SafeExamBrowser.UserInterface.Mobile
Hide();
}
private void Add(IApplicationInstance instance)
{
instance.Terminated += Instance_Terminated;
instances.Add(instance);
Update();
}
private void Remove(InstanceIdentifier id)
{
var instance = instances.FirstOrDefault(i => i.Id == id);
if (instance != default(IApplicationInstance))
{
instances.Remove(instance);
Update();
}
}
private void SelectNext()
{
ShowConditional();
@ -119,7 +97,7 @@ namespace SafeExamBrowser.UserInterface.Mobile
private void ShowConditional()
{
if (instances.Any() && Visibility != Visibility.Visible)
if (controls.Any() && Visibility != Visibility.Visible)
{
Show();
Activate();
@ -128,22 +106,31 @@ namespace SafeExamBrowser.UserInterface.Mobile
private void Update()
{
var max = Math.Ceiling(Math.Sqrt(instances.Count));
var stack = new Stack<IApplicationInstance>(instances);
var windows = new Stack<IApplicationWindow>();
foreach (var application in applications)
{
foreach (var window in application.GetWindows())
{
windows.Push(window);
}
}
var max = Math.Ceiling(Math.Sqrt(windows.Count));
controls.Clear();
Rows.Children.Clear();
for (var rowCount = 0; rowCount < max && stack.Any(); rowCount++)
for (var rowCount = 0; rowCount < max && windows.Any(); rowCount++)
{
var row = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
Rows.Children.Add(row);
for (var columnIndex = 0; columnIndex < max && stack.Any(); columnIndex++)
for (var columnIndex = 0; columnIndex < max && windows.Any(); columnIndex++)
{
var instance = stack.Pop();
var control = new TaskViewInstanceControl(instance);
var window = windows.Pop();
var control = new TaskViewWindowControl(window);
controls.AddLast(control);
row.Children.Add(control);
@ -158,7 +145,7 @@ namespace SafeExamBrowser.UserInterface.Mobile
Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left;
Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top;
if (!instances.Any())
if (!windows.Any())
{
Hide();
}

View file

@ -40,12 +40,6 @@ namespace SafeExamBrowser.WindowsApi.Contracts
/// </summary>
event ProcessTerminatedEventHandler Terminated;
/// <summary>
/// Attempts to activate the process (i.e. bring its main window to the foreground). This may only work for interactive processes which have
/// a main window. Returns <c>true</c> if the process was successfully activated, otherwise <c>false</c>.
/// </summary>
bool TryActivate();
/// <summary>
/// Attempts to gracefully terminate the process by closing its main window. This will only work for interactive processes which have a main
/// window. Optionally waits the specified amount of time for the process to terminate. Returns <c>true</c> if the process has terminated,
@ -53,12 +47,6 @@ namespace SafeExamBrowser.WindowsApi.Contracts
/// </summary>
bool TryClose(int timeout_ms = 0);
/// <summary>
/// Attempts to retrieve the title of the main window of the process. This will only work if for interactive processes which have a main
/// window. Returns <c>true</c> if the title was successfully retrieved, otherwise <c>false</c>.
/// </summary>
bool TryGetWindowTitle(out string title);
/// <summary>
/// Attempts to immediately kill the process. Optionally waits the specified amount of time for the process to terminate. Returns <c>true</c>
/// if the process has terminated, otherwise <c>false</c>.