// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PleaseWaitWindow.xaml.cs" company="Catel development team">
// Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
// Please wait window to show a please wait window with the option to customize the text.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using Catel.MVVM.Services;
using Catel.Windows.Properties;
using log4net;
namespace Catel.Windows
{
/// <summary>
/// Please wait window to show a please wait window with the option to customize the text.
/// </summary>
/// <remarks>
/// Parts of this code comes from this blog: http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx
/// </remarks>
public partial class PleaseWaitWindow
{
#region Variables
private static readonly AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
private Thread _thread;
private readonly Storyboard _dimmStoryboard = new Storyboard();
private const double DimmedValue = 0.75d;
private readonly Storyboard _undimmStoryboard = new Storyboard();
private const double UndimmedValue = 1d;
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new instance of the <see cref="PleaseWaitWindow"/> class.
/// </summary>
public PleaseWaitWindow()
: this(Properties.Resources.PleaseWait) { }
/// <summary>
/// Initializes a please wait window with default text.
/// </summary>
/// <param name="text">Text to display in the window.</param>
public PleaseWaitWindow(string text)
{
InitializeComponent();
DoubleAnimation dimmAnimation = new DoubleAnimation(DimmedValue, new Duration(new TimeSpan(0, 0, 0, 0, 200)));
dimmAnimation.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Opacity"));
_dimmStoryboard.Children.Add(dimmAnimation);
DoubleAnimation undimmAnimation = new DoubleAnimation(UndimmedValue, new Duration(new TimeSpan(0, 0, 0, 0, 200)));
undimmAnimation.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Opacity"));
_undimmStoryboard.Children.Add(undimmAnimation);
_dimmStoryboard.Completed += delegate { IsOwnerDimmed = true; };
_undimmStoryboard.Completed += delegate
{
IsOwnerDimmed = false;
Owner.IsHitTestVisible = true;
};
this.SetOwnerWindow();
Loaded += OnLoaded;
Text = text;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets Text.
/// </summary>
/// <remarks>
/// Wrapper for the Text dependency property.
/// </remarks>
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// DependencyProperty definition as the backing store for Text.
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(PleaseWaitWindow), new UIPropertyMetadata(Properties.Resources.PleaseWait));
#endregion
#region Methods
/// <summary>
/// Invoked whenever the effective value of any dependency property on this <see cref="T:System.Windows.FrameworkElement"/> has been updated. The specific dependency property that changed is reported in the arguments parameter. Overrides <see cref="M:System.Windows.DependencyObject.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs)"/>.
/// </summary>
/// <param name="e">The event data that describes the property that changed, as well as old and new values.</param>
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property.Name == VisibilityProperty.Name)
{
switch (Visibility)
{
case Visibility.Visible:
if (Owner != null)
{
Left = (Owner.Left + (Owner.ActualWidth / 2)) - (ActualWidth / 2);
Top = (Owner.Top + (Owner.ActualHeight / 2)) - (ActualHeight / 2);
}
ChangeOwnerDimming(true);
break;
case Visibility.Hidden:
case Visibility.Collapsed:
ChangeOwnerDimming(false);
break;
}
}
}
/// <summary>
/// Gets or sets a value indicating whether this instance is dimmed.
/// </summary>
/// <value><c>true</c> if this instance is dimmed; otherwise, <c>false</c>.</value>
public bool IsOwnerDimmed { get; private set; }
/// <summary>
/// Changes the owner dimming.
/// </summary>
/// <param name="dimm">if set to <c>true</c>, the owner should be dimmed.</param>
private void ChangeOwnerDimming(bool dimm)
{
if (Owner != null)
{
Owner.IsHitTestVisible = false;
Owner.BeginStoryboard(dimm ? _dimmStoryboard : _undimmStoryboard);
}
}
/// <summary>
/// Called when the window is loaded.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
private void OnLoaded(object sender, RoutedEventArgs e)
{
visualWrapper.Child = CreateMediaElementOnWorkerThread();
}
/// <summary>
/// Raises the <see cref="E:System.Windows.Window.Closing"/> event.
/// </summary>
/// <param name="e">A <see cref="T:System.ComponentModel.CancelEventArgs"/> that contains the event data.</param>
protected override void OnClosing(CancelEventArgs e)
{
if (_thread != null)
{
_thread.Abort();
_thread = null;
}
}
/// <summary>
/// Creates the media element on worker thread.
/// </summary>
/// <returns></returns>
private HostVisual CreateMediaElementOnWorkerThread()
{
var hostVisual = new HostVisual();
_thread = new Thread(MediaWorkerThread);
_thread.SetApartmentState(ApartmentState.STA);
_thread.IsBackground = true;
_thread.Start(hostVisual);
_autoResetEvent.WaitOne();
return hostVisual;
}
/// <summary>
/// Medias the worker thread.
/// </summary>
/// <param name="arg">The arg.</param>
private static void MediaWorkerThread(object arg)
{
try
{
var hostVisual = (HostVisual)arg;
var visualTargetPS = new VisualTargetPresentationSource(hostVisual);
_autoResetEvent.Set();
visualTargetPS.RootVisual = CreateMediaElement();
Dispatcher.Run();
}
catch
{
}
}
/// <summary>
/// Creates the media element.
/// </summary>
/// <returns></returns>
private static FrameworkElement CreateMediaElement()
{
return new LoaderAnimation { Width = 32, Height = 32 };
}
#endregion
}
/// <summary>
/// PleaseWait window Helper class.
/// </summary>
public class PleaseWaitHelper
{
#region Variables
/// <summary>
/// The <see cref="ILog">log</see> object.
/// </summary>
private static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private static PleaseWaitHelper _instance;
private static readonly object _padlock = new object();
private static readonly object _visibleStopwatchLock = new object();
private static Stopwatch _visibleStopwatch;
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new instance of the <see cref="PleaseWaitHelper"/> class.
/// </summary>
private PleaseWaitHelper()
{
PleaseWaitVisible = false;
PleaseWaitWindow = new PleaseWaitWindow();
MinimumDurationBeforeShow = 500;
MinimumShowTime = 1000;
}
#endregion
#region Delegates
/// <summary>
/// Delegate that allows this class to re-invoke the HideWindow method.
/// </summary>
protected delegate void HideWindowDelegate();
/// <summary>
/// Delegate to update the status text of the <see cref="PleaseWaitWindow"/>.
/// </summary>
private delegate void UpdateStatusTextDelegate(string text, double windowWidth);
#endregion
#region Properties
/// <summary>
/// Gets the instance of this singleton class.
/// </summary>
protected static PleaseWaitHelper Instance
{
get
{
lock (_padlock)
{
if (_instance == null)
{
_instance = new PleaseWaitHelper();
}
return _instance;
}
}
}
/// <summary>
/// Gets or sets the <see cref="PleaseWaitWindow"/> instance.
/// </summary>
private PleaseWaitWindow PleaseWaitWindow { get; set; }
/// <summary>
/// Gets whether the Please Wait window is visible without checking the actual window itself.
/// </summary>
protected bool PleaseWaitVisible { get; private set; }
/// <summary>
/// Gets or sets the minimum duration in milliseconds that an operation must take before the window is actually shown.
/// </summary>
/// <value>The minimum duration in milliseconds that an operation must take before the window is actually shown.</value>
public static int MinimumDurationBeforeShow { get; set; }
/// <summary>
/// Gets or sets the minimum show time in milliseconds.
/// </summary>
/// <value>The minimum show time in milliseconds.</value>
public static int MinimumShowTime { get; set; }
#endregion
#region Methods
/// <summary>
/// Shows the please wait window with the default status text.
/// </summary>
/// <remarks>
/// When this method is used, the <see cref="Hide"/> method must be called to hide the window again.
/// </remarks>
public static void Show()
{
Show(Resources.PleaseWait);
}
/// <summary>
/// Shows the please wait window with the specified status text.
/// </summary>
/// <param name="status">The status.</param>
/// <remarks>
/// When this method is used, the <see cref="Hide"/> method must be called to hide the window again.
/// </remarks>
public static void Show(string status)
{
UpdateStatus(status);
Instance.ShowWindow();
}
/// <summary>
/// Shows the please wait window with the default status text and executes the work delegate (in a background thread). When the work
/// is finished, the please wait window will be automatically closed.
/// </summary>
/// <param name="workDelegate">The work delegate.</param>
public static void Show(PleaseWaitWorkDelegate workDelegate)
{
Show(workDelegate, null, Resources.PleaseWait, double.NaN);
}
/// <summary>
/// Shows the please wait window with the default status text and executes the work delegate (in a background thread). When the work
/// is finished, the please wait window will be automatically closed. This method will also subscribe to the
/// <see cref="BackgroundWorker.RunWorkerCompleted"/> event.
/// </summary>
/// <param name="workDelegate">The work delegate.</param>
/// <param name="runWorkerCompletedDelegate">The run worker completed delegate.</param>
public static void Show(PleaseWaitWorkDelegate workDelegate, RunWorkerCompletedEventHandler runWorkerCompletedDelegate)
{
Show(workDelegate, runWorkerCompletedDelegate, Resources.PleaseWait, double.NaN);
}
/// <summary>
/// Shows the please wait window with the specified status text and executes the work delegate (in a background thread). When the work
/// is finished, the please wait window will be automatically closed.
/// </summary>
/// <param name="workDelegate">The work delegate.</param>
/// <param name="status">The status.</param>
public static void Show(PleaseWaitWorkDelegate workDelegate, string status)
{
Show(workDelegate, null, status, double.NaN);
}
/// <summary>
/// Shows the please wait window with the specified status text and executes the work delegate (in a background thread). When the work
/// is finished, the please wait window will be automatically closed.
/// </summary>
/// <param name="workDelegate">The work delegate.</param>
/// <param name="status">The status.</param>
/// <param name="windowWidth">Width of the window.</param>
public static void Show(PleaseWaitWorkDelegate workDelegate, string status, double windowWidth)
{
Show(workDelegate, null, status, windowWidth);
}
/// <summary>
/// Allows the completion of any UI processing
/// </summary>
private static void DoEvents()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { }));
}
/// <summary>
/// Shows the please wait window with the default status text and executes the work delegate (in a background thread). When the work
/// is finished, the please wait window will be automatically closed. This method will also subscribe to the
/// <see cref="BackgroundWorker.RunWorkerCompleted"/> event.
/// </summary>
/// <param name="workDelegate">The work delegate.</param>
/// <param name="runWorkerCompletedDelegate">The run worker completed delegate.</param>
/// <param name="status">The status.</param>
/// <param name="windowWidth">Width of the window.</param>
public static void Show(PleaseWaitWorkDelegate workDelegate, RunWorkerCompletedEventHandler runWorkerCompletedDelegate, string status, double windowWidth)
{
UpdateStatus(status, windowWidth);
Instance.ShowWindow();
if (workDelegate != null)
{
while (!Instance.PleaseWaitWindow.IsOwnerDimmed)
{
DoEvents();
}
workDelegate();
lock (_visibleStopwatchLock)
{
// Make sure the window is shown for a minimum duration
int milliSecondsLeftToShow = 0;
if (_visibleStopwatch != null)
{
_visibleStopwatch.Stop();
milliSecondsLeftToShow = MinimumShowTime - (int)_visibleStopwatch.ElapsedMilliseconds;
_visibleStopwatch = null;
}
if (milliSecondsLeftToShow > 0)
{
Thread.Sleep(milliSecondsLeftToShow);
}
}
}
if (runWorkerCompletedDelegate != null)
{
runWorkerCompletedDelegate(null, null);
}
Hide();
}
/// <summary>
/// Updates the status.
/// </summary>
/// <param name="status">The status.</param>
public static void UpdateStatus(string status)
{
Instance.UpdateStatusText(status, double.NaN);
}
/// <summary>
/// Updates the status text.
/// </summary>
/// <param name="status">The status.</param>
/// <param name="width">The width.</param>
public static void UpdateStatus(string status, double width)
{
Instance.UpdateStatusText(status, width);
}
/// <summary>
/// Hides the Please Wait window.
/// </summary>
public static void Hide()
{
Instance.HideWindow();
}
/// <summary>
/// Updates the status text.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="windowWidth">Width of the window.</param>
private void UpdateStatusText(string text, double windowWidth)
{
if (!PleaseWaitWindow.Dispatcher.CheckAccess())
{
PleaseWaitWindow.Dispatcher.Invoke(new UpdateStatusTextDelegate(UpdateStatusText), new object[] { text, windowWidth });
return;
}
PleaseWaitWindow.Text = text;
PleaseWaitWindow.MinWidth = double.IsNaN(windowWidth) ? 0d : windowWidth;
// Update the layout to prevent resizing while showing
PleaseWaitWindow.UpdateLayout();
}
/// <summary>
/// Shows the window delayed by using the <see cref="MinimumDurationBeforeShow"/>.
/// </summary>
private void ShowWindow()
{
PleaseWaitVisible = true;
Log.Debug(TraceMessages.ShowingPleaseWaitWindow);
PleaseWaitWindow.Show();
lock (_visibleStopwatchLock)
{
if (_visibleStopwatch == null)
{
_visibleStopwatch = new Stopwatch();
_visibleStopwatch.Start();
}
else
{
_visibleStopwatch.Reset();
_visibleStopwatch.Start();
}
}
}
/// <summary>
/// Hides the window.
/// </summary>
private void HideWindow()
{
if (!PleaseWaitVisible)
{
return;
}
if (!PleaseWaitWindow.Dispatcher.CheckAccess())
{
PleaseWaitWindow.Dispatcher.Invoke((HideWindowDelegate)HideWindow, new object[] { });
return;
}
PleaseWaitVisible = false;
PleaseWaitWindow.MinWidth = 0;
PleaseWaitWindow.Width = double.NaN;
// Update the layout to prevent resizing while showing
PleaseWaitWindow.UpdateLayout();
Log.Debug(TraceMessages.HidingPleaseWaitWindow);
PleaseWaitWindow.Hide();
}
#endregion
}
}