using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Vanara.Windows.Forms
{
///
/// Multi-level, auto-sizing, progress dialog supporting asyncronous tasks. The background activities are provided as asyncronous methods who have a
/// and an instance passed as parameters. The method uses the
/// instance to determine if the user has pressed the "Cancel" button and the
/// method to report progress.
///
///
/// 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)));
/// }
/// });
/// }
///
public class ProgressDialog : CommonDialog
{
private const string defaultCancelText = "&Cancel";
private readonly InternalProgressDialog progressDlg;
private readonly Progress update;
private CancellationTokenSource cancelToken;
private IWin32Window parent;
private bool running;
/// Initializes a new instance of the class.
public ProgressDialog()
{
progressDlg = new InternalProgressDialog();
progressDlg.Cancelled += (o, a) => cancelToken?.Cancel();
update = new Progress(UpdateProgress);
}
/// Background task to run when calling without any specified action.
/// The background task.
[Browsable(false)]
public Func, Task> BackgroundTask { get; set; }
/// Gets or sets the cancel button text.
/// The cancel button text.
[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;
}
/// Gets or sets the progress dialog box title.
/// The progress dialog box title. The default value is an empty string ("").
[DefaultValue(""), Category("Window"), Description("The progress dialog box title.")]
[Localizable(true), Bindable(true)]
public string Title
{
get => progressDlg.Text;
set => progressDlg.Text = value;
}
/// Builds an integer percent value.
/// The index of the item being processed.
/// The total count of the items being processed
/// A value with which to pad the starting value.
/// The percentage of the way through the count.
public static int Percent(int idx, int count, int start = 0) => start + (int)Math.Floor(idx * (float)(100 - start) / count);
/// When overridden in a derived class, resets the properties of a common dialog box to their default values.
public override void Reset()
{
if (cancelToken != null && !cancelToken.IsCancellationRequested)
cancelToken.Cancel();
if (progressDlg.Visible)
progressDlg.Hide();
cancelToken = null;
parent = null;
running = false;
}
/// Shows the progress dialog as a modal dialog box with the specified owner while executing the supplied function.
/// Any object that implements that represents the top-level window that will own the model dialog box.
/// The function whose execution is run in the background of the progress dialog.
/// A task that represents the asynchronous operation.
/// The value cannot be null.
/// Another instance is already running.
public async Task ShowDialog(IWin32Window owner, Func, 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();
}
}
/// Shows the progress dialog as a modal dialog box with the specified owner while executing the supplied function.
/// The type of the value returned by .
/// Any object that implements that represents the top-level window that will own the model dialog box.
/// The function whose execution is run in the background of the progress dialog.
/// The task object representing the asynchronous operation. The property on the task object returns the value returned by .
/// The value cannot be null.
/// Another instance is already running.
public async Task ShowDialog(IWin32Window owner, Func, 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;
return await function(cancelToken.Token, update);
}
finally
{
Reset();
}
}
/// When overridden in a derived class, specifies a common dialog box.
/// A value that represents the window handle of the owner window for the common dialog box.
/// if the dialog box was successfully run; otherwise, .
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;
/// Initializes a new instance of the class.
public InternalProgressDialog()
{
InitializeComponent();
MaximumSize = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
}
/// Occurs when the Cancel button is pressed.
public event CancelEventHandler Cancelled;
///
/// Gets or sets the value of the macro progress bar. Valid values are 0 to 100. If this value is 0 and is null or
/// empty, the macro items will be hidden.
///
/// The macro percent complete.
[DefaultValue(0)]
public int MacroPercentComplete
{
get => macroProgressBar.Value;
set
{
macroProgressBar.Value = value;
MacroItemsVisible = macroProgressBar.Value != 0 && !string.IsNullOrEmpty(macroStatusLabel.Text);
}
}
///
/// Gets or sets the status text displayed above the macro progress bar. If this value is null or empty and is 0,
/// the macro items will be hidden.
///
/// The macro status text.
[DefaultValue("")]
public string MacroStatusText
{
get => macroStatusLabel.Text;
set
{
macroStatusLabel.Text = value;
MacroItemsVisible = macroProgressBar.Value != 0 && !string.IsNullOrEmpty(macroStatusLabel.Text);
}
}
/// Gets or sets the value of the standard progress bar. Valid values are 0 to 100.
/// The percent complete.
[DefaultValue(0)]
public int PercentComplete
{
get => progressBar.Value;
set => progressBar.Value = value;
}
/// Gets or sets the status text displayed above the standard progress bar.
/// The status text.
[DefaultValue("")]
public string StatusText
{
get => statusLabel.Text;
set => statusLabel.Text = value;
}
/// Gets or sets a value indicating whether [task visible].
/// true if [task visible]; otherwise, false.
private bool MacroItemsVisible
{
get => macroProgressBar.Visible;
set => macroProgressBar.Visible = macroStatusLabel.Visible = value;
}
/// Raises the event.
/// The instance containing the event data.
protected virtual void OnCancelled(CancelEventArgs e)
{
Cancelled?.Invoke(this, e);
}
/// Raises the event.
/// A that contains the event data.
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
MacroItemsVisible = false;
}
/// Handles the Click event of the cancelBtn control.
/// The source of the event.
/// The instance containing the event data.
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();
}
}
}
/// Updates progress on a .
public class ProgressEventArgs : EventArgs
{
/// Initializes a new instance of the class.
/// The status text.
/// The percent complete.
public ProgressEventArgs(string statusText, int percentComplete)
{
StatusText = statusText;
PercentComplete = percentComplete;
}
/// Initializes a new instance of the class.
/// The macro status text.
/// The macro percent complete.
/// The status text.
/// The percent complete.
public ProgressEventArgs(string macroStatusText, int macroPercentComplete, string statusText, int percentComplete) :
this(statusText, percentComplete)
{
MacroStatusText = macroStatusText;
MacroPercentComplete = macroPercentComplete;
}
///
/// Gets or sets the value of the macro progress bar. Valid values are 0 to 100. If this value is 0 and is null or empty,
/// the macro items will be hidden.
///
/// The macro percent complete.
[DefaultValue(0)]
public int MacroPercentComplete { get; set; }
///
/// Gets or sets the status text displayed above the macro progress bar. If this value is null or empty and is 0, the
/// macro items will be hidden.
///
/// The macro status text.
[DefaultValue("")]
public string MacroStatusText { get; set; }
/// Gets or sets the value of the standard progress bar. Valid values are 0 to 100.
/// The percent complete.
[DefaultValue(0)]
public int PercentComplete { get; set; }
/// Gets or sets the status text displayed above the standard progress bar.
/// The status text.
[DefaultValue("")]
public string StatusText { get; set; }
}
}