mirror of https://github.com/dahall/Vanara.git
452 lines
18 KiB
C#
452 lines
18 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
|
|
namespace Vanara.Windows.Forms
|
|
{
|
|
/// <summary>
|
|
/// Multi-level, auto-sizing, progress dialog supporting asyncronous tasks. The background activities are provided as asyncronous methods who have a
|
|
/// <see cref="CancellationToken"/> and an <see cref="IProgress{ProgresEventArgs}"/> instance passed as parameters. The method uses the
|
|
/// <see cref="CancellationToken"/> instance to determine if the user has pressed the "Cancel" button and the
|
|
/// <see cref="IProgress{ProgresEventArgs}.Report"/> method to report progress.
|
|
/// </summary>
|
|
/// <example lang="cs">
|
|
/// public async Task ShowProgress()
|
|
/// {
|
|
/// var progressDialog = new ProgressDialog { Title = "Progress" };
|
|
/// await progressDialog.ShowDialog(null, (cancellationToken, progressReporter) => SomeTask(listOfItemsToProcess, cancellationToken, progressReporter));
|
|
/// }
|
|
///
|
|
/// private Task SomeTask(List<Item> items, CancellationToken token, IProgress<ProgresEventArgs> reporter)
|
|
/// {
|
|
/// return Task.Run(() => {
|
|
/// for (var i = 0; i < items.Count; i++)
|
|
/// {
|
|
/// if (token.IsCancellationRequested) break;
|
|
/// SomeLongJob(items[i]);
|
|
/// reporter.Report(new ProgresEventArgs(items[i].ToString(), ProgressDialog.Percent(i, items.Count)));
|
|
/// }
|
|
/// });
|
|
/// }
|
|
/// </example>
|
|
public class ProgressDialog : CommonDialog
|
|
{
|
|
private const string defaultCancelText = "&Cancel";
|
|
private readonly InternalProgressDialog progressDlg;
|
|
private readonly Progress<ProgressEventArgs> update;
|
|
private CancellationTokenSource cancelToken;
|
|
private IWin32Window parent;
|
|
private bool running;
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="ProgressDialog"/> class.</summary>
|
|
public ProgressDialog()
|
|
{
|
|
progressDlg = new InternalProgressDialog();
|
|
progressDlg.Cancelled += (o, a) => cancelToken?.Cancel();
|
|
update = new Progress<ProgressEventArgs>(UpdateProgress);
|
|
}
|
|
|
|
/// <summary>Background task to run when calling <see cref="CommonDialog.ShowDialog()"/> without any specified action.</summary>
|
|
/// <value>The background task.</value>
|
|
[Browsable(false)]
|
|
public Func<CancellationToken, IProgress<ProgressEventArgs>, Task> BackgroundTask { get; set; }
|
|
|
|
/// <summary>Gets or sets the cancel button text.</summary>
|
|
/// <value>The cancel button text.</value>
|
|
[DefaultValue(defaultCancelText), Category("Appearance"), Description("The text on the Cancel button.")]
|
|
[Localizable(true), Bindable(true)]
|
|
public string CancelButtonText
|
|
{
|
|
get => progressDlg.cancelBtn.Text;
|
|
set => progressDlg.cancelBtn.Text = value;
|
|
}
|
|
|
|
/// <summary>Gets or sets the progress dialog box title.</summary>
|
|
/// <value>The progress dialog box title. The default value is an empty string ("").</value>
|
|
[DefaultValue(""), Category("Window"), Description("The progress dialog box title.")]
|
|
[Localizable(true), Bindable(true)]
|
|
public string Title
|
|
{
|
|
get => progressDlg.Text;
|
|
set => progressDlg.Text = value;
|
|
}
|
|
|
|
/// <summary>Builds an integer percent value.</summary>
|
|
/// <param name="idx">The index of the item being processed.</param>
|
|
/// <param name="count">The total count of the items being processed</param>
|
|
/// <param name="start">A value with which to pad the starting value.</param>
|
|
/// <returns>The percentage of the way through the count.</returns>
|
|
public static int Percent(int idx, int count, int start = 0) => start + (int)Math.Floor(idx * (float)(100 - start) / count);
|
|
|
|
/// <summary>When overridden in a derived class, resets the properties of a common dialog box to their default values.</summary>
|
|
public override void Reset()
|
|
{
|
|
if (cancelToken != null && !cancelToken.IsCancellationRequested)
|
|
cancelToken.Cancel();
|
|
if (progressDlg.Visible)
|
|
progressDlg.Hide();
|
|
cancelToken = null;
|
|
parent = null;
|
|
running = false;
|
|
}
|
|
|
|
/// <summary>Shows the progress dialog as a modal dialog box with the specified owner while executing the supplied function.</summary>
|
|
/// <param name="owner">Any object that implements <see cref="IWin32Window"/> that represents the top-level window that will own the model dialog box.</param>
|
|
/// <param name="function">The function whose execution is run in the background of the progress dialog.</param>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
/// <exception cref="ArgumentNullException">The <paramref name="function"/> value cannot be <c>null</c>.</exception>
|
|
/// <exception cref="InvalidOperationException">Another instance is already running.</exception>
|
|
public async Task ShowDialog(IWin32Window owner, Func<CancellationToken, IProgress<ProgressEventArgs>, Task> function)
|
|
{
|
|
if (function == null) throw new ArgumentNullException();
|
|
if (running) throw new InvalidOperationException("Another instance is already running.");
|
|
try
|
|
{
|
|
parent = owner;
|
|
cancelToken = new CancellationTokenSource();
|
|
running = true;
|
|
await function(cancelToken.Token, update);
|
|
}
|
|
finally
|
|
{
|
|
Reset();
|
|
}
|
|
}
|
|
|
|
/// <summary>Shows the progress dialog as a modal dialog box with the specified owner while executing the supplied function.</summary>
|
|
/// <typeparam name="T">The type of the value returned by <paramref name="function"/>.</typeparam>
|
|
/// <param name="owner">Any object that implements <see cref="IWin32Window"/> that represents the top-level window that will own the model dialog box.</param>
|
|
/// <param name="function">The function whose execution is run in the background of the progress dialog.</param>
|
|
/// <returns>The task object representing the asynchronous operation. The <see cref="Task{TResult}.Result"/> property on the task object returns the value returned by <paramref name="function"/>.</returns>
|
|
/// <exception cref="ArgumentNullException">The <paramref name="function"/> value cannot be <c>null</c>.</exception>
|
|
/// <exception cref="InvalidOperationException">Another instance is already running.</exception>
|
|
public async Task<T> ShowDialog<T>(IWin32Window owner, Func<CancellationToken, IProgress<ProgressEventArgs>, Task<T>> function)
|
|
{
|
|
if (function == null) throw new ArgumentNullException();
|
|
if (running) throw new InvalidOperationException("Another instance is already running.");
|
|
try
|
|
{
|
|
parent = owner;
|
|
cancelToken = new CancellationTokenSource();
|
|
running = true;
|
|
return await function(cancelToken.Token, update);
|
|
}
|
|
finally
|
|
{
|
|
Reset();
|
|
}
|
|
}
|
|
|
|
/// <summary>When overridden in a derived class, specifies a common dialog box.</summary>
|
|
/// <param name="hwndOwner">A value that represents the window handle of the owner window for the common dialog box.</param>
|
|
/// <returns><see langword="true" /> if the dialog box was successfully run; otherwise, <see langword="false" />.</returns>
|
|
protected override bool RunDialog(IntPtr hwndOwner)
|
|
{
|
|
try
|
|
{
|
|
ShowDialog(NativeWindow.FromHandle(hwndOwner), BackgroundTask).Wait();
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void UpdateProgress(ProgressEventArgs p)
|
|
{
|
|
if (p.PercentComplete == 100)
|
|
{
|
|
if (progressDlg.Visible)
|
|
progressDlg.Hide();
|
|
}
|
|
else
|
|
{
|
|
if (!progressDlg.Visible)
|
|
progressDlg.ShowDialog(parent);
|
|
progressDlg.PercentComplete = p.PercentComplete;
|
|
progressDlg.StatusText = p.StatusText;
|
|
progressDlg.MacroPercentComplete = p.MacroPercentComplete;
|
|
progressDlg.MacroStatusText = p.MacroStatusText;
|
|
}
|
|
}
|
|
|
|
internal class InternalProgressDialog : Form
|
|
{
|
|
internal Button cancelBtn;
|
|
private TableLayoutPanel commandPanel;
|
|
private TableLayoutPanel contentPanel;
|
|
private Panel dividerPanel;
|
|
private ProgressBar macroProgressBar;
|
|
private Label macroStatusLabel;
|
|
private ProgressBar progressBar;
|
|
private Label statusLabel;
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="InternalProgressDialog"/> class.</summary>
|
|
public InternalProgressDialog()
|
|
{
|
|
InitializeComponent();
|
|
MaximumSize = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
|
|
}
|
|
|
|
/// <summary>Occurs when the Cancel button is pressed.</summary>
|
|
public event CancelEventHandler Cancelled;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the value of the macro progress bar. Valid values are 0 to 100. If this value is 0 and <see cref="MacroStatusText"/> is null or
|
|
/// empty, the macro items will be hidden.
|
|
/// </summary>
|
|
/// <value>The macro percent complete.</value>
|
|
[DefaultValue(0)]
|
|
public int MacroPercentComplete
|
|
{
|
|
get => macroProgressBar.Value;
|
|
set
|
|
{
|
|
macroProgressBar.Value = value;
|
|
MacroItemsVisible = macroProgressBar.Value != 0 && !string.IsNullOrEmpty(macroStatusLabel.Text);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the status text displayed above the macro progress bar. If this value is null or empty and <see cref="MacroPercentComplete"/> is 0,
|
|
/// the macro items will be hidden.
|
|
/// </summary>
|
|
/// <value>The macro status text.</value>
|
|
[DefaultValue("")]
|
|
public string MacroStatusText
|
|
{
|
|
get => macroStatusLabel.Text;
|
|
set
|
|
{
|
|
macroStatusLabel.Text = value;
|
|
MacroItemsVisible = macroProgressBar.Value != 0 && !string.IsNullOrEmpty(macroStatusLabel.Text);
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets or sets the value of the standard progress bar. Valid values are 0 to 100.</summary>
|
|
/// <value>The percent complete.</value>
|
|
[DefaultValue(0)]
|
|
public int PercentComplete
|
|
{
|
|
get => progressBar.Value;
|
|
set => progressBar.Value = value;
|
|
}
|
|
|
|
/// <summary>Gets or sets the status text displayed above the standard progress bar.</summary>
|
|
/// <value>The status text.</value>
|
|
[DefaultValue("")]
|
|
public string StatusText
|
|
{
|
|
get => statusLabel.Text;
|
|
set => statusLabel.Text = value;
|
|
}
|
|
|
|
/// <summary>Gets or sets a value indicating whether [task visible].</summary>
|
|
/// <value><c>true</c> if [task visible]; otherwise, <c>false</c>.</value>
|
|
private bool MacroItemsVisible
|
|
{
|
|
get => macroProgressBar.Visible;
|
|
set => macroProgressBar.Visible = macroStatusLabel.Visible = value;
|
|
}
|
|
|
|
/// <summary>Raises the <see cref="E:Cancelled"/> event.</summary>
|
|
/// <param name="e">The <see cref="CancelEventArgs"/> instance containing the event data.</param>
|
|
protected virtual void OnCancelled(CancelEventArgs e)
|
|
{
|
|
Cancelled?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>Raises the <see cref="E:System.Windows.Forms.Form.FormClosed"/> event.</summary>
|
|
/// <param name="e">A <see cref="T:System.Windows.Forms.FormClosedEventArgs"/> that contains the event data.</param>
|
|
protected override void OnFormClosed(FormClosedEventArgs e)
|
|
{
|
|
base.OnFormClosed(e);
|
|
MacroItemsVisible = false;
|
|
}
|
|
|
|
/// <summary>Handles the Click event of the cancelBtn control.</summary>
|
|
/// <param name="sender">The source of the event.</param>
|
|
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
|
|
private void CancelBtn_Click(object sender, EventArgs e)
|
|
{
|
|
OnCancelled(new CancelEventArgs(true));
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
progressBar = new ProgressBar();
|
|
statusLabel = new Label();
|
|
commandPanel = new TableLayoutPanel();
|
|
cancelBtn = new Button();
|
|
dividerPanel = new Panel();
|
|
contentPanel = new TableLayoutPanel();
|
|
macroStatusLabel = new Label();
|
|
macroProgressBar = new ProgressBar();
|
|
commandPanel.SuspendLayout();
|
|
contentPanel.SuspendLayout();
|
|
SuspendLayout();
|
|
// progressBar
|
|
progressBar.Dock = DockStyle.Top;
|
|
progressBar.Location = new Point(11, 75);
|
|
progressBar.Margin = new Padding(0, 7, 0, 0);
|
|
progressBar.Size = new Size(346, 9);
|
|
progressBar.TabIndex = 0;
|
|
// statusLabel
|
|
statusLabel.AutoSize = true;
|
|
statusLabel.Dock = DockStyle.Top;
|
|
statusLabel.Location = new Point(11, 53);
|
|
statusLabel.Margin = new Padding(0);
|
|
statusLabel.Size = new Size(346, 15);
|
|
statusLabel.TabIndex = 1;
|
|
// commandPanel
|
|
commandPanel.AutoSize = true;
|
|
commandPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
|
commandPanel.BackColor = SystemColors.Control;
|
|
commandPanel.ColumnCount = 2;
|
|
commandPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
|
commandPanel.ColumnStyles.Add(new ColumnStyle());
|
|
commandPanel.Controls.Add(cancelBtn, 1, 1);
|
|
commandPanel.Controls.Add(dividerPanel, 0, 0);
|
|
commandPanel.Dock = DockStyle.Top;
|
|
commandPanel.Location = new Point(0, 95);
|
|
commandPanel.Margin = new Padding(0);
|
|
commandPanel.RowCount = 2;
|
|
commandPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F));
|
|
commandPanel.RowStyles.Add(new RowStyle());
|
|
commandPanel.Size = new Size(368, 46);
|
|
commandPanel.TabIndex = 6;
|
|
// cancelBtn
|
|
cancelBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
|
cancelBtn.DialogResult = DialogResult.Cancel;
|
|
cancelBtn.Location = new Point(282, 12);
|
|
cancelBtn.Margin = new Padding(0, 11, 11, 11);
|
|
cancelBtn.Size = new Size(75, 23);
|
|
cancelBtn.TabIndex = 2;
|
|
cancelBtn.Text = defaultCancelText;
|
|
cancelBtn.UseVisualStyleBackColor = true;
|
|
cancelBtn.Click += CancelBtn_Click;
|
|
// dividerPanel
|
|
dividerPanel.BackColor = Color.FromArgb(223, 223, 223);
|
|
commandPanel.SetColumnSpan(dividerPanel, 2);
|
|
dividerPanel.Dock = DockStyle.Fill;
|
|
dividerPanel.Location = new Point(0, 0);
|
|
dividerPanel.Margin = new Padding(0);
|
|
dividerPanel.Size = new Size(368, 1);
|
|
dividerPanel.TabIndex = 3;
|
|
// contentPanel
|
|
contentPanel.AutoSize = true;
|
|
contentPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
|
contentPanel.ColumnCount = 1;
|
|
contentPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
|
contentPanel.Controls.Add(macroStatusLabel, 0, 0);
|
|
contentPanel.Controls.Add(macroProgressBar, 0, 1);
|
|
contentPanel.Controls.Add(statusLabel, 0, 2);
|
|
contentPanel.Controls.Add(progressBar, 0, 3);
|
|
contentPanel.Dock = DockStyle.Top;
|
|
contentPanel.Location = new Point(0, 0);
|
|
contentPanel.Margin = new Padding(0);
|
|
contentPanel.Padding = new Padding(11);
|
|
contentPanel.RowCount = 4;
|
|
contentPanel.RowStyles.Add(new RowStyle());
|
|
contentPanel.RowStyles.Add(new RowStyle());
|
|
contentPanel.RowStyles.Add(new RowStyle());
|
|
contentPanel.RowStyles.Add(new RowStyle());
|
|
contentPanel.Size = new Size(368, 95);
|
|
contentPanel.TabIndex = 7;
|
|
// macroStatusLabel
|
|
macroStatusLabel.AutoSize = true;
|
|
macroStatusLabel.Dock = DockStyle.Top;
|
|
macroStatusLabel.Location = new Point(11, 11);
|
|
macroStatusLabel.Margin = new Padding(0);
|
|
macroStatusLabel.Size = new Size(346, 15);
|
|
macroStatusLabel.TabIndex = 3;
|
|
macroStatusLabel.Visible = false;
|
|
// macroProgressBar
|
|
macroProgressBar.Dock = DockStyle.Top;
|
|
macroProgressBar.Location = new Point(11, 33);
|
|
macroProgressBar.Margin = new Padding(0, 7, 0, 11);
|
|
macroProgressBar.Size = new Size(346, 9);
|
|
macroProgressBar.TabIndex = 2;
|
|
macroProgressBar.Visible = false;
|
|
// ProgressDialog
|
|
AutoSize = true;
|
|
AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
|
BackColor = SystemColors.Window;
|
|
CancelButton = cancelBtn;
|
|
ClientSize = new Size(368, 141);
|
|
ControlBox = false;
|
|
Controls.Add(commandPanel);
|
|
Controls.Add(contentPanel);
|
|
Font = new Font("Segoe UI", 9F);
|
|
FormBorderStyle = FormBorderStyle.FixedSingle;
|
|
Margin = new Padding(3, 4, 3, 4);
|
|
MaximizeBox = false;
|
|
MinimizeBox = false;
|
|
MinimumSize = new Size(320, 132);
|
|
ShowIcon = false;
|
|
ShowInTaskbar = false;
|
|
StartPosition = FormStartPosition.CenterParent;
|
|
commandPanel.ResumeLayout(false);
|
|
contentPanel.ResumeLayout(false);
|
|
contentPanel.PerformLayout();
|
|
ResumeLayout(false);
|
|
PerformLayout();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Updates progress on a <see cref="ProgressDialog"/>.</summary>
|
|
public class ProgressEventArgs : EventArgs
|
|
{
|
|
/// <summary>Initializes a new instance of the <see cref="ProgressEventArgs"/> class.</summary>
|
|
/// <param name="statusText">The status text.</param>
|
|
/// <param name="percentComplete">The percent complete.</param>
|
|
public ProgressEventArgs(string statusText, int percentComplete)
|
|
{
|
|
StatusText = statusText;
|
|
PercentComplete = percentComplete;
|
|
}
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="ProgressEventArgs"/> class.</summary>
|
|
/// <param name="macroStatusText">The macro status text.</param>
|
|
/// <param name="macroPercentComplete">The macro percent complete.</param>
|
|
/// <param name="statusText">The status text.</param>
|
|
/// <param name="percentComplete">The percent complete.</param>
|
|
public ProgressEventArgs(string macroStatusText, int macroPercentComplete, string statusText, int percentComplete) :
|
|
this(statusText, percentComplete)
|
|
{
|
|
MacroStatusText = macroStatusText;
|
|
MacroPercentComplete = macroPercentComplete;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the value of the macro progress bar. Valid values are 0 to 100. If this value is 0 and <see cref="MacroStatusText"/> is null or empty,
|
|
/// the macro items will be hidden.
|
|
/// </summary>
|
|
/// <value>The macro percent complete.</value>
|
|
[DefaultValue(0)]
|
|
public int MacroPercentComplete { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the status text displayed above the macro progress bar. If this value is null or empty and <see cref="MacroPercentComplete"/> is 0, the
|
|
/// macro items will be hidden.
|
|
/// </summary>
|
|
/// <value>The macro status text.</value>
|
|
[DefaultValue("")]
|
|
public string MacroStatusText { get; set; }
|
|
|
|
/// <summary>Gets or sets the value of the standard progress bar. Valid values are 0 to 100.</summary>
|
|
/// <value>The percent complete.</value>
|
|
[DefaultValue(0)]
|
|
public int PercentComplete { get; set; }
|
|
|
|
/// <summary>Gets or sets the status text displayed above the standard progress bar.</summary>
|
|
/// <value>The status text.</value>
|
|
[DefaultValue("")]
|
|
public string StatusText { get; set; }
|
|
}
|
|
} |