[WIP] Partial refactor of applying profile display UI

This commit is contained in:
Terry MacDonald 2020-10-04 23:01:03 +13:00
parent 227caa2230
commit 17cc302d17
12 changed files with 859 additions and 44 deletions

View File

@ -576,7 +576,7 @@ namespace HeliosPlus.Shared
// Now lets start by changing the display topology // Now lets start by changing the display topology
Task applyProfileTopologyTask = Task.Run(() => Task applyProfileTopologyTask = Task.Run(() =>
{ {
Console.WriteLine("ShortcutRepository/SaveShortcutIconToCache : Applying Profile Topology " + profile.Name); Console.WriteLine("ProfileRepository/SaveShortcutIconToCache : Applying Profile Topology " + profile.Name);
ApplyTopology(profile); ApplyTopology(profile);
}); });
applyProfileTopologyTask.Wait(); applyProfileTopologyTask.Wait();
@ -584,7 +584,7 @@ namespace HeliosPlus.Shared
// And then change the path information // And then change the path information
Task applyProfilePathInfoTask = Task.Run(() => Task applyProfilePathInfoTask = Task.Run(() =>
{ {
Console.WriteLine("ShortcutRepository/SaveShortcutIconToCache : Applying Profile Path " + profile.Name); Console.WriteLine("ProfileRepository/SaveShortcutIconToCache : Applying Profile Path " + profile.Name);
ApplyPathInfo(profile); ApplyPathInfo(profile);
}); });
applyProfilePathInfoTask.Wait(); applyProfilePathInfoTask.Wait();
@ -593,16 +593,17 @@ namespace HeliosPlus.Shared
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"ShortcutRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}"); Console.WriteLine($"ProfileRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
return false; return false;
} }
} }
private static void ApplyTopology(ProfileItem profile) public static bool ApplyTopology(ProfileItem profile)
{ {
Debug.Print("ShortcutRepository.ApplyTopology()"); Debug.Print("ProfileRepository.ApplyTopology()");
if (profile == null)
return; if (!(profile is ProfileItem))
return false;
try try
{ {
@ -615,6 +616,7 @@ namespace HeliosPlus.Shared
if (surroundTopologies.Length == 0) if (surroundTopologies.Length == 0)
{ {
// This profile does not use NVIDIA Surround
var currentTopologies = GridTopology.GetGridTopologies(); var currentTopologies = GridTopology.GetGridTopologies();
if (currentTopologies.Any(topology => topology.Rows * topology.Columns > 1)) if (currentTopologies.Any(topology => topology.Rows * topology.Columns > 1))
@ -625,34 +627,39 @@ namespace HeliosPlus.Shared
.Select(displays => new GridTopology(1, 1, new[] { displays })) .Select(displays => new GridTopology(1, 1, new[] { displays }))
.ToArray(); .ToArray();
} }
} } else if (surroundTopologies.Length > 0)
if (surroundTopologies.Length > 0)
{ {
// This profile is an NVIDIA Surround profile
GridTopology.SetGridTopologies(surroundTopologies, SetDisplayTopologyFlag.MaximizePerformance); GridTopology.SetGridTopologies(surroundTopologies, SetDisplayTopologyFlag.MaximizePerformance);
} }
return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"ShortcutRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}"); Console.WriteLine($"ProfileRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
// ignored return false;
} }
} }
private static void ApplyPathInfo(ProfileItem profile) public static bool ApplyPathInfo(ProfileItem profile)
{ {
Debug.Print("ShortcutRepository.ApplyPathInfo()"); Debug.Print("ProfileRepository.ApplyPathInfo()");
if (profile == null) if (!(profile is ProfileItem))
return; return false;
if (!profile.IsPossible) try
{ {
throw new InvalidOperationException(
$"ShortcutRepository/ApplyPathInfo exception: Problem applying the '{profile.Name}' Display Profile! The display configuration changed since this profile is created. Please re-create this profile.");
}
var pathInfos = profile.Viewports.Select(viewport => viewport.ToPathInfo()).Where(info => info != null).ToArray(); var pathInfos = profile.Viewports.Select(viewport => viewport.ToPathInfo()).Where(info => info != null).ToArray();
WindowsDisplayAPI.DisplayConfig.PathInfo.ApplyPathInfos(pathInfos, true, true, true); WindowsDisplayAPI.DisplayConfig.PathInfo.ApplyPathInfos(pathInfos, true, true, true);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"ProfileRepository/ApplyPathInfo exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
return false;
}
} }
public static bool IsValidFilename(string testName) public static bool IsValidFilename(string testName)

View File

@ -80,8 +80,15 @@
<Compile Include="GameLibraries\SteamAppInfoParser\App.cs" /> <Compile Include="GameLibraries\SteamAppInfoParser\App.cs" />
<Compile Include="GameLibraries\SteamLibrary.cs" /> <Compile Include="GameLibraries\SteamLibrary.cs" />
<Compile Include="IconUtils.cs" /> <Compile Include="IconUtils.cs" />
<Compile Include="ProgressReporter.cs" />
<Compile Include="ShortcutItem.cs" /> <Compile Include="ShortcutItem.cs" />
<Compile Include="ShortcutRepository.cs" /> <Compile Include="ShortcutRepository.cs" />
<Compile Include="UIForms\ApplyingProfileForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UIForms\ApplyingProfileForm.Designer.cs">
<DependentUpon>ApplyingProfileForm.cs</DependentUpon>
</Compile>
<Compile Include="UIForms\ShortcutAdaptor.cs" /> <Compile Include="UIForms\ShortcutAdaptor.cs" />
<Compile Include="UIForms\ProfileAdaptor.cs" /> <Compile Include="UIForms\ProfileAdaptor.cs" />
<Compile Include="UIForms\MainForm.cs"> <Compile Include="UIForms\MainForm.cs">
@ -139,6 +146,9 @@
<LastGenOutput>Language.Designer.cs</LastGenOutput> <LastGenOutput>Language.Designer.cs</LastGenOutput>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="UIForms\ApplyingProfileForm.resx">
<DependentUpon>ApplyingProfileForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UIForms\MainForm.resx"> <EmbeddedResource Include="UIForms\MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon> <DependentUpon>MainForm.cs</DependentUpon>
<SubType>Designer</SubType> <SubType>Designer</SubType>

View File

@ -19,6 +19,7 @@ using HeliosPlus.UIForms;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Drawing; using System.Drawing;
using System.Diagnostics.Contracts;
namespace HeliosPlus { namespace HeliosPlus {
public enum SupportedProgramMode public enum SupportedProgramMode
@ -338,5 +339,65 @@ namespace HeliosPlus {
} }
return uncheckedFilename; return uncheckedFilename;
}*/ }*/
// ApplyProfile lives here so that the UI works.
public static bool ApplyProfile(ProfileItem profile)
{
// If we're already on the wanted profile then no need to change!
if (ProfileRepository.IsActiveProfile(profile))
return true;
// We need to check if the profile is valid
if (!profile.IsPossible)
return false;
try
{
// Set up the UI forms to show
ApplyingProfileForm timeoutForm = new ApplyingProfileForm(3, 0, $"Applying Profile '{profile.Name}'", $"Press ESC to timeout");
ApplyingProfileForm topologyForm = new ApplyingProfileForm(0, 30, $"Applying Profile '{profile.Name}' Topology");
ApplyingProfileForm pathInfoForm = new ApplyingProfileForm(0, 30, $"Applying Profile '{profile.Name}' Path");
topologyForm.ShowDialog();
// Now lets start by changing the display topology
Task applyTopologyTask = Task.Run(() =>
{
Console.WriteLine("ProfileRepository/SaveShortcutIconToCache : Applying Profile Topology " + profile.Name);
ProfileRepository.ApplyTopology(profile);
});
applyTopologyTask.Wait();
topologyForm.Close();
if (applyTopologyTask.IsCompleted)
{
pathInfoForm.ShowDialog();
Task applyPathInfoTask = Task.Run(() => {
Console.WriteLine("ProfileRepository/SaveShortcutIconToCache : Applying Profile Path " + profile.Name);
ProfileRepository.ApplyPathInfo(profile);
});
applyPathInfoTask.Wait();
pathInfoForm.Close();
// And then change the path information
if (applyPathInfoTask.IsCompleted)
return true;
return false;
}
else
{
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"ProfileRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
return false;
}
}
} }
} }

View File

@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace HeliosPlus
{
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// A class used by Tasks to report progress or completion updates back to the UI.
/// </summary>
public sealed class ProgressReporter
{
/// <summary>
/// The underlying scheduler for the UI's synchronization context.
/// </summary>
private readonly TaskScheduler scheduler;
/// <summary>
/// Initializes a new instance of the <see cref="ProgressReporter"/> class.
/// This should be run on a UI thread.
/// </summary>
public ProgressReporter()
{
this.scheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
/// <summary>
/// Gets the task scheduler which executes tasks on the UI thread.
/// </summary>
public TaskScheduler Scheduler
{
get { return this.scheduler; }
}
/// <summary>
/// Reports the progress to the UI thread. This method should be called from the task.
/// Note that the progress update is asynchronous with respect to the reporting Task.
/// For a synchronous progress update, wait on the returned <see cref="Task"/>.
/// </summary>
/// <param name="action">The action to perform in the context of the UI thread.
/// Note that this action is run asynchronously on the UI thread.</param>
/// <returns>The task queued to the UI thread.</returns>
public Task ReportProgressAsync(Action action)
{
return Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, this.scheduler);
}
/// <summary>
/// Reports the progress to the UI thread, and waits for the UI thread to process
/// the update before returning. This method should be called from the task.
/// </summary>
/// <param name="action">The action to perform in the context of the UI thread.</param>
public void ReportProgress(Action action)
{
this.ReportProgressAsync(action).Wait();
}
/// <summary>
/// Registers a UI thread handler for when the specified task finishes execution,
/// whether it finishes with success, failiure, or cancellation.
/// </summary>
/// <param name="task">The task to monitor for completion.</param>
/// <param name="action">The action to take when the task has completed, in the context of the UI thread.</param>
/// <returns>The continuation created to handle completion. This is normally ignored.</returns>
public Task RegisterContinuation(Task task, Action action)
{
return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.None, this.scheduler);
}
/// <summary>
/// Registers a UI thread handler for when the specified task finishes execution,
/// whether it finishes with success, failiure, or cancellation.
/// </summary>
/// <typeparam name="TResult">The type of the task result.</typeparam>
/// <param name="task">The task to monitor for completion.</param>
/// <param name="action">The action to take when the task has completed, in the context of the UI thread.</param>
/// <returns>The continuation created to handle completion. This is normally ignored.</returns>
public Task RegisterContinuation<TResult>(Task<TResult> task, Action action)
{
return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.None, this.scheduler);
}
/// <summary>
/// Registers a UI thread handler for when the specified task successfully finishes execution.
/// </summary>
/// <param name="task">The task to monitor for successful completion.</param>
/// <param name="action">The action to take when the task has successfully completed, in the context of the UI thread.</param>
/// <returns>The continuation created to handle successful completion. This is normally ignored.</returns>
public Task RegisterSucceededHandler(Task task, Action action)
{
return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, this.scheduler);
}
/// <summary>
/// Registers a UI thread handler for when the specified task successfully finishes execution
/// and returns a result.
/// </summary>
/// <typeparam name="TResult">The type of the task result.</typeparam>
/// <param name="task">The task to monitor for successful completion.</param>
/// <param name="action">The action to take when the task has successfully completed, in the context of the UI thread.
/// The argument to the action is the return value of the task.</param>
/// <returns>The continuation created to handle successful completion. This is normally ignored.</returns>
public Task RegisterSucceededHandler<TResult>(Task<TResult> task, Action<TResult> action)
{
return task.ContinueWith(t => action(t.Result), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, this.Scheduler);
}
/// <summary>
/// Registers a UI thread handler for when the specified task becomes faulted.
/// </summary>
/// <param name="task">The task to monitor for faulting.</param>
/// <param name="action">The action to take when the task has faulted, in the context of the UI thread.</param>
/// <returns>The continuation created to handle faulting. This is normally ignored.</returns>
public Task RegisterFaultedHandler(Task task, Action<Exception> action)
{
return task.ContinueWith(t => action(t.Exception), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, this.Scheduler);
}
/// <summary>
/// Registers a UI thread handler for when the specified task becomes faulted.
/// </summary>
/// <typeparam name="TResult">The type of the task result.</typeparam>
/// <param name="task">The task to monitor for faulting.</param>
/// <param name="action">The action to take when the task has faulted, in the context of the UI thread.</param>
/// <returns>The continuation created to handle faulting. This is normally ignored.</returns>
public Task RegisterFaultedHandler<TResult>(Task<TResult> task, Action<Exception> action)
{
return task.ContinueWith(t => action(t.Exception), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, this.Scheduler);
}
/// <summary>
/// Registers a UI thread handler for when the specified task is cancelled.
/// </summary>
/// <param name="task">The task to monitor for cancellation.</param>
/// <param name="action">The action to take when the task is cancelled, in the context of the UI thread.</param>
/// <returns>The continuation created to handle cancellation. This is normally ignored.</returns>
public Task RegisterCancelledHandler(Task task, Action action)
{
return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, this.Scheduler);
}
/// <summary>
/// Registers a UI thread handler for when the specified task is cancelled.
/// </summary>
/// <typeparam name="TResult">The type of the task result.</typeparam>
/// <param name="task">The task to monitor for cancellation.</param>
/// <param name="action">The action to take when the task is cancelled, in the context of the UI thread.</param>
/// <returns>The continuation created to handle cancellation. This is normally ignored.</returns>
public Task RegisterCancelledHandler<TResult>(Task<TResult> task, Action action)
{
return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, this.Scheduler);
}
}
}

View File

@ -484,7 +484,8 @@ namespace HeliosPlus
IPCService.GetInstance().Status = InstanceStatus.Busy; IPCService.GetInstance().Status = InstanceStatus.Busy;
// Apply the Profile! // Apply the Profile!
if (!ProfileRepository.ApplyProfile(shortcutToUse.ProfileToUse)) //if (!ProfileRepository.ApplyProfile(shortcutToUse.ProfileToUse))
if (!Program.ApplyProfile(shortcutToUse.ProfileToUse))
{ {
throw new Exception(Language.Cannot_change_active_profile); throw new Exception(Language.Cannot_change_active_profile);
} }
@ -694,7 +695,8 @@ namespace HeliosPlus
// Change back to the original profile if it is different // Change back to the original profile if it is different
if (!ProfileRepository.IsActiveProfile(rollbackProfile)) if (!ProfileRepository.IsActiveProfile(rollbackProfile))
{ {
if (!ProfileRepository.ApplyProfile(rollbackProfile)) //if (!ProfileRepository.ApplyProfile(rollbackProfile))
if (!Program.ApplyProfile(rollbackProfile))
{ {
throw new Exception(Language.Cannot_change_active_profile); throw new Exception(Language.Cannot_change_active_profile);
} }

View File

@ -37,7 +37,7 @@ namespace HeliosPlus.UIForms
this.lbl_sub_message = new System.Windows.Forms.Label(); this.lbl_sub_message = new System.Windows.Forms.Label();
this.lbl_message = new System.Windows.Forms.Label(); this.lbl_message = new System.Windows.Forms.Label();
this.progressBar = new CircularProgressBar.CircularProgressBar(); this.progressBar = new CircularProgressBar.CircularProgressBar();
this.t_start = new System.Windows.Forms.Timer(this.components); this.t_cancellation = new System.Windows.Forms.Timer(this.components);
this.t_countdown = new System.Windows.Forms.Timer(this.components); this.t_countdown = new System.Windows.Forms.Timer(this.components);
this.progressPanel.SuspendLayout(); this.progressPanel.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
@ -106,8 +106,8 @@ namespace HeliosPlus.UIForms
// //
// t_start // t_start
// //
this.t_start.Interval = 1000; this.t_cancellation.Interval = 1000;
this.t_start.Tick += new System.EventHandler(this.t_start_Tick); this.t_cancellation.Tick += new System.EventHandler(this.t_cancellation_Tick);
// //
// t_countdown // t_countdown
// //
@ -150,7 +150,7 @@ namespace HeliosPlus.UIForms
private Panel progressPanel; private Panel progressPanel;
private CircularProgressBar.CircularProgressBar progressBar; private CircularProgressBar.CircularProgressBar progressBar;
private System.Windows.Forms.Label lbl_message; private System.Windows.Forms.Label lbl_message;
private System.Windows.Forms.Timer t_start; private System.Windows.Forms.Timer t_cancellation;
private System.Windows.Forms.Timer t_countdown; private System.Windows.Forms.Timer t_countdown;
private Label lbl_sub_message; private Label lbl_sub_message;
} }

