Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Catel - Part 4 of n: Unit testing with Catel

, 28 Jan 2011
This article explains how to write unit tests for MVVM using Catel.
Catel-04_01-unittesting.zip
src
Catel.Articles.04 - Unit testing
Models
Properties
Settings.settings
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
ViewModels
Windows
Catel.Articles.04 - Unit testing.Test
Models
Properties
UI
ViewModels
Catel.Articles.Base
Data
Attributes
Properties
Settings.settings
Resources
Images
CatenaLogic.png
Preview.png
Run.png
ShowCode.png
UI
Controls
Helpers
ViewModels
Windows
Catel.Core
Attributes
ClassDiagrams
DataObjectBase.cd
SavableDataObjectBase.cd
Collections
Helpers
ComponentModel
Data
Attributes
Exceptions
Interfaces
Diagnostics
Extensions
Helpers
Exceptions
Helpers
IO
Exceptions
IoC
LLBLGen
Log4net
Appender
Extensions
Helpers
MVVM
Commands
Interfaces
Exceptions
Services
EventArgs
Exceptions
Interfaces
ViewModels
Attributes
Interfaces
Properties
Reflection
Exceptions
Extensions
Helpers
Runtime
Serialization
Attributes
Helpers
Security
Cryptography
Helpers
Catel.Examples.Models
Properties
Catel.Examples.PersonApplication
Properties
Settings.settings
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
ViewModels
Windows
Catel.Examples.Silverlight
Properties
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
Pages
ViewModels
Windows
Catel.Examples.Silverlight.Web
Catel.Examples.Silverlight.Web.csproj.user
ClientBin
Properties
Catel.FxCop
Catel.Silverlight
Diagnostics
log4net
Core
MVVM
Commands
Services
ViewModels
Properties
Catel.Core
Catel.Windows
Reflection
Themes
Generic
Assets
Old
Windows
Controls
Data
Converters
Helpers
Helpers
Catel.Silverlight.Test
Properties
Catel.Silverlight.Test.Web
Catel.Silverlight.Test.Web.csproj.user
ClientBin
Properties
Catel.snk
Catel.Templates.WpfApplication
Properties
Settings.settings
UI
Controls
ViewModels
Windows
Catel.Templates.WpfItemTemplates
Properties
UI
Controls
ViewModels
Windows
Catel.Test
Collections
Convert
Data
Helpers
IO
MVVM
UI
ViewModels
Properties
Reflection
Runtime
Serialization
Security
Cryptography
Test References
Catel.Windows.accessor
Windows
Data
Converters
Catel.vsmdi
Catel.Windows
ClassDiagrams
ViewModelBase.cd
Collections
Extensions
Helpers
MVVM
Commands
Services
Test
UI
ViewModels
Properties
Settings.settings
Resources
Images
Add.png
ClearOutput.png
Edit.png
Error.png
Loading.gif
Preview.png
Remove.png
Save.png
TipOfTheDay.png
Warning.png
Themes
Aero
ExpressionDark
Assets
Generic
Assets
Controls
Jetpack
Assets
background.png
Old
SunnyOrange
Assets
Windows
Controls
Extensions
LinkLabel
StackGrid
Data
Converters
Helpers
Documents
Extensions
Extensions
Helpers
Input
Markup
Media
Effects
EmptyEffect
EmptyEffect.fx
EmptyEffect.ps
EmptyEffect.fx
GrayscaleEffect
GrayscaleEffect.fx
GrayscaleEffect.ps
Extensions
Imaging
Extensions
Windows
DataWindow
TipOfTheDay
Local.testsettings
Settings.StyleCop
TraceAndTestImpact.testsettings
// --------------------------------------------------------------------------------------------------------------------
// <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
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Geert van Horrik
Software Developer CatenaLogic
Netherlands Netherlands
I am Geert van Horrik, and I have studied Computer Science in the Netherlands.
 
I love to write software using .NET (especially the combination of WPF and C#). I am also the lead developer of Catel, an open-source application development framework for WPF, Silverlight, WP7 and WinRT with the focus on MVVM.
 
I have my own company since January 1st 2007, called CatenaLogic. This company develops commercial and non-commercial software.
 
To download (or buy) applications I have written, visit my website: http://www.catenalogic.com
Follow on   Twitter

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 28 Jan 2011
Article Copyright 2011 by Geert van Horrik
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid