Vanara/Windows.Forms/Dialogs/ShellProgressDialog.cs

248 lines
10 KiB
C#

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Vanara.Extensions;
using static Vanara.PInvoke.Shell32;
namespace Vanara.Windows.Forms
{
/// <summary>Display style for a <see cref="ShellProgressDialog"/>.</summary>
public enum ShellProgressDialogStyle
{
/// <summary>Displays a progress bar that tracks to the progress values.</summary>
Normal,
/// <summary>
/// Sets the progress bar to marquee mode. Use this when you wish to indicate that progress is being made, but the time required for
/// the operation is unknown.
/// </summary>
Marquee,
/// <summary>
/// Do not display a progress bar. Typically, an application can quantitatively determine how much of the operation remains and
/// periodically pass that value to <c>UpdateProgress</c>. The progress dialog box uses this information to
/// update its progress bar. This flag is typically set when the calling application must wait for an operation to finish, but does
/// not have any quantitative information it can use to update the dialog box.
/// </summary>
Hidden
}
/// <summary>
/// Wrapper for IProgressDialog which displays a system progress dialog. This object is a generic way to show a user how an operation is
/// progressing. It is typically used when deleting, uploading, copying, moving, or downloading large numbers of files. The dialog is shown
/// on a separate thread and will not block operations in the current thread.
/// </summary>
/// <example>This is an example of how to use ShellProgressDialog.
/// <code>
/// private void button1_Click(object sender, System.EventArgs e)
/// {
/// var dlg = new ShellProgressDialog
/// {
/// AutoTimeEstimation = true,
/// CancelMessage = "Canceling. Please wait...",
/// Description = "Processing application...",
/// Title = "MyApp - Long Process"
/// };
/// dlg.Start(this);
/// for (int i = 0; i &lt; steps.Count; i++)
/// {
/// dlg.Detail = steps[i].Text;
/// steps[1].Action.Invoke();
/// dlg.UpdateProgress(i + 1, steps.Count);
/// }
/// }
/// </code>
/// </example>
public class ShellProgressDialog : Component
{
private string cancelMsg;
private bool closed = true;
private string description;
private string detail;
private EnumFlagIndexer<PROGDLG> flags;
private IProgressDialog iDlg;
private string title = string.Empty;
/// <summary>Initializes a new instance of the <see cref="ShellProgressDialog"/> class.</summary>
public ShellProgressDialog()
{
iDlg = new IProgressDialog();
}
/// <summary> Gets or sets a value indicating whether to automatically estimate the remaining time and display it.</summary>
[DefaultValue(false), Category("Behavior"), Description("Automatically estimate the remaining time and display it.")]
public bool AutoTimeEstimation { get => flags[PROGDLG.PROGDLG_AUTOTIME]; set => flags[PROGDLG.PROGDLG_AUTOTIME] = value; }
/// <summary>Gets or sets a message to be displayed if the user cancels the operation.</summary>
/// <remarks>
/// Even though the user clicks Cancel, the application cannot immediately call <see cref="Stop"/> to close the dialog box. The
/// application must wait until the next time it read <see cref="IsCancelled"/> to discover that the user has canceled the operation.
/// Since this delay might be significant, the progress dialog box provides the user with immediate feedback by clearing the
/// description and detail lines and displaying this cancel message. The message is intended to let the user know that the delay is
/// normal and that the progress dialog box will be closed shortly. It is typically is set to something like "Please wait while ...".
/// </remarks>
[DefaultValue(null), Category("Appearance"), Description("Message to be displayed if the user cancels the operation.")]
public string CancelMessage
{
get => cancelMsg;
set
{
if (cancelMsg == value) return;
iDlg.SetCancelMsg(cancelMsg = value);
}
}
/// <summary>Gets or sets a value indicating whether to compact displayed strings if they are too large to fit on a line.</summary>
/// <value><see langword="true"/> to have path strings compacted if they are too large to fit on a line.</value>
[DefaultValue(false), Category("Appearance"), Description("Compact displayed strings if they are too large to fit on a line.")]
public bool CompactStrings { get; set; }
/// <summary>Gets or sets the description that appears on the first line.</summary>
/// <value>The description.</value>
[DefaultValue(null), Category("Appearance"), Description("Description that appears on the first line.")]
public string Description
{
get => description;
set
{
if (description == value) return;
iDlg.SetLine(1, description = value, CompactStrings);
}
}
/// <summary>Gets or sets the detail that appears on the second line.</summary>
/// <value>The detail.</value>
[DefaultValue(null), Category("Appearance"), Description("Detail text that appears on the second line.")]
public string Detail
{
get => detail;
set
{
if (detail == value) return;
iDlg.SetLine(2, detail = value, CompactStrings);
}
}
/// <summary>Gets or sets a value indicating whether to show the "time remaining" text.</summary>
/// <value>Show the "time remaining" text.</value>
[DefaultValue(false), Category("Appearance"), Description("Show the \"time remaining\" text.")]
public bool HideTimeRemaining { get => flags[PROGDLG.PROGDLG_NOTIME]; set => flags[PROGDLG.PROGDLG_NOTIME] = value; }
/// <summary>Checks whether the user has canceled the operation.</summary>
/// <returns><see langword="true"/> if the user has canceled the operation; otherwise, <see langword="false"/>.</returns>
/// <remarks>
/// The system does not send a message to the component when the user clicks the Cancel button. You must periodically use this
/// function to poll the progress dialog box object to determine whether the operation has been canceled.
/// </remarks>
[Browsable(false)]
public bool IsCancelled => iDlg.HasUserCancelled();
/// <summary>Gets or sets a value indicating whether the Minimize button is displayed in the caption bar of the progress dialog.</summary>
/// <value>
/// <see langword="true"/> to display a Minimize button for the dialog; otherwise, <see langword="false"/>. The default is <see langword="true"/>.
/// </value>
[DefaultValue(true), Category("Appearance"), Description("Show the Minimize button in the caption bar")]
public bool MinimizeBox { get => !flags[PROGDLG.PROGDLG_NOMINIMIZE]; set => flags[PROGDLG.PROGDLG_NOMINIMIZE] = !value; }
/// <summary>Gets a value indicating whether this form is displayed modally.</summary>
/// <value><see langword="true"/> if the form is displayed modally; otherwise, <see langword="false"/>.</value>
[DefaultValue(false), Category("Appearance"), Description("Display this form is modally.")]
public bool Modal { get => flags[PROGDLG.PROGDLG_MODAL]; set => flags[PROGDLG.PROGDLG_MODAL] = value; }
/// <summary>Gets or sets a value indicating whether a Cancel button is displayed on the progress dialog.</summary>
/// <value>
/// <see langword="true"/> to display a Cancel button on the progress dialog; otherwise, <see langword="false"/>. The default is <see langword="true"/>.
/// </value>
[DefaultValue(true), Category("Appearance"), Description("Display the Cancel button.")]
public bool ShowCancelButton { get => !flags[PROGDLG.PROGDLG_NOCANCEL]; set => flags[PROGDLG.PROGDLG_NOCANCEL] = !value; }
/// <summary>Gets or sets the style of the progress bar on the progress dialog.</summary>
/// <value>The style of the progress bar.</value>
[DefaultValue(ShellProgressDialogStyle.Normal), Category("Appearance"), Description("Progress bar style.")]
public ShellProgressDialogStyle Style
{
get => flags[PROGDLG.PROGDLG_NOPROGRESSBAR] ? ShellProgressDialogStyle.Hidden : (flags[PROGDLG.PROGDLG_MARQUEEPROGRESS] ? ShellProgressDialogStyle.Marquee : ShellProgressDialogStyle.Normal);
set
{
switch (value)
{
case ShellProgressDialogStyle.Normal:
flags[PROGDLG.PROGDLG_NOPROGRESSBAR] = false;
flags[PROGDLG.PROGDLG_MARQUEEPROGRESS] = false;
break;
case ShellProgressDialogStyle.Marquee:
flags[PROGDLG.PROGDLG_NOPROGRESSBAR] = false;
flags[PROGDLG.PROGDLG_MARQUEEPROGRESS] = true;
break;
case ShellProgressDialogStyle.Hidden:
flags[PROGDLG.PROGDLG_NOPROGRESSBAR] = true;
flags[PROGDLG.PROGDLG_MARQUEEPROGRESS] = false;
break;
default:
break;
}
}
}
/// <summary>Gets or sets the file dialog box title.</summary>
/// <value>The progress dialog box title. The default value is an empty string ("").</value>
[DefaultValue(""), Category("Appearance"), Description("")]
public string Title
{
get => title;
set
{
if (title == value) return;
iDlg.SetTitle(title = value);
}
}
/// <summary>Starts the progress dialog box.</summary>
/// <param name="hwndParent">The progress dialog box's parent window. This value can be <see langword="null"/>.</param>
public virtual void Start(IWin32Window hwndParent)
{
closed = false;
iDlg.StartProgressDialog(hwndParent?.Handle ?? IntPtr.Zero, dwFlags: flags);
iDlg.Timer(PDTIMER.PDTIMER_RESET);
}
/// <summary>Stops the progress dialog box and removes it from the screen.</summary>
public virtual void Stop()
{
if (closed) return;
iDlg.StopProgressDialog();
closed = true;
}
/// <summary>Updates the progress dialog box with the current state of the operation.</summary>
/// <param name="completed">
/// An application-defined value that indicates what proportion of the operation has been completed at the time the method was
/// called. If called with this value equaling the value supplied in <paramref name="total"/>, the <see cref="Stop"/> method will be
/// called to close the progress dialog.
/// </param>
/// <param name="total">
/// An application-defined value that specifies what value <paramref name="completed"/> will have when the operation is complete.
/// </param>
public virtual void UpdateProgress(ulong completed, ulong total)
{
if (closed) return;
iDlg.SetProgress64(completed, total);
if (completed == total) Stop();
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
return;
if (!closed)
Stop();
iDlg = null;
}
}
}