SEBWIN-312: Implemented thumbnails of open windows for desktop taskview.
This commit is contained in:
parent
018e596905
commit
a93678d5d7
17 changed files with 329 additions and 93 deletions
|
@ -6,6 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
|
||||
|
@ -16,6 +17,11 @@ namespace SafeExamBrowser.Applications.Contracts
|
|||
/// </summary>
|
||||
public interface IApplicationWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// The native handle of the window.
|
||||
/// </summary>
|
||||
IntPtr Handle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The icon of the window.
|
||||
/// </summary>
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace SafeExamBrowser.Applications
|
|||
{
|
||||
private INativeMethods nativeMethods;
|
||||
|
||||
internal IntPtr Handle { get; }
|
||||
public IntPtr Handle { get; }
|
||||
public IconResource Icon { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
internal int Id { get; }
|
||||
|
||||
public IntPtr Handle { get; private set; }
|
||||
public IconResource Icon { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
|
||||
|
@ -178,6 +179,8 @@ namespace SafeExamBrowser.Browser
|
|||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
window.Show();
|
||||
|
||||
Handle = window.Handle;
|
||||
|
||||
logger.Debug("Initialized browser window.");
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace SafeExamBrowser.Client
|
|||
private IRuntimeProxy runtimeProxy;
|
||||
private ISystemInfo systemInfo;
|
||||
private ITaskbar taskbar;
|
||||
private ITaskView taskView;
|
||||
private ITaskView taskview;
|
||||
private IText text;
|
||||
private ITextResource textResource;
|
||||
private IUserInterfaceFactory uiFactory;
|
||||
|
@ -95,7 +95,7 @@ namespace SafeExamBrowser.Client
|
|||
uiFactory = BuildUserInterfaceFactory();
|
||||
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client);
|
||||
taskbar = BuildTaskbar();
|
||||
taskView = BuildTaskView();
|
||||
taskview = BuildTaskView();
|
||||
|
||||
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
|
||||
var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, processFactory);
|
||||
|
@ -199,7 +199,7 @@ namespace SafeExamBrowser.Client
|
|||
{
|
||||
var moduleLogger = ModuleLogger(nameof(BrowserApplication));
|
||||
var browser = new BrowserApplication(context.AppConfig, context.Settings.Browser, messageBox, moduleLogger, text, uiFactory);
|
||||
var operation = new BrowserOperation(actionCenter, context, logger, taskbar, taskView, uiFactory);
|
||||
var operation = new BrowserOperation(actionCenter, context, logger, taskbar, taskview, uiFactory);
|
||||
|
||||
context.Browser = browser;
|
||||
|
||||
|
@ -258,7 +258,7 @@ namespace SafeExamBrowser.Client
|
|||
powerSupply,
|
||||
systemInfo,
|
||||
taskbar,
|
||||
taskView,
|
||||
taskview,
|
||||
text,
|
||||
uiFactory,
|
||||
wirelessAdapter);
|
||||
|
@ -311,7 +311,7 @@ namespace SafeExamBrowser.Client
|
|||
case UserInterfaceMode.Mobile:
|
||||
return new Mobile.TaskView();
|
||||
default:
|
||||
return new Desktop.TaskView();
|
||||
return new Desktop.Taskview();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace SafeExamBrowser.Client.Operations
|
|||
private IActionCenter actionCenter;
|
||||
private ILogger logger;
|
||||
private ITaskbar taskbar;
|
||||
private ITaskView taskView;
|
||||
private ITaskView taskview;
|
||||
private IUserInterfaceFactory uiFactory;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||
|
@ -31,13 +31,13 @@ namespace SafeExamBrowser.Client.Operations
|
|||
ClientContext context,
|
||||
ILogger logger,
|
||||
ITaskbar taskbar,
|
||||
ITaskView taskView,
|
||||
ITaskView taskview,
|
||||
IUserInterfaceFactory uiFactory) : base(context)
|
||||
{
|
||||
this.actionCenter = actionCenter;
|
||||
this.logger = logger;
|
||||
this.taskbar = taskbar;
|
||||
this.taskView = taskView;
|
||||
this.taskview = taskview;
|
||||
this.uiFactory = uiFactory;
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ namespace SafeExamBrowser.Client.Operations
|
|||
taskbar.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.Taskbar), true);
|
||||
}
|
||||
|
||||
taskView.Add(Context.Browser);
|
||||
taskview.Add(Context.Browser);
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace SafeExamBrowser.Client.Operations
|
|||
private IPowerSupply powerSupply;
|
||||
private ISystemInfo systemInfo;
|
||||
private ITaskbar taskbar;
|
||||
private ITaskView taskView;
|
||||
private ITaskView taskview;
|
||||
private IText text;
|
||||
private IUserInterfaceFactory uiFactory;
|
||||
private IWirelessAdapter wirelessAdapter;
|
||||
|
@ -56,7 +56,7 @@ namespace SafeExamBrowser.Client.Operations
|
|||
IPowerSupply powerSupply,
|
||||
ISystemInfo systemInfo,
|
||||
ITaskbar taskbar,
|
||||
ITaskView taskView,
|
||||
ITaskView taskview,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory,
|
||||
IWirelessAdapter wirelessAdapter) : base(context)
|
||||
|
@ -73,7 +73,7 @@ namespace SafeExamBrowser.Client.Operations
|
|||
this.systemInfo = systemInfo;
|
||||
this.text = text;
|
||||
this.taskbar = taskbar;
|
||||
this.taskView = taskView;
|
||||
this.taskview = taskview;
|
||||
this.uiFactory = uiFactory;
|
||||
this.wirelessAdapter = wirelessAdapter;
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ namespace SafeExamBrowser.Client.Operations
|
|||
|
||||
if (Context.Settings.Keyboard.AllowAltTab && activator is ITaskViewActivator taskViewActivator)
|
||||
{
|
||||
taskView.Register(taskViewActivator);
|
||||
taskview.Register(taskViewActivator);
|
||||
taskViewActivator.Start();
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@ namespace SafeExamBrowser.Client.Operations
|
|||
|
||||
foreach (var application in Context.Applications)
|
||||
{
|
||||
taskView.Add(application);
|
||||
taskview.Add(application);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
|
@ -26,6 +27,11 @@ namespace SafeExamBrowser.UserInterface.Contracts.Browser
|
|||
/// Enables the forward navigation button.
|
||||
/// </summary>
|
||||
bool CanNavigateForwards { set; }
|
||||
|
||||
/// <summary>
|
||||
/// The native handle of the window.
|
||||
/// </summary>
|
||||
IntPtr Handle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the user changed the URL.
|
||||
|
|
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
|||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
|
@ -40,6 +41,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
|
||||
public bool CanNavigateBackwards { set => Dispatcher.Invoke(() => BackwardButton.IsEnabled = value); }
|
||||
public bool CanNavigateForwards { set => Dispatcher.Invoke(() => ForwardButton.IsEnabled = value); }
|
||||
public IntPtr Handle { get; private set; }
|
||||
|
||||
public event AddressChangedEventHandler AddressChanged;
|
||||
public event ActionRequestedEventHandler BackwardNavigationRequested;
|
||||
|
@ -157,6 +159,8 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
|
||||
private void BrowserWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Handle = new WindowInteropHelper(this).Handle;
|
||||
|
||||
if (isMainWindow)
|
||||
{
|
||||
WindowUtility.DisableCloseButtonFor(this);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<UserControl x:Class="SafeExamBrowser.UserInterface.Desktop.Controls.TaskViewWindowControl"
|
||||
<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"
|
||||
|
@ -11,11 +11,20 @@
|
|||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="5" Height="150" Width="250">
|
||||
<Grid Margin="5" Height="175" Width="250">
|
||||
<Border Name="Indicator" Background="{StaticResource BackgroundTransparentEmphasisBrush}" BorderThickness="0" Visibility="Hidden" />
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Vertical" VerticalAlignment="Center">
|
||||
<ContentControl Name="Icon" MaxWidth="40" />
|
||||
<TextBlock Name="Title" Margin="10" Foreground="{StaticResource PrimaryTextBrush}" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
<Grid Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl Grid.Row="0" Grid.Column="0" Name="Icon" Margin="0,0,5,0" Height="16" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Name="Title" Foreground="{StaticResource PrimaryTextBrush}" VerticalAlignment="Center" TextTrimming="CharacterEllipsis" />
|
||||
<ContentControl Grid.Row="1" Grid.ColumnSpan="2" Name="Placeholder" Margin="0,5,0,0" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
|
@ -6,21 +6,26 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.UserInterface.Shared.Utilities;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
||||
{
|
||||
public partial class TaskViewWindowControl : UserControl
|
||||
public partial class TaskviewWindowControl : UserControl
|
||||
{
|
||||
private Taskview taskview;
|
||||
private IntPtr thumbnail;
|
||||
private IApplicationWindow window;
|
||||
|
||||
public TaskViewWindowControl(IApplicationWindow window)
|
||||
public TaskviewWindowControl(IApplicationWindow window, Taskview taskview)
|
||||
{
|
||||
this.window = window;
|
||||
this.taskview = taskview;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeControl();
|
||||
|
@ -33,35 +38,128 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
|||
|
||||
internal void Deselect()
|
||||
{
|
||||
Icon.MaxWidth = 40;
|
||||
Indicator.Visibility = Visibility.Hidden;
|
||||
Title.FontWeight = FontWeights.Normal;
|
||||
}
|
||||
|
||||
internal void Destroy()
|
||||
{
|
||||
if (thumbnail != IntPtr.Zero)
|
||||
{
|
||||
Thumbnail.DwmUnregisterThumbnail(thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Select()
|
||||
{
|
||||
Icon.MaxWidth = 50;
|
||||
Indicator.Visibility = Visibility.Visible;
|
||||
Title.FontWeight = FontWeights.SemiBold;
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
if (!IsLoaded || !IsVisible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (thumbnail == IntPtr.Zero && taskview.Handle != IntPtr.Zero && window.Handle != IntPtr.Zero)
|
||||
{
|
||||
Thumbnail.DwmRegisterThumbnail(taskview.Handle, window.Handle, out thumbnail);
|
||||
}
|
||||
|
||||
if (thumbnail != IntPtr.Zero)
|
||||
{
|
||||
Thumbnail.DwmQueryThumbnailSourceSize(thumbnail, out var size);
|
||||
|
||||
var destination = CalculatePhysicalDestination(size);
|
||||
var properties = new Thumbnail.Properties
|
||||
{
|
||||
Destination = destination,
|
||||
Flags = Thumbnail.DWM_TNP_RECTDESTINATION | Thumbnail.DWM_TNP_VISIBLE,
|
||||
Visible = true
|
||||
};
|
||||
|
||||
Thumbnail.DwmUpdateThumbnailProperties(thumbnail, ref properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void TaskViewWindowControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.NewValue as bool? == true)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_TitleChanged(string title)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Title.Text = title);
|
||||
}
|
||||
|
||||
private void Window_IconChanged(IconResource icon)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(window.Icon));
|
||||
}
|
||||
|
||||
private Thumbnail.Rectangle CalculatePhysicalDestination(Thumbnail.Size size)
|
||||
{
|
||||
var controlToTaskview = TransformToVisual(taskview);
|
||||
var placeholderToControl = Placeholder.TransformToVisual(this);
|
||||
var placeholderLeft = placeholderToControl.Transform(new Point(0, 0)).X;
|
||||
var placeholderTop = placeholderToControl.Transform(new Point(0, 0)).Y;
|
||||
var placeholderRight = placeholderToControl.Transform(new Point(Placeholder.ActualWidth, 0)).X;
|
||||
var placeholderBottom = placeholderToControl.Transform(new Point(0, Placeholder.ActualHeight)).Y;
|
||||
|
||||
var physicalBounds = new Thumbnail.Rectangle
|
||||
{
|
||||
Left = (int) Math.Round(this.TransformToPhysical(controlToTaskview.Transform(new Point(placeholderLeft, 0)).X, 0).X),
|
||||
Top = (int) Math.Round(this.TransformToPhysical(0, controlToTaskview.Transform(new Point(0, placeholderTop)).Y).Y),
|
||||
Right = (int) Math.Round(this.TransformToPhysical(controlToTaskview.Transform(new Point(placeholderRight, 0)).X, 0).X),
|
||||
Bottom = (int) Math.Round(this.TransformToPhysical(0, controlToTaskview.Transform(new Point(0, placeholderBottom)).Y).Y)
|
||||
};
|
||||
|
||||
var scaleFactor = default(double);
|
||||
var thumbnailHeight = default(double);
|
||||
var thumbnailWidth = default(double);
|
||||
var maxWidth = (double) physicalBounds.Right - physicalBounds.Left;
|
||||
var maxHeight = (double) physicalBounds.Bottom - physicalBounds.Top;
|
||||
var placeholderRatio = maxWidth / maxHeight;
|
||||
var windowRatio = (double) size.X / size.Y;
|
||||
|
||||
if (windowRatio < placeholderRatio)
|
||||
{
|
||||
thumbnailHeight = maxHeight;
|
||||
scaleFactor = thumbnailHeight / size.Y;
|
||||
thumbnailWidth = size.X * scaleFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
thumbnailWidth = maxWidth;
|
||||
scaleFactor = thumbnailWidth / size.X;
|
||||
thumbnailHeight = size.Y * scaleFactor;
|
||||
}
|
||||
|
||||
var widthDifference = maxWidth - thumbnailWidth;
|
||||
var heightDifference = maxHeight - thumbnailHeight;
|
||||
|
||||
return new Thumbnail.Rectangle
|
||||
{
|
||||
Left = (int) Math.Round(physicalBounds.Left + (widthDifference / 2)),
|
||||
Top = (int) Math.Round(physicalBounds.Top + (heightDifference / 2)),
|
||||
Right = (int) Math.Round(physicalBounds.Right - (widthDifference / 2)),
|
||||
Bottom = (int) Math.Round(physicalBounds.Bottom - (heightDifference / 2))
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeControl()
|
||||
{
|
||||
Icon.Content = IconResourceLoader.Load(window.Icon);
|
||||
IsVisibleChanged += TaskViewWindowControl_IsVisibleChanged;
|
||||
Loaded += (o, args) => Update();
|
||||
Title.Text = window.Title;
|
||||
|
||||
window.IconChanged += Instance_IconChanged;
|
||||
window.TitleChanged += Instance_TitleChanged;
|
||||
}
|
||||
|
||||
private void Instance_TitleChanged(string title)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Title.Text = title);
|
||||
}
|
||||
|
||||
private void Instance_IconChanged(IconResource icon)
|
||||
{
|
||||
Dispatcher.InvokeAsync(() => Icon.Content = IconResourceLoader.Load(window.Icon));
|
||||
window.IconChanged += Window_IconChanged;
|
||||
window.TitleChanged += Window_TitleChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,8 +142,8 @@
|
|||
<Compile Include="Controls\TaskbarWirelessNetworkControl.xaml.cs">
|
||||
<DependentUpon>TaskbarWirelessNetworkControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Controls\TaskViewWindowControl.xaml.cs">
|
||||
<DependentUpon>TaskViewWindowControl.xaml</DependentUpon>
|
||||
<Compile Include="Controls\TaskviewWindowControl.xaml.cs">
|
||||
<DependentUpon>TaskviewWindowControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="LockScreen.xaml.cs">
|
||||
<DependentUpon>LockScreen.xaml</DependentUpon>
|
||||
|
@ -161,8 +161,8 @@
|
|||
<Compile Include="SplashScreen.xaml.cs">
|
||||
<DependentUpon>SplashScreen.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="TaskView.xaml.cs">
|
||||
<DependentUpon>TaskView.xaml</DependentUpon>
|
||||
<Compile Include="Taskview.xaml.cs">
|
||||
<DependentUpon>Taskview.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UserInterfaceFactory.cs" />
|
||||
<Compile Include="ViewModels\DateTimeViewModel.cs" />
|
||||
|
@ -317,7 +317,7 @@
|
|||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Resource>
|
||||
<Page Include="Controls\TaskViewWindowControl.xaml">
|
||||
<Page Include="Controls\TaskviewWindowControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
|
@ -325,7 +325,7 @@
|
|||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="TaskView.xaml">
|
||||
<Page Include="Taskview.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<Window x:Class="SafeExamBrowser.UserInterface.Desktop.TaskView"
|
||||
<Window x:Class="SafeExamBrowser.UserInterface.Desktop.Taskview"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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.Desktop"
|
||||
mc:Ignorable="d" AllowsTransparency="True" Background="{DynamicResource BackgroundTransparentBrush}" BorderBrush="DodgerBlue" BorderThickness="1"
|
||||
Title="TaskView" Topmost="True" Height="450" SizeToContent="WidthAndHeight" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None">
|
||||
Title="Taskview" Topmost="True" Height="450" SizeToContent="WidthAndHeight" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
|
|
|
@ -11,24 +11,28 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
||||
using SafeExamBrowser.UserInterface.Desktop.Controls;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop
|
||||
{
|
||||
public partial class TaskView : Window, ITaskView
|
||||
public partial class Taskview : Window, ITaskView
|
||||
{
|
||||
private IList<IApplication> applications;
|
||||
private LinkedListNode<TaskViewWindowControl> current;
|
||||
private LinkedList<TaskViewWindowControl> controls;
|
||||
private LinkedListNode<TaskviewWindowControl> current;
|
||||
private LinkedList<TaskviewWindowControl> controls;
|
||||
|
||||
public TaskView()
|
||||
internal IntPtr Handle { get; private set; }
|
||||
|
||||
public Taskview()
|
||||
{
|
||||
applications = new List<IApplication>();
|
||||
controls = new LinkedList<TaskViewWindowControl>();
|
||||
controls = new LinkedList<TaskviewWindowControl>();
|
||||
|
||||
InitializeComponent();
|
||||
InitializeTaskview();
|
||||
}
|
||||
|
||||
public void Add(IApplication application)
|
||||
|
@ -66,9 +70,21 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
|
||||
private void ActivateAndHide()
|
||||
{
|
||||
Activate();
|
||||
current?.Value.Activate();
|
||||
Hide();
|
||||
if (IsVisible)
|
||||
{
|
||||
Activate();
|
||||
current?.Value.Activate();
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeTaskview()
|
||||
{
|
||||
Loaded += (o, args) =>
|
||||
{
|
||||
Handle = new WindowInteropHelper(this).Handle;
|
||||
Update();
|
||||
};
|
||||
}
|
||||
|
||||
private void SelectNext()
|
||||
|
@ -106,31 +122,37 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
|
||||
private void Update()
|
||||
{
|
||||
var windows = new Stack<IApplicationWindow>();
|
||||
ClearTaskview();
|
||||
LoadControls();
|
||||
UpdateLocation();
|
||||
}
|
||||
|
||||
foreach (var application in applications)
|
||||
private void ClearTaskview()
|
||||
{
|
||||
foreach (var control in controls)
|
||||
{
|
||||
foreach (var window in application.GetWindows())
|
||||
{
|
||||
windows.Push(window);
|
||||
}
|
||||
control.Destroy();
|
||||
}
|
||||
|
||||
var max = Math.Ceiling(Math.Sqrt(windows.Count));
|
||||
|
||||
controls.Clear();
|
||||
Rows.Children.Clear();
|
||||
}
|
||||
|
||||
for (var rowCount = 0; rowCount < max && windows.Any(); rowCount++)
|
||||
private void LoadControls()
|
||||
{
|
||||
var windows = GetAllWindows();
|
||||
var maxColumns = Math.Ceiling(Math.Sqrt(windows.Count));
|
||||
|
||||
while (windows.Any())
|
||||
{
|
||||
var row = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
|
||||
|
||||
Rows.Children.Add(row);
|
||||
|
||||
for (var columnIndex = 0; columnIndex < max && windows.Any(); columnIndex++)
|
||||
for (var column = 0; column < maxColumns && windows.Any(); column++)
|
||||
{
|
||||
var window = windows.Pop();
|
||||
var control = new TaskViewWindowControl(window);
|
||||
var control = new TaskviewWindowControl(window, this);
|
||||
|
||||
controls.AddLast(control);
|
||||
row.Children.Add(control);
|
||||
|
@ -139,16 +161,36 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
|
||||
current = controls.First;
|
||||
current?.Value.Select();
|
||||
}
|
||||
|
||||
UpdateLayout();
|
||||
private void UpdateLocation()
|
||||
{
|
||||
if (controls.Any())
|
||||
{
|
||||
UpdateLayout();
|
||||
|
||||
Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left;
|
||||
Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top;
|
||||
|
||||
if (!controls.Any())
|
||||
Left = (SystemParameters.WorkArea.Width - Width) / 2 + SystemParameters.WorkArea.Left;
|
||||
Top = (SystemParameters.WorkArea.Height - Height) / 2 + SystemParameters.WorkArea.Top;
|
||||
}
|
||||
else
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private Stack<IApplicationWindow> GetAllWindows()
|
||||
{
|
||||
var stack = new Stack<IApplicationWindow>();
|
||||
|
||||
foreach (var application in applications)
|
||||
{
|
||||
foreach (var window in application.GetWindows())
|
||||
{
|
||||
stack.Push(window);
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
|||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
|
@ -40,6 +41,7 @@ namespace SafeExamBrowser.UserInterface.Mobile
|
|||
|
||||
public bool CanNavigateBackwards { set => Dispatcher.Invoke(() => BackwardButton.IsEnabled = value); }
|
||||
public bool CanNavigateForwards { set => Dispatcher.Invoke(() => ForwardButton.IsEnabled = value); }
|
||||
public IntPtr Handle { get; private set; }
|
||||
|
||||
public event AddressChangedEventHandler AddressChanged;
|
||||
public event ActionRequestedEventHandler BackwardNavigationRequested;
|
||||
|
@ -157,6 +159,8 @@ namespace SafeExamBrowser.UserInterface.Mobile
|
|||
|
||||
private void BrowserWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Handle = new WindowInteropHelper(this).Handle;
|
||||
|
||||
if (isMainWindow)
|
||||
{
|
||||
WindowUtility.DisableCloseButtonFor(this);
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Utilities\IconResourceLoader.cs" />
|
||||
<Compile Include="Utilities\Thumbnail.cs" />
|
||||
<Compile Include="Utilities\VisualExtensions.cs" />
|
||||
<Compile Include="Utilities\WindowUtility.cs" />
|
||||
</ItemGroup>
|
||||
|
|
63
SafeExamBrowser.UserInterface.Shared/Utilities/Thumbnail.cs
Normal file
63
SafeExamBrowser.UserInterface.Shared/Utilities/Thumbnail.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Shared.Utilities
|
||||
{
|
||||
/// <remarks>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/dwm/thumbnail-ovw.
|
||||
/// </remarks>
|
||||
public static class Thumbnail
|
||||
{
|
||||
public const int DWM_TNP_VISIBLE = 0x8;
|
||||
public const int DWM_TNP_OPACITY = 0x4;
|
||||
public const int DWM_TNP_RECTDESTINATION = 0x1;
|
||||
public const int S_OK = 0;
|
||||
|
||||
[DllImport("dwmapi.dll")]
|
||||
public static extern int DwmQueryThumbnailSourceSize(IntPtr thumbnail, out Size size);
|
||||
|
||||
[DllImport("dwmapi.dll")]
|
||||
public static extern int DwmRegisterThumbnail(IntPtr destinationWindow, IntPtr sourceWindow, out IntPtr thumbnail);
|
||||
|
||||
[DllImport("dwmapi.dll")]
|
||||
public static extern int DwmUnregisterThumbnail(IntPtr thumbnail);
|
||||
|
||||
[DllImport("dwmapi.dll")]
|
||||
public static extern int DwmUpdateThumbnailProperties(IntPtr thumbnail, ref Properties properties);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Properties
|
||||
{
|
||||
public int Flags;
|
||||
public Rectangle Destination;
|
||||
public Rectangle Source;
|
||||
public byte Opacity;
|
||||
public bool Visible;
|
||||
public bool SourceClientAreaOnly;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Size
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Rectangle
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -72,29 +72,6 @@ namespace SafeExamBrowser.WindowsApi
|
|||
return process;
|
||||
}
|
||||
|
||||
private System.Diagnostics.Process StartOnDesktop(string path, params string[] args)
|
||||
{
|
||||
var commandLine = $"{'"' + path + '"'} {string.Join(" ", args)}";
|
||||
var processInfo = new PROCESS_INFORMATION();
|
||||
var startupInfo = new STARTUPINFO();
|
||||
|
||||
startupInfo.cb = Marshal.SizeOf(startupInfo);
|
||||
startupInfo.lpDesktop = StartupDesktop?.Name;
|
||||
|
||||
var success = Kernel32.CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, Constant.NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref startupInfo, ref processInfo);
|
||||
|
||||
if (success)
|
||||
{
|
||||
return System.Diagnostics.Process.GetProcessById(processInfo.dwProcessId);
|
||||
}
|
||||
|
||||
var errorCode = Marshal.GetLastWin32Error();
|
||||
|
||||
logger.Error($"Failed to start process '{path}' on desktop '{StartupDesktop}'! Error code: {errorCode}.");
|
||||
|
||||
throw new Win32Exception(errorCode);
|
||||
}
|
||||
|
||||
public bool TryGetById(int id, out IProcess process)
|
||||
{
|
||||
var raw = System.Diagnostics.Process.GetProcesses().FirstOrDefault(p => p.Id == id);
|
||||
|
@ -183,5 +160,28 @@ namespace SafeExamBrowser.WindowsApi
|
|||
{
|
||||
return logger.CloneFor($"{nameof(Process)} '{name}' ({process.Id})");
|
||||
}
|
||||
|
||||
private System.Diagnostics.Process StartOnDesktop(string path, params string[] args)
|
||||
{
|
||||
var commandLine = $"{'"' + path + '"'} {string.Join(" ", args)}";
|
||||
var processInfo = new PROCESS_INFORMATION();
|
||||
var startupInfo = new STARTUPINFO();
|
||||
|
||||
startupInfo.cb = Marshal.SizeOf(startupInfo);
|
||||
startupInfo.lpDesktop = StartupDesktop?.Name;
|
||||
|
||||
var success = Kernel32.CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, Constant.NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref startupInfo, ref processInfo);
|
||||
|
||||
if (success)
|
||||
{
|
||||
return System.Diagnostics.Process.GetProcessById(processInfo.dwProcessId);
|
||||
}
|
||||
|
||||
var errorCode = Marshal.GetLastWin32Error();
|
||||
|
||||
logger.Error($"Failed to start process '{path}' on desktop '{StartupDesktop}'! Error code: {errorCode}.");
|
||||
|
||||
throw new Win32Exception(errorCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue