#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

415 lines
14 KiB

// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Gui;
namespace ICSharpCode.SharpDevelop.Gui
{
internal sealed partial class AsynchronousWaitDialogForm
{
internal AsynchronousWaitDialogForm(bool allowCancel)
{
InitializeComponent();
cancelButton.Text = ResourceService.GetString("Global.CancelButtonText");
if (allowCancel) {
cancelButton.Visible = true;
progressBar.Width = cancelButton.Left - 8 - progressBar.Left;
} else {
cancelButton.Visible = false;
progressBar.Width = cancelButton.Right - progressBar.Left;
}
}
}
/// <summary>
/// Shows an wait dialog on a separate thread if the action takes longer than 500ms.
/// Usage:
/// using (AsynchronousWaitDialog.ShowWaitDialog("title")) {
/// long_running_action();
/// }
/// or:
/// using (IProgressMonitor monitor = AsynchronousWaitDialog.ShowWaitDialog("title")) {
/// long_running_action(monitor);
/// }
/// </summary>
public sealed class AsynchronousWaitDialog : IProgressMonitor
{
/// <summary>
/// Delay until the wait dialog becomes visible, in ms.
/// </summary>
public const int ShowWaitDialogDelay = 500;
readonly string titleName;
readonly string defaultTaskName;
readonly CancellationTokenSource cancellation;
readonly ProgressCollector collector;
readonly bool runningInOwnThread;
readonly Action<IProgressMonitor> asyncOperation;
readonly SynchronizationHelper synchronizationHelper = new SynchronizationHelper();
AsynchronousWaitDialogForm dlg;
#region Constructors
/// <summary>
/// Shows a wait dialog.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <returns>AsynchronousWaitDialog object - you can use it to access the wait dialog's properties.
/// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns>
public static AsynchronousWaitDialog ShowWaitDialog(string titleName)
{
return ShowWaitDialog(titleName, null, false);
}
/// <summary>
/// Shows a wait dialog.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <param name="allowCancel">Specifies whether a cancel button should be shown.</param>
/// <returns>AsynchronousWaitDialog object - you can use it to access the wait dialog's properties.
/// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns>
public static AsynchronousWaitDialog ShowWaitDialog(string titleName, bool allowCancel)
{
return ShowWaitDialog(titleName, null, allowCancel);
}
/// <summary>
/// Shows a wait dialog that does not support cancelling.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <param name="allowCancel">Specifies whether a cancel button should be shown.</param>
/// <param name="defaultTaskName">The default description text, if no named task is active.</param>
/// <returns>AsynchronousWaitDialog object - you can use it to access the wait dialog's properties.
/// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns>
public static AsynchronousWaitDialog ShowWaitDialog(string titleName, string defaultTaskName, bool allowCancel)
{
if (titleName == null)
throw new ArgumentNullException("titleName");
AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, defaultTaskName, allowCancel, null);
h.StartInThread();
return h;
}
/// <summary>
/// Shows a wait dialog that does supports cancelling.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <param name="defaultTaskName">The default description text, if no named task is active.</param>
/// <param name="action">The action to run within the wait dialog</param>
public static void RunInCancellableWaitDialog(string titleName, string defaultTaskName, Action<AsynchronousWaitDialog> action)
{
if (titleName == null)
throw new ArgumentNullException("titleName");
using (AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, defaultTaskName, true, null)) {
h.StartInThread();
try {
action(h);
} catch (OperationCanceledException ex) {
// consume OperationCanceledException
if (ex.CancellationToken != h.CancellationToken)
throw;
}
}
}
/// <summary>
/// Shows a wait dialog that does not support cancelling and waits for an asynchronous operation to end.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <param name="asyncOperation">Asynchronous operation to be executed.</param>
public static void ShowWaitDialogForAsyncOperation(string titleName, Action<IProgressMonitor> asyncOperation)
{
ShowWaitDialogForAsyncOperation(titleName, null, asyncOperation);
}
/// <summary>
/// Shows a wait dialog that does not support cancelling and waits for an asynchronous operation to end.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <param name="allowCancel">Specifies whether a cancel button should be shown.</param>
/// <param name="defaultTaskName">The default description text, if no named task is active.</param>
/// <param name="asyncOperation">Asynchronous operation to be executed.</param>
/// <returns>AsynchronousWaitDialog object - you can use it to access the wait dialog's properties.
/// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns>
public static void ShowWaitDialogForAsyncOperation(string titleName, string defaultTaskName, Action<IProgressMonitor> asyncOperation)
{
if (titleName == null)
throw new ArgumentNullException("titleName");
AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, defaultTaskName, false, asyncOperation);
h.StartWithAsyncOperation();
}
private AsynchronousWaitDialog(string titleName, string defaultTaskName, bool allowCancel, Action<IProgressMonitor> asyncOperation)
{
this.titleName = StringParser.Parse(titleName);
this.defaultTaskName = StringParser.Parse(defaultTaskName ?? "${res:Global.PleaseWait}");
if (allowCancel)
this.cancellation = new CancellationTokenSource();
this.asyncOperation = asyncOperation;
this.runningInOwnThread = (asyncOperation == null);
this.collector = new ProgressCollector(synchronizationHelper, allowCancel ? cancellation.Token : CancellationToken.None);
}
#endregion
#region SynchronizationHelper
// this class works around the issue that we don't initially have an ISynchronizeInvoke implementation
// for the target thread, we only create it after ShowWaitDialogDelay.
sealed class SynchronizationHelper : ISynchronizeInvoke
{
volatile ISynchronizeInvoke targetSynchronizeInvoke;
public bool InvokeRequired {
get {
ISynchronizeInvoke si = targetSynchronizeInvoke;
return si != null && si.InvokeRequired;
}
}
public IAsyncResult BeginInvoke(Delegate method, object[] args)
{
ISynchronizeInvoke si = targetSynchronizeInvoke;
if (si != null) {
var winForm = si as System.Windows.Forms.Form;
if ((winForm == null) || !winForm.IsDisposed)
return si.BeginInvoke(method, args);
}
// When target is not available, invoke method on current thread, but use a lock
// to ensure we don't run multiple methods concurrently.
lock (this) {
method.DynamicInvoke(args);
return null;
}
// yes this is morally questionable - maybe it would be better to enqueue all invocations and run them later?
// ProgressCollector would have to avoid enqueuing stuff multiple times for all kinds of updates
// (currently it does this only with updates to the Progress property)
}
public void SetTarget(ISynchronizeInvoke targetSynchronizeInvoke)
{
lock (this) {
this.targetSynchronizeInvoke = targetSynchronizeInvoke;
}
}
public object EndInvoke(IAsyncResult result)
{
throw new NotSupportedException();
}
public object Invoke(Delegate method, object[] args)
{
throw new NotSupportedException();
}
}
#endregion
#region Start waiting thread
/// <summary>
/// Start waiting thread
/// </summary>
internal void StartInThread()
{
Thread newThread = new Thread(Run);
newThread.Name = "AsynchronousWaitDialog thread";
newThread.Start();
Thread.Sleep(0); // allow new thread to start
}
[STAThread]
void Run()
{
Thread.Sleep(ShowWaitDialogDelay);
if (CreateDialogForm()) {
if (collector.ShowingDialog) {
Application.Run();
} else {
Application.Run(dlg);
}
}
}
#endregion
internal void StartWithAsyncOperation()
{
// Start asynchronous operation
if (asyncOperation == null)
return;
asyncOperation(collector.ProgressMonitor);
// Wait delay before showing dialog
var timer = new System.Windows.Forms.Timer();
timer.Interval = ShowWaitDialogDelay;
timer.Tick += (sender, e) =>
{
timer.Stop();
timer.Dispose();
// Create and show dialog
if (CreateDialogForm())
dlg.ShowDialog();
};
timer.Start();
}
bool CreateDialogForm()
{
if (collector.ProgressMonitorIsDisposed)
return false;
dlg = new AsynchronousWaitDialogForm(cancellation != null);
dlg.Text = titleName;
dlg.cancelButton.Click += CancelButtonClick;
dlg.CreateControl();
IntPtr h = dlg.Handle; // force handle creation
// ensure events occur on this thread, then register event handlers
synchronizationHelper.SetTarget(dlg);
collector.ProgressMonitorDisposed += progress_ProgressMonitorDisposed;
collector.PropertyChanged += progress_PropertyChanged;
// check IsDisposed once again (we might have missed an event while we initialized the dialog):
if (collector.ProgressMonitorIsDisposed) {
dlg.Dispose();
return false;
}
progress_PropertyChanged(null, new System.ComponentModel.PropertyChangedEventArgs("TaskName"));
return true;
}
/// <summary>
/// Closes the wait dialog.
/// </summary>
void progress_ProgressMonitorDisposed(object sender, EventArgs e)
{
dlg.Dispose();
if (runningInOwnThread)
Application.ExitThread();
}
bool reshowTimerRunning = false;
void progress_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// show/hide dialog as required by ShowingDialog
if (dlg.Visible == collector.ShowingDialog) {
if (collector.ShowingDialog) {
dlg.Hide();
} else if (!reshowTimerRunning) {
reshowTimerRunning = true;
var timer = new System.Windows.Forms.Timer();
timer.Interval = 100;
timer.Tick += delegate {
timer.Dispose();
reshowTimerRunning = false;
if (!collector.ShowingDialog) {
if (runningInOwnThread)
dlg.Show();
else
if (!dlg.Visible)
dlg.ShowDialog();
}
};
timer.Start();
}
}
// update task name and progress
if (e.PropertyName == "TaskName")
dlg.taskLabel.Text = collector.TaskName ?? defaultTaskName;
if (double.IsNaN(collector.Progress)) {
dlg.progressBar.Style = ProgressBarStyle.Marquee;
} else {
dlg.progressBar.Style = ProgressBarStyle.Continuous;
dlg.progressBar.Value = Math.Max(0, Math.Min(100, (int)(collector.Progress * 100)));
}
}
void CancelButtonClick(object sender, EventArgs e)
{
dlg.cancelButton.Enabled = false;
if (cancellation != null)
cancellation.Cancel();
}
#region IProgressMonitor interface impl (forwards to progress.ProgressMonitor)
/// <inheritdoc/>
public string TaskName {
get { return collector.ProgressMonitor.TaskName; }
set { collector.ProgressMonitor.TaskName = value; }
}
/// <inheritdoc/>
public double Progress {
get { return collector.ProgressMonitor.Progress; }
set { collector.ProgressMonitor.Progress = value; }
}
void IProgress<double>.Report(double value)
{
this.Progress = value;
}
/// <inheritdoc/>
public bool ShowingDialog {
get { return collector.ProgressMonitor.ShowingDialog; }
set { collector.ProgressMonitor.ShowingDialog = value; }
}
/// <inheritdoc/>
public OperationStatus Status {
get { return collector.ProgressMonitor.Status; }
set { collector.ProgressMonitor.Status = value; }
}
/// <inheritdoc/>
public CancellationToken CancellationToken {
get { return collector.ProgressMonitor.CancellationToken; }
}
/// <inheritdoc/>
public IProgressMonitor CreateSubTask(double workAmount)
{
return collector.ProgressMonitor.CreateSubTask(workAmount);
}
/// <inheritdoc/>
public IProgressMonitor CreateSubTask(double workAmount, CancellationToken cancellationToken)
{
return collector.ProgressMonitor.CreateSubTask(workAmount, cancellationToken);
}
public void Dispose()
{
collector.ProgressMonitor.Dispose();
}
#endregion
}
}