// Modified ProgressDialog from Ookii.Dialogs // // Copyright © Sven Groot (Ookii.org) 2009 // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1) Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // 2) Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // 3) Neither the name of the ORGANIZATION nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. // using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; namespace GARbro.GUI { public class ProgressDialog : Component { private class ProgressChangedData { public string Text { get; set; } public string Description { get; set; } public object UserState { get; set; } } private string _windowTitle; private string _text; private string _description; private Interop.IProgressDialog _dialog; private string _cancellationText; private bool _useCompactPathsForText; private bool _useCompactPathsForDescription; private bool _cancellationPending; private BackgroundWorker _backgroundWorker; /// /// Event raised when the dialog is displayed. /// /// /// Use this event to perform the operation that the dialog is showing the progress for. /// This event will be raised on a different thread than the UI thread. /// public event DoWorkEventHandler DoWork; /// /// Event raised when the operation completes. /// public event RunWorkerCompletedEventHandler RunWorkerCompleted; /// /// Event raised when is called. /// public event ProgressChangedEventHandler ProgressChanged; /// /// Initializes a new instance of the class. /// public ProgressDialog () { InitializeComponent(); ProgressBarStyle = ProgressBarStyle.ProgressBar; ShowCancelButton = true; MinimizeBox = true; } /// /// Gets or sets the text in the progress dialog's title bar. /// /// /// The text in the progress dialog's title bar. The default value is an empty string. /// /// /// /// This property must be set before or is called. Changing property has /// no effect while the dialog is being displayed. /// /// [Localizable(true), Category("Appearance"), Description("The text in the progress dialog's title bar."), DefaultValue("")] public string WindowTitle { get { return _windowTitle ?? string.Empty; } set { _windowTitle = value; } } /// /// Gets or sets a short description of the operation being carried out. /// /// /// A short description of the operation being carried. The default value is an empty string. /// /// /// /// This is the primary message to the user. /// /// /// This property can be changed while the dialog is running, but may only be changed from the thread which /// created the progress dialog. The recommended method to change this value while the dialog is running /// is to use the method. /// /// [Localizable(true), Category("Appearance"), Description("A short description of the operation being carried out.")] public string Text { get { return _text ?? string.Empty; } set { _text = value; if (_dialog != null) _dialog.SetLine (1, Text, UseCompactPathsForText, IntPtr.Zero); } } /// /// Gets or sets a value that indicates whether path strings in the property should be compacted if /// they are too large to fit on one line. /// /// /// to compact path strings if they are too large to fit on one line; otherwise, /// . The default value is . /// /// /// /// This property requires Windows Vista or later. On older versions of Windows, it has no effect. /// /// /// This property can be changed while the dialog is running, but may only be changed from the thread which /// created the progress dialog. /// /// [Category("Behavior"), Description("Indicates whether path strings in the Text property should be compacted if they are too large to fit on one line."), DefaultValue(false)] public bool UseCompactPathsForText { get { return _useCompactPathsForText; } set { _useCompactPathsForText = value; if (_dialog != null) _dialog.SetLine (1, Text, _useCompactPathsForText, IntPtr.Zero); } } /// /// Gets or sets additional details about the operation being carried out. /// /// /// Additional details about the operation being carried out. The default value is an empty string. /// /// /// This text is used to provide additional details beyond the property. /// /// /// /// This property can be changed while the dialog is running, but may only be changed from the thread which /// created the progress dialog. The recommended method to change this value while the dialog is running /// is to use the method. /// /// [Localizable(true), Category("Appearance"), Description("Additional details about the operation being carried out."), DefaultValue("")] public string Description { get { return _description ?? string.Empty; } set { _description = value; if (_dialog != null) _dialog.SetLine (2, Description, UseCompactPathsForDescription, IntPtr.Zero); } } /// /// Gets or sets a value that indicates whether path strings in the property should be compacted if /// they are too large to fit on one line. /// /// /// to compact path strings if they are too large to fit on one line; otherwise, /// . The default value is . /// /// /// /// This property requires Windows Vista or later. On older versions of Windows, it has no effect. /// /// /// This property can be changed while the dialog is running, but may only be changed from the thread which /// created the progress dialog. /// /// [Category("Behavior"), Description("Indicates whether path strings in the Description property should be compacted if they are too large to fit on one line."), DefaultValue(false)] public bool UseCompactPathsForDescription { get { return _useCompactPathsForDescription; } set { _useCompactPathsForDescription = value; if( _dialog != null ) _dialog.SetLine(2, Description, UseCompactPathsForDescription, IntPtr.Zero); } } /// /// Gets or sets the text that will be shown after the Cancel button is pressed. /// /// /// The text that will be shown after the Cancel button is pressed. /// /// /// /// This property must be set before or is called. Changing property has /// no effect while the dialog is being displayed. /// /// [Localizable(true), Category("Appearance"), Description("The text that will be shown after the Cancel button is pressed."), DefaultValue("")] public string CancellationText { get { return _cancellationText ?? string.Empty; } set { _cancellationText = value; } } /// /// Gets or sets a value that indicates whether an estimate of the remaining time will be shown. /// /// /// if an estimate of remaining time will be shown; otherwise, . The /// default value is . /// /// /// /// This property must be set before or is called. Changing property has /// no effect while the dialog is being displayed. /// /// [Category("Appearance"), Description("Indicates whether an estimate of the remaining time will be shown."), DefaultValue(false)] public bool ShowTimeRemaining { get; set; } /// /// Gets or sets a value that indicates whether the dialog has a cancel button. /// /// /// if the dialog has a cancel button; otherwise, . The default /// value is . /// /// /// /// This property requires Windows Vista or later; on older versions of Windows, the cancel button will always /// be displayed. /// /// /// The event handler for the event must periodically check the value of the /// property to see if the operation has been cancelled if this /// property is . /// /// /// Setting this property to is not recommended unless absolutely necessary. /// /// [Category("Appearance"), Description("Indicates whether the dialog has a cancel button. Do not set to false unless absolutely necessary."), DefaultValue(true)] public bool ShowCancelButton { get; set; } /// /// Gets or sets a value that indicates whether the progress dialog has a minimize button. /// /// /// if the dialog has a minimize button; otherwise, . The default /// value is . /// /// /// /// This property has no effect on modal dialogs (which do not have a minimize button). It only applies /// to modeless dialogs shown by using the method. /// /// /// This property must be set before is called. Changing property has /// no effect while the dialog is being displayed. /// /// [Category("Window Style"), Description("Indicates whether the progress dialog has a minimize button."), DefaultValue(true)] public bool MinimizeBox { get; set; } /// /// Gets a value indicating whether the user has requested cancellation of the operation. /// /// /// if the user has cancelled the progress dialog; otherwise, . The default is . /// /// /// The event handler for the event must periodically check this property and abort the operation /// if it returns . /// [Browsable(false)] public bool CancellationPending { get { _backgroundWorker.ReportProgress (-1); // Call with an out-of-range percentage will update the value of // _cancellationPending but do nothing else. return _cancellationPending; } } /// /// Gets or sets a value that indicates whether a regular or marquee style progress bar should be used. /// /// /// One of the values of . /// The default value is . /// /// /// /// Operating systems older than Windows Vista do not support marquee progress bars on the progress dialog. On those operating systems, the /// progress bar will be hidden completely if this property is . /// /// /// When this property is set to , use the method to set /// the value of the progress bar. When this property is set to /// you can still use the method to update the text of the dialog, /// but the percentage will be ignored. /// /// /// This property must be set before or is called. Changing property has /// no effect while the dialog is being displayed. /// /// [Category("Appearance"), Description("Indicates the style of the progress bar."), DefaultValue(ProgressBarStyle.ProgressBar)] public ProgressBarStyle ProgressBarStyle { get; set; } /// /// Gets a value that indicates whether the is running an asynchronous operation. /// /// /// if the is running an asynchronous operation; /// otherwise, . /// [Browsable(false)] public bool IsBusy { get { return _backgroundWorker.IsBusy; } } /// /// Displays the progress dialog as a modeless dialog. /// /// /// /// This function will not block the parent window and will return immediately. /// /// /// Although this function returns immediately, you cannot use the UI thread to do any processing. The dialog /// will not function correctly unless the UI thread continues to handle window messages, so that thread may /// not be blocked by some other activity. All processing related to the progress dialog must be done in /// the event handler. /// /// public void Show () { Show (null); } /// /// Displays the progress dialog as a modeless dialog. /// /// A parameter for use by the background operation to be executed in the event handler. /// /// /// This function will not block the parent window and return immediately. /// /// /// Although this function returns immediately, you cannot use the UI thread to do any processing. The dialog /// will not function correctly unless the UI thread continues to handle window messages, so that thread may /// not be blocked by some other activity. All processing related to the progress dialog must be done in /// the event handler. /// /// public void Show (object argument) { RunProgressDialog (IntPtr.Zero, argument); } /// /// Displays the progress dialog as a modal dialog. /// /// /// /// The ShowDialog function for most .Net dialogs will not return until the dialog is closed. However, /// the function for the class will return immediately. /// The parent window will be disabled as with all modal dialogs. /// /// /// Although this function returns immediately, you cannot use the UI thread to do any processing. The dialog /// will not function correctly unless the UI thread continues to handle window messages, so that thread may /// not be blocked by some other activity. All processing related to the progress dialog must be done in /// the event handler. /// /// /// The progress dialog's window will appear in the taskbar. This behaviour is also contrary to most .Net dialogs, /// but is part of the underlying native progress dialog API so cannot be avoided. /// /// /// When possible, it is recommended that you use a modeless dialog using the function. /// /// public void ShowDialog() { ShowDialog (null, null); } /// /// Displays the progress dialog as a modal dialog. /// /// The window that owns the dialog. /// /// /// The ShowDialog function for most .Net dialogs will not return until the dialog is closed. However, /// the function for the class will return immediately. /// The parent window will be disabled as with all modal dialogs. /// /// /// Although this function returns immediately, you cannot use the UI thread to do any processing. The dialog /// will not function correctly unless the UI thread continues to handle window messages, so that thread may /// not be blocked by some other activity. All processing related to the progress dialog must be done in /// the event handler. /// /// /// The progress dialog's window will appear in the taskbar. This behaviour is also contrary to most .Net dialogs, /// but is part of the underlying native progress dialog API so cannot be avoided. /// /// /// When possible, it is recommended that you use a modeless dialog using the function. /// /// public void ShowDialog (Window owner) { ShowDialog (owner, null); } /// /// Displays the progress dialog as a modal dialog. /// /// The window that owns the dialog. /// A parameter for use by the background operation to be executed in the event handler. /// /// /// The ShowDialog function for most .Net dialogs will not return until the dialog is closed. However, /// the function for the class will return immediately. /// The parent window will be disabled as with all modal dialogs. /// /// /// Although this function returns immediately, you cannot use the UI thread to do any processing. The dialog /// will not function correctly unless the UI thread continues to handle window messages, so that thread may /// not be blocked by some other activity. All processing related to the progress dialog must be done in /// the event handler. /// /// /// The progress dialog's window will appear in the taskbar. This behaviour is also contrary to most .Net dialogs, /// but is part of the underlying native progress dialog API so cannot be avoided. /// /// /// When possible, it is recommended that you use a modeless dialog using the function. /// /// public void ShowDialog (Window owner, object argument) { RunProgressDialog (owner == null ? NativeMethods.GetActiveWindow() : new WindowInteropHelper(owner).Handle, argument); } const int SW_HIDE = 0; const int SW_SHOW = 5; public void Hide () { if (null == _dialog) return; var hwnd = GetWindowHandle(); if (hwnd != IntPtr.Zero) NativeMethods.ShowWindow (hwnd, SW_HIDE); } public void Restore () { if (null == _dialog) return; var hwnd = GetWindowHandle(); if (hwnd != IntPtr.Zero) NativeMethods.ShowWindow (hwnd, SW_SHOW); } /// /// Get win32 handle of the progress dialog window. /// public IntPtr GetWindowHandle () { var ole = _dialog as Interop.IOleWindow; if (null == ole) return IntPtr.Zero; IntPtr hwnd; ole.GetWindow (out hwnd); return hwnd; } /// /// Updates the dialog's progress bar. /// /// The percentage, from 0 to 100, of the operation that is complete. /// /// /// Call this method from the event handler if you want to report progress. /// /// /// This method has no effect is is /// or . /// /// /// is out of range. /// The progress dialog is not currently being displayed. public void ReportProgress (int percentProgress) { ReportProgress (percentProgress, null, null, null); } /// /// Updates the dialog's progress bar. /// /// The percentage, from 0 to 100, of the operation that is complete. /// The new value of the progress dialog's primary text message, or to leave the value unchanged. /// The new value of the progress dialog's additional description message, or to leave the value unchanged. /// Call this method from the event handler if you want to report progress. /// is out of range. /// The progress dialog is not currently being displayed. public void ReportProgress (int percentProgress, string text, string description) { ReportProgress (percentProgress, text, description, null); } /// /// Updates the dialog's progress bar. /// /// The percentage, from 0 to 100, of the operation that is complete. /// The new value of the progress dialog's primary text message, or to leave the value unchanged. /// The new value of the progress dialog's additional description message, or to leave the value unchanged. /// A state object that will be passed to the event handler. /// Call this method from the event handler if you want to report progress. /// is out of range. /// The progress dialog is not currently being displayed. public void ReportProgress (int percentProgress, string text, string description, object userState) { if (percentProgress < 0 || percentProgress > 100) throw new ArgumentOutOfRangeException ("percentProgress"); if (_dialog == null) throw new InvalidOperationException ("The progress dialog is not shown."); _backgroundWorker.ReportProgress (percentProgress, new ProgressChangedData { Text = text, Description = description, UserState = userState }); } /// /// Raises the event. /// /// The containing data for the event. protected virtual void OnDoWork (DoWorkEventArgs e) { var handler = DoWork; if (handler != null) handler (this, e); } /// /// Raises the event. /// /// The containing data for the event. protected virtual void OnRunWorkerCompleted (RunWorkerCompletedEventArgs e) { var handler = RunWorkerCompleted; if (handler != null) handler (this, e); } /// /// Raises the event. /// /// The containing data for the event. protected virtual void OnProgressChanged (ProgressChangedEventArgs e) { var handler = ProgressChanged; if (handler != null) handler (this, e); } private void RunProgressDialog (IntPtr owner, object argument) { if (_backgroundWorker.IsBusy) throw new InvalidOperationException ("The progress dialog is already running."); _cancellationPending = false; _dialog = new Interop.ProgressDialog(); _dialog.SetTitle (WindowTitle); if (CancellationText.Length > 0) _dialog.SetCancelMsg (CancellationText, null); _dialog.SetLine (1, Text, UseCompactPathsForText, IntPtr.Zero); _dialog.SetLine (2, Description, UseCompactPathsForDescription, IntPtr.Zero); var flags = Interop.ProgressDialogFlags.Normal; if (owner != IntPtr.Zero) flags |= Interop.ProgressDialogFlags.Modal; switch (ProgressBarStyle) { case ProgressBarStyle.None: flags |= Interop.ProgressDialogFlags.NoProgressBar; break; case ProgressBarStyle.MarqueeProgressBar: if (NativeMethods.IsWindowsVistaOrLater) flags |= Interop.ProgressDialogFlags.MarqueeProgress; else flags |= Interop.ProgressDialogFlags.NoProgressBar; // Older than Vista doesn't support marquee. break; } if( ShowTimeRemaining ) flags |= Interop.ProgressDialogFlags.AutoTime; if( !ShowCancelButton ) flags |= Interop.ProgressDialogFlags.NoCancel; if( !MinimizeBox ) flags |= Interop.ProgressDialogFlags.NoMinimize; _dialog.StartProgressDialog (owner, null, flags, IntPtr.Zero); _backgroundWorker.RunWorkerAsync (argument); } private void InitializeComponent () { _backgroundWorker = new BackgroundWorker(); _backgroundWorker.WorkerReportsProgress = true; _backgroundWorker.WorkerSupportsCancellation = true; _backgroundWorker.DoWork += new DoWorkEventHandler (_backgroundWorker_DoWork); _backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler (_backgroundWorker_RunWorkerCompleted); _backgroundWorker.ProgressChanged += new ProgressChangedEventHandler (_backgroundWorker_ProgressChanged); } private void _backgroundWorker_DoWork (object sender, DoWorkEventArgs e) { OnDoWork (e); } private void _backgroundWorker_RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e) { _dialog.StopProgressDialog(); Marshal.ReleaseComObject (_dialog); _dialog = null; OnRunWorkerCompleted (new RunWorkerCompletedEventArgs((!e.Cancelled && e.Error == null) ? e.Result : null, e.Error, e.Cancelled)); } private void _backgroundWorker_ProgressChanged (object sender, ProgressChangedEventArgs e) { _cancellationPending = _dialog.HasUserCancelled(); // ReportProgress doesn't allow values outside this range. However, CancellationPending will call // BackgroundWorker.ReportProgress directly with a value that is outside this range to update the value of the property. if (e.ProgressPercentage >= 0 && e.ProgressPercentage <= 100) { _dialog.SetProgress ((uint)e.ProgressPercentage, 100); var data = e.UserState as ProgressChangedData; if (data != null) { if (data.Text != null) Text = data.Text; if (data.Description != null) Description = data.Description; OnProgressChanged (new ProgressChangedEventArgs (e.ProgressPercentage, data.UserState)); } } } } /// /// Indicates the type of progress on a task dialog. /// public enum ProgressBarStyle { /// /// No progress bar is displayed on the dialog. /// None, /// /// A regular progress bar is displayed on the dialog. /// ProgressBar, /// /// A marquee progress bar is displayed on the dialog. Use this value for operations /// that cannot report concrete progress information. /// MarqueeProgressBar } }