View File

@ -79,9 +79,9 @@ namespace HeliosPlus.UIForms
return base.ProcessCmdKey(ref msg, keyData); return base.ProcessCmdKey(ref msg, keyData);
} }
if (t_start.Enabled) if (t_cancellation.Enabled)
{ {
t_start.Stop(); t_cancellation.Stop();
t_countdown.Stop(); t_countdown.Stop();
DialogResult = DialogResult.Cancel; DialogResult = DialogResult.Cancel;
Close(); Close();
@ -133,14 +133,14 @@ namespace HeliosPlus.UIForms
private void DoTimeout() private void DoTimeout()
{ {
lbl_message.Text = CancellationMessage;
lbl_sub_message.Text = CancellationSubMessage;
progressBar.ProgressColor = Color.DodgerBlue;
if (_startCounter > 0) if (_startCounter > 0)
{ {
lbl_message.Text = CancellationMessage;
lbl_sub_message.Text = CancellationSubMessage;
progressBar.ProgressColor = Color.DodgerBlue;
progressBar.Text = (progressBar.Value = progressBar.Maximum = _startCounter).ToString(); progressBar.Text = (progressBar.Value = progressBar.Maximum = _startCounter).ToString();
t_start.Start(); t_cancellation.Start();
} }
else else
{ {
@ -241,11 +241,11 @@ namespace HeliosPlus.UIForms
Reposition(); Reposition();
} }
private void t_start_Tick(object sender, EventArgs e) private void t_cancellation_Tick(object sender, EventArgs e)
{ {
if (_startCounter < 0) if (_startCounter < 0)
{ {
t_start.Stop(); t_cancellation.Stop();
DoJob(); DoJob();
return; return;

View File

@ -0,0 +1,149 @@
namespace HeliosPlus.UIForms
{
partial class ApplyingProfileForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.progressPanel = new System.Windows.Forms.Panel();
this.lbl_sub_message = new System.Windows.Forms.Label();
this.progressBar = new CircularProgressBar.CircularProgressBar();
this.lbl_message = new System.Windows.Forms.Label();
this.t_countdown = new System.Windows.Forms.Timer(this.components);
this.t_cancellation = new System.Windows.Forms.Timer(this.components);
this.progressPanel.SuspendLayout();
this.SuspendLayout();
//
// progressPanel
//
this.progressPanel.Anchor = System.Windows.Forms.AnchorStyles.None;
this.progressPanel.Controls.Add(this.lbl_sub_message);
this.progressPanel.Controls.Add(this.progressBar);
this.progressPanel.Controls.Add(this.lbl_message);
this.progressPanel.Location = new System.Drawing.Point(77, 154);
this.progressPanel.Name = "progressPanel";
this.progressPanel.Size = new System.Drawing.Size(621, 270);
this.progressPanel.TabIndex = 1;
//
// lbl_sub_message
//
this.lbl_sub_message.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.lbl_sub_message.ForeColor = System.Drawing.Color.White;
this.lbl_sub_message.Location = new System.Drawing.Point(159, 87);
this.lbl_sub_message.Name = "lbl_sub_message";
this.lbl_sub_message.Size = new System.Drawing.Size(300, 16);
this.lbl_sub_message.TabIndex = 2;
this.lbl_sub_message.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// progressBar
//
this.progressBar.AnimationFunction = WinFormAnimation.KnownAnimationFunctions.Liner;
this.progressBar.AnimationSpeed = 1000;
this.progressBar.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(40)))), ((int)(((byte)(40)))));
this.progressBar.Font = new System.Drawing.Font("Microsoft Sans Serif", 39.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.progressBar.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.progressBar.InnerColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.progressBar.InnerMargin = 0;
this.progressBar.InnerWidth = 0;
this.progressBar.Location = new System.Drawing.Point(243, 125);
this.progressBar.MarqueeAnimationSpeed = 2000;
this.progressBar.Name = "progressBar";
this.progressBar.OuterColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.progressBar.OuterMargin = -8;
this.progressBar.OuterWidth = 6;
this.progressBar.ProgressColor = System.Drawing.Color.DodgerBlue;
this.progressBar.ProgressWidth = 10;
this.progressBar.SecondaryFont = new System.Drawing.Font("Microsoft Sans Serif", 26.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.progressBar.Size = new System.Drawing.Size(135, 135);
this.progressBar.StartAngle = 270;
this.progressBar.SubscriptColor = System.Drawing.Color.FromArgb(((int)(((byte)(166)))), ((int)(((byte)(166)))), ((int)(((byte)(166)))));
this.progressBar.SubscriptMargin = new System.Windows.Forms.Padding(10, -35, 0, 0);
this.progressBar.SubscriptText = "";
this.progressBar.SuperscriptColor = System.Drawing.Color.Gray;
this.progressBar.SuperscriptMargin = new System.Windows.Forms.Padding(0, 30, 0, 0);
this.progressBar.SuperscriptText = "";
this.progressBar.TabIndex = 0;
this.progressBar.TextMargin = new System.Windows.Forms.Padding(2, 5, 0, 0);
this.progressBar.Value = 68;
//
// lbl_message
//
this.lbl_message.Font = new System.Drawing.Font("Microsoft Sans Serif", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(178)));
this.lbl_message.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.lbl_message.Location = new System.Drawing.Point(3, 0);
this.lbl_message.Name = "lbl_message";
this.lbl_message.Size = new System.Drawing.Size(615, 61);
this.lbl_message.TabIndex = 1;
this.lbl_message.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// t_countdown
//
this.t_countdown.Interval = 1000;
this.t_countdown.Tick += new System.EventHandler(this.t_countdown_Tick);
//
// t_cancellation
//
this.t_cancellation.Interval = 1000;
this.t_cancellation.Tick += new System.EventHandler(this.t_cancellation_Tick);
//
// ApplyingProfileForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(40)))), ((int)(((byte)(40)))));
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.progressPanel);
this.DoubleBuffered = true;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.KeyPreview = true;
this.Name = "ApplyingProfileForm";
this.Opacity = 0D;
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "HeliosPlus - Please Wait";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ApplyingProfileForm_FormClosing);
this.Load += new System.EventHandler(this.ApplyingProfileForm_Reposition);
this.Shown += new System.EventHandler(this.ApplyingProfileForm_Shown);
this.LocationChanged += new System.EventHandler(this.ApplyingProfileForm_Reposition);
this.Leave += new System.EventHandler(this.ApplyingProfileForm_Reposition);
this.Move += new System.EventHandler(this.ApplyingProfileForm_Reposition);
this.progressPanel.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel progressPanel;
private CircularProgressBar.CircularProgressBar progressBar;
private System.Windows.Forms.Label lbl_sub_message;
private System.Windows.Forms.Label lbl_message;
private System.Windows.Forms.Timer t_countdown;
private System.Windows.Forms.Timer t_cancellation;
}
}

View File

@ -0,0 +1,300 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using WinFormAnimation;
namespace HeliosPlus.UIForms
{
public partial class ApplyingProfileForm : Form
{
private readonly Bitmap _progressImage;
private readonly List<Point> _progressPositions = new List<Point>();
private int _countdownCounter;
private readonly int _displayChangeMaxDelta = -1;
private int _displayChangeDelta;
private int _lastCount;
private bool _isClosing;
private int _cancellationCounter;
public ApplyingProfileForm()
{
InitializeComponent();
_progressImage = new Bitmap(progressPanel.Width, progressPanel.Height);
Controls.Remove(progressPanel);
progressPanel.BackColor = BackColor;
progressBar.Invalidated += (sender, args) => Invalidate();
progressPanel.Invalidated += (sender, args) => Invalidate();
Reposition();
}
public ApplyingProfileForm(int cancellationTimeout = 0, int countdown = 0, string title = null, string message = null, int displayChangeMaxDelta = 5) : this()
{
_cancellationCounter = cancellationTimeout;
_countdownCounter = countdown;
_lastCount = _countdownCounter;
_displayChangeMaxDelta = displayChangeMaxDelta;
if (!string.IsNullOrEmpty(title)) CountdownTitle = title;
if (!string.IsNullOrEmpty(message)) CountdownMessage = message;
}
public string CancellationTitle { get; set; } = "Starting in ...";
public string CancellationMessage { get; set; } = "Please press ESC to cancel";
public string CountdownTitle { get; set; } = "Please wait...";
public string CountdownMessage { get; set; } = "It won't be long now!";
protected override void OnKeyDown(KeyEventArgs e)
{
e.Handled = true;
}
protected override void OnPaint(PaintEventArgs e)
{
lock (_progressPositions)
{
progressPanel.DrawToBitmap(_progressImage, new Rectangle(Point.Empty, progressPanel.Size));
foreach (var position in _progressPositions)
{
e.Graphics.DrawImage(_progressImage, new Rectangle(position, progressPanel.Size));
}
}
base.OnPaint(e);
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData != Keys.Escape)
{
return base.ProcessCmdKey(ref msg, keyData);
}
if (t_cancellation.Enabled)
{
t_cancellation.Stop();
t_countdown.Stop();
DialogResult = DialogResult.Cancel;
Close();
return true;
}
return true;
}
private void HandleDisplayChangeDelta()
{
if (_displayChangeMaxDelta <= -1) return;
_displayChangeDelta = _lastCount - _countdownCounter;
if (_displayChangeDelta > _displayChangeMaxDelta)
{
Debug.Print("_displayChangeDelta > _displayChangeMaxDelta! " + _displayChangeDelta + " > " + _displayChangeMaxDelta);
DialogResult = DialogResult.OK;
Close();
}
}
private void DoCountdown()
{
if (_countdownCounter > 0)
{
lbl_message.Text = CountdownTitle;
lbl_sub_message.Text = CountdownMessage;
progressBar.ProgressColor = Color.OrangeRed;
progressBar.Text = (progressBar.Value = progressBar.Maximum = _countdownCounter).ToString();
t_countdown.Start();
}
else
{
progressBar.Style = ProgressBarStyle.Marquee;
progressBar.Text = "";
progressBar.Maximum = 100;
progressBar.Value = 50;
progressBar.Style = ProgressBarStyle.Marquee;
DialogResult = DialogResult.OK;
Close();
}
HandleDisplayChangeDelta();
}
private void DoCancellationTimeout()
{
if (_cancellationCounter > 0)
{
lbl_message.Text = CancellationTitle;
lbl_sub_message.Text = CancellationMessage;
progressBar.ProgressColor = Color.DodgerBlue;
progressBar.Text = (progressBar.Value = progressBar.Maximum = _cancellationCounter).ToString();
t_cancellation.Start();
}
else
{
DoCountdown();
}
HandleDisplayChangeDelta();
}
private void Reposition()
{
lock (_progressPositions)
{
Screen[] screens = Screen.AllScreens;
Size = screens.Select(screen => screen.Bounds)
.Aggregate(Rectangle.Union)
.Size;
_progressPositions.Clear();
_progressPositions.AddRange(
screens.Select(
screen =>
new Point(screen.Bounds.X + ((screen.Bounds.Width - progressPanel.Width) / 2),
screen.Bounds.Y + ((screen.Bounds.Height - progressPanel.Height) / 2))
)
);
}
Invalidate();
}
private void ApplyingProfileForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (_isClosing)
{
return;
}
_isClosing = true;
e.Cancel = true;
var dialogResult = DialogResult;
new Animator(new Path((float)Opacity, 0, 200))
.Play(new SafeInvoker<float>(f =>
{
try
{
Opacity = f;
}
catch (Exception ex)
{
Console.WriteLine($"ApplyingProfileForm/ApplyingProfileForm_FormClosing exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
// ignored
}
}, this), new SafeInvoker(() =>
{
DialogResult = dialogResult;
Close();
}, this));
}
private void ApplyingProfileForm_Reposition(object sender, EventArgs e)
{
Reposition();
}
private void ApplyingProfileForm_Shown(object sender, EventArgs e)
{
new Animator(new Path((float)Opacity, 0.97f, 200))
.Play(new SafeInvoker<float>(f =>
{
try
{
Opacity = f;
}
catch (Exception ex)
{
Console.WriteLine($"ApplyingProfileForm/ApplyingProfileForm_Shown exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
// ignored
}
}, this), new SafeInvoker(DoCancellationTimeout, this));
}
private void t_countdown_Tick(object sender, EventArgs e)
{
if (_countdownCounter < 0)
{
t_countdown.Stop();
DialogResult = DialogResult.OK;
Close();
return;
}
HandleDisplayChangeDelta();
progressBar.Value = _countdownCounter;
progressBar.Text = progressBar.Value.ToString();
_countdownCounter--;
Reposition();
}
private void t_cancellation_Tick(object sender, EventArgs e)
{
if (_cancellationCounter < 0)
{
t_cancellation.Stop();
DoCountdown();
return;
}
progressBar.Value = _cancellationCounter;
progressBar.Text = progressBar.Value.ToString();
_cancellationCounter--;
Reposition();
}
protected override void WndProc(ref Message m)
{
const int WM_SETTINGCHANGE = 0x001A;
const int SPI_SETWORKAREA = 0x02F;
const int WM_DISPLAYCHANGE = 0x007E;
const int x_bitshift = 0;
const int y_bitshift = 16;
const int xy_mask = 0xFFFF;
bool displayChange = false;
switch (m.Msg)
{
case WM_SETTINGCHANGE:
Debug.Print("Message: " + m.ToString());
Debug.Print("WM_SETTINGCHANGE");
switch ((int)m.WParam)
{
case SPI_SETWORKAREA:
Debug.Print("SPI_SETWORKAREA");
displayChange = true;
break;
}
break;
case WM_DISPLAYCHANGE:
int cxScreen = (xy_mask & ((int)m.LParam) >> x_bitshift);
int cyScreen = (xy_mask & ((int)m.LParam) >> y_bitshift);
Debug.Print("Message: " + m.ToString());
Debug.Print("WM_DISPLAYCHANGE");
Debug.Print("cxScreen: " + cxScreen + " cyScreen: " + cyScreen);
displayChange = true;
break;
}
if (displayChange)
{
_displayChangeDelta = _lastCount - _countdownCounter;
_lastCount = _countdownCounter;
Debug.Print("Display Change Detected at t " + _lastCount + " difference between changes is " + _displayChangeDelta);
}
base.WndProc(ref m);
}
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="t_countdown.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>143, 17</value>
</metadata>
<metadata name="t_cancellation.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@ -312,9 +312,8 @@ namespace HeliosPlus.UIForms
this.MinimumSize = new System.Drawing.Size(800, 400); this.MinimumSize = new System.Drawing.Size(800, 400);
this.Name = "DisplayProfileForm"; this.Name = "DisplayProfileForm";
this.ShowInTaskbar = false; this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "HeliosPlus - Setup Display Profiles"; this.Text = "HeliosPlus - Setup Display Profiles";
this.TopMost = true;
this.Activated += new System.EventHandler(this.DisplayProfileForm_Activated); this.Activated += new System.EventHandler(this.DisplayProfileForm_Activated);
this.Load += new System.EventHandler(this.DisplayProfileForm_Load); this.Load += new System.EventHandler(this.DisplayProfileForm_Load);
this.menu_profiles.ResumeLayout(false); this.menu_profiles.ResumeLayout(false);

View File

@ -46,7 +46,8 @@ namespace HeliosPlus.UIForms
} }
// Apply the Profile // Apply the Profile
ProfileRepository.ApplyProfile(_selectedProfile); //ProfileRepository.ApplyProfile(_selectedProfile);
Program.ApplyProfile(_selectedProfile);
} }