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

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="DataWindow.cs" company="Catel development team">
//   Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
//   Mode of the <see cref="DataWindow" />.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using Catel.MVVM.UI;
using Catel.Windows.Controls;
using Catel.MVVM;

namespace Catel.Windows
{
    /// <summary>
    /// Mode of the <see cref="DataWindow"/>.
    /// </summary>
    public enum DataWindowMode
    {
        /// <summary>
        /// Window contains OK and Cancel buttons.
        /// </summary>
        OkCancel,

        /// <summary>
        /// Window contains OK, Cancel and Apply buttons.
        /// </summary>
        OkCancelApply,

        /// <summary>
        /// Window contains Close button.
        /// </summary>
        Close,

        /// <summary>
        /// Window contains custom buttons.
        /// </summary>
        Custom
    }

    /// <summary>
    /// Available default buttons on the data window mode.
    /// </summary>
    public enum DataWindowDefaultButton
    {
        /// <summary>
        /// OK button.
        /// </summary>
        OK,

        /// <summary>
        /// Apply button.
        /// </summary>
        Apply,

        /// <summary>
        /// Close button.
        /// </summary>
        Close,

        /// <summary>
        /// No button.
        /// </summary>
        None
    }

    /// <summary>
    /// <see cref="Window"/> class that implements the <see cref="InfoBarMessageControl"/> and
    /// the default buttons, according to the <see cref="DataWindowMode"/>.
    /// </summary>
    public abstract class DataWindow
#if SILVERLIGHT
 : ChildWindow
#else
        : Window
#endif
    {
        #region Constants
        /// <summary>
        /// Offset of the window to the sides of the primary monitor.
        /// </summary>
        private const int Offset = 50;
        #endregion

        #region Variables
        private readonly Collection<DataWindowButton> _buttons = new Collection<DataWindowButton>();
        private readonly Collection<ICommand> _commands = new Collection<ICommand>();
        #endregion

        #region Constructor & destructor
        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> with the <see cref="DataWindowMode.OkCancel"/> mode.
        /// </summary>
        protected DataWindow()
            : this(DataWindowMode.OkCancel) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        protected DataWindow(DataWindowMode mode)
            : this(mode, null) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        protected DataWindow(DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons)
            : this(mode, additionalButtons, DataWindowDefaultButton.OK) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(DataWindowMode mode, bool setOwnerAndFocus)
            : this(mode, null, setOwnerAndFocus) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons, bool setOwnerAndFocus)
            : this(mode, additionalButtons, DataWindowDefaultButton.OK, setOwnerAndFocus) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="defaultButton">The default button.</param>
        protected DataWindow(DataWindowMode mode, DataWindowDefaultButton defaultButton)
            : this(mode, null, defaultButton) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        /// <param name="defaultButton">The default button.</param>
        protected DataWindow(DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons, DataWindowDefaultButton defaultButton)
            : this(mode, additionalButtons, defaultButton, true) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="defaultButton">The default button.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(DataWindowMode mode, DataWindowDefaultButton defaultButton, bool setOwnerAndFocus)
            : this(mode, null, defaultButton, setOwnerAndFocus) { }

        /// <summary>
        /// Initializes a new instance of this class with custom commands.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        /// <param name="defaultButton">The default button.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons, DataWindowDefaultButton defaultButton, bool setOwnerAndFocus)
        {
            if (DesignerProperties.GetIsInDesignMode(this)) return;

            // Set window style (WPF doesn't allow styling on root elements of XAML files, too bad)
            // For more info, see http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/3059c0e4-c372-4da2-b384-28f271feef05/
#if SILVERLIGHT
            Style = Resources[typeof(Window)] as Style;
#else

            SetResourceReference(StyleProperty, typeof(Window));
#endif

            Mode = mode;
            DefaultButton = defaultButton;

#if !SILVERLIGHT
            SizeToContent = SizeToContent.WidthAndHeight;
            ShowInTaskbar = false;
            ResizeMode = ResizeMode.NoResize;
            WindowStartupLocation = WindowStartupLocation.CenterOwner;

            SnapsToDevicePixels = true;

            MaxWidth = SystemParameters.PrimaryScreenWidth - Offset;
            MaxHeight = SystemParameters.PrimaryScreenHeight - Offset;
#endif

            if (additionalButtons != null)
            {
                foreach (var button in additionalButtons)
                {
                    _buttons.Add(button);
                }
            }

            CanClose = true;

            Loaded += (sender, e) => Initialize();
            Closing += OnDataWindowClosing;
            Loaded += delegate { OnLoaded(); };
            Unloaded += delegate { OnUnloaded(); };

#if !SILVERLIGHT
            if (setOwnerAndFocus)
            {
                this.SetOwnerWindowAndFocus();
            }
            else
            {
                this.FocusFirstControl();
            }
#endif
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="buttons">The buttons.</param>
        protected DataWindow(IEnumerable<DataWindowButton> buttons)
            : this(buttons, true) { }

        /// <summary>
        /// Initializes a new instance of this class with custom commands.
        /// </summary>
        /// <param name="buttons">The buttons.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(IEnumerable<DataWindowButton> buttons, bool setOwnerAndFocus)
            : this(DataWindowMode.Custom, buttons, DataWindowDefaultButton.None, setOwnerAndFocus) { }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the <see cref="DataWindowMode"/> that this window uses.
        /// </summary>
        protected DataWindowMode Mode { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether this instance can close.
        /// </summary>
        /// <value><c>true</c> if this instance can close; otherwise, <c>false</c>.</value>
        protected bool CanClose { get; set; }

        /// <summary>
        /// Gets the commands that are currently available on the data window.
        /// </summary>
        /// <value>The commands.</value>
        protected ReadOnlyCollection<ICommand> Commands { get { return new ReadOnlyCollection<ICommand>(_commands); } }

        /// <summary>
        /// Gets the default button.
        /// </summary>
        /// <value>The default button.</value>
        protected DataWindowDefaultButton DefaultButton { get; private set; }

        /// <summary>
        /// Gets a value indicating whether this instance is OK button available.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is OK button available; otherwise, <c>false</c>.
        /// </value>
        private bool IsOKButtonAvailable
        {
            get
            {
                if (Mode == DataWindowMode.OkCancel || Mode == DataWindowMode.OkCancelApply)
                {
                    return true;
                }

                return false;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is cancel button available.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is cancel button available; otherwise, <c>false</c>.
        /// </value>
        private bool IsCancelButtonAvailable
        {
            get
            {
                if (Mode == DataWindowMode.OkCancel || Mode == DataWindowMode.OkCancelApply)
                {
                    return true;
                }

                return false;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is apply button available.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is apply button available; otherwise, <c>false</c>.
        /// </value>
        private bool IsApplyButtonAvailable
        {
            get
            {
                if (Mode == DataWindowMode.OkCancelApply)
                {
                    return true;
                }

                return false;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is close button available.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is close button available; otherwise, <c>false</c>.
        /// </value>
        private bool IsCloseButtonAvailable
        {
            get
            {
                if (Mode == DataWindowMode.Close)
                {
                    return true;
                }

                return false;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the window was closed by a 'user'-button.
        /// </summary>
        /// <value><c>true</c> if closed by button; otherwise, <c>false</c>.</value>
        private bool ClosedByButton { get; set; }

        /// <summary>
        /// Gets the internal grid. This control gives internal classes a change to add additional controls to
        /// the dynamically created grid.
        /// </summary>
        /// <value>The internal grid.</value>
        internal Grid InternalGrid { get; private set; }
        #endregion

        #region Commands
        /// <summary>
        /// Executes the OK command without a parameter.
        /// </summary>
        protected void ExecuteOK()
        {
            ExecuteOK(null);
        }

        /// <summary>
        /// Executes the OK command with a parameter.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        protected void ExecuteOK(object parameter)
        {
            if (OK_CanExecute(parameter))
            {
                OK_Executed(parameter);
            }
        }

        /// <summary>
        /// Determines whether the user can execute the OK command.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns><c>true</c> if the command can be executed; otherwise <c>false</c>.</returns>
        private bool OK_CanExecute(object parameter)
        {
            return ValidateData();
        }

        /// <summary>
        /// Handled when the user invokes the OK command.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        private void OK_Executed(object parameter)
        {
            if (!ApplyChanges())
            {
                return;
            }

            ClosedByButton = true;
            DialogResult = true;
        }

        /// <summary>
        /// Executes the Cancel command without a parameter.
        /// </summary>
        protected void ExecuteCancel()
        {
            ExecuteCancel(null);
        }

        /// <summary>
        /// Executes the Cancel command with a parameter.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        protected void ExecuteCancel(object parameter)
        {
            if (Cancel_CanExecute(parameter))
            {
                Cancel_Executed(parameter);
            }
        }

        /// <summary>
        /// Determines whether the user can execute the Cancel command.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns><c>true</c> if the command can be executed; otherwise <c>false</c>.</returns>
        private bool Cancel_CanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        /// Handled when the user invokes the Cancel command.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        private void Cancel_Executed(object parameter)
        {
            DiscardChanges();

            ClosedByButton = true;
            DialogResult = false;
        }

        /// <summary>
        /// Executes the Apply command without a parameter.
        /// </summary>
        protected void ExecuteApply()
        {
            ExecuteApply(null);
        }

        /// <summary>
        /// Executes the Apply command with a parameter.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        protected void ExecuteApply(object parameter)
        {
            if (Apply_CanExecute(parameter))
            {
                Apply_Executed(parameter);
            }
        }

        /// <summary>
        /// Determines whether the user can execute the Apply command.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns><c>true</c> if the command can be executed; otherwise <c>false</c>.</returns>
        private bool Apply_CanExecute(object parameter)
        {
            return ValidateData();
        }

        /// <summary>
        /// Handled when the user invokes the Apply command.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        private void Apply_Executed(object parameter)
        {
            ApplyChanges();
        }

        /// <summary>
        /// Executes the Close command without a parameter.
        /// </summary>
        protected void ExecuteClose()
        {
            ExecuteClose(null);
        }

        /// <summary>
        /// Executes the Close command with a parameter.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        protected void ExecuteClose(object parameter)
        {
            if (Close_CanExecute(parameter))
            {
                Close_Executed(parameter);
            }
        }

        /// <summary>
        /// Determines whether the user can execute the Close command.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns><c>true</c> if the command can be executed; otherwise <c>false</c>.</returns>
        private bool Close_CanExecute(object parameter)
        {
            return CanClose;
        }

        /// <summary>
        /// Handled when the user invokes the Close command.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        private void Close_Executed(object parameter)
        {
            Close();
        }
        #endregion

        #region Methods
        /// <summary>
        /// Invoked when the content of this control has been changed. This method will add the dynamic controls automatically.
        /// </summary>
        /// <param name="oldContent">Old content.</param>
        /// <param name="newContent">New content.</param>
        protected override void OnContentChanged(object oldContent, object newContent)
        {
            base.OnContentChanged(oldContent, newContent);

            if (!WrapControlHelper.CanBeWrapped(newContent as FrameworkElement))
            {
                return;
            }

            if (IsOKButtonAvailable)
            {
                var button = new DataWindowButton(Properties.Resources.CommandWindowOK, OK_Executed, OK_CanExecute) { IsDefault = true };
                button.IsDefault = (DefaultButton == DataWindowDefaultButton.OK);
                _buttons.Add(button);
            }
            if (IsCancelButtonAvailable)
            {
                var button = new DataWindowButton(Properties.Resources.CommandWindowCancel, Cancel_Executed, Cancel_CanExecute);
                button.IsCancel = true;
                _buttons.Add(button);
            }
            if (IsApplyButtonAvailable)
            {
                var button = new DataWindowButton(Properties.Resources.CommandWindowApply, Apply_Executed, Apply_CanExecute);
                button.IsDefault = (DefaultButton == DataWindowDefaultButton.Apply);
                _buttons.Add(button);
            }
            if (IsCloseButtonAvailable)
            {
                var button = new DataWindowButton(Properties.Resources.CommandWindowClose, Close_Executed, Close_CanExecute);
                button.IsDefault = (DefaultButton == DataWindowDefaultButton.Apply);
                _buttons.Add(button);
            }

            foreach (DataWindowButton button in _buttons)
            {
                _commands.Add(button.Command);
            }

            Grid contentGrid = WrapControlHelper.Wrap((FrameworkElement)newContent, WrapOptions.All, _buttons.ToArray(), this);

            Grid internalGrid = contentGrid.FindVisualDescendant(obj => (obj is FrameworkElement) && (((FrameworkElement)obj).Name == WrapControlHelper.InternalGridName)) as Grid;
            if (internalGrid != null)
            {
#if SILVERLIGHT
                internalGrid.Style = Application.Current.Resources["WindowGridStyle"] as Style;
#else
                internalGrid.SetResourceReference(StyleProperty, "WindowGridStyle");
#endif

                InternalGrid = internalGrid;

                OnInternalGridChanged();
            }
        }

        /// <summary>
        /// Called when the internal grid has changed.
        /// </summary>
        /// <remarks>
        /// This method is only invoked when the grid is set, not when the grid is cleared (which is something that should never happen).
        /// </remarks>
        protected virtual void OnInternalGridChanged() { }

        /// <summary>
        /// Called when the <see cref="DataWindow"/> is loaded.
        /// </summary>
        protected virtual void OnLoaded() { }

        /// <summary>
        /// Called when the <see cref="DataWindow"/> is unloaded.
        /// </summary>
        protected virtual void OnUnloaded() { }

        /// <summary>
        /// Handles the Closing event of the DataWindow control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        private void OnDataWindowClosing(object sender, CancelEventArgs args)
        {
            if (!ClosedByButton)
            {
                DiscardChanges();
            }

            AutoDisposeHelper.AutoDisposeProperties(this);
        }

        /// <summary>
        /// Initializes the window.
        /// </summary>
        protected virtual void Initialize()
        { }

        /// <summary>
        /// Validates the data.
        /// </summary>
        /// <returns>True if successful, otherwise false.</returns>
        protected virtual bool ValidateData()
        {
            return true;
        }

        /// <summary>
        /// Applies all changes made by this window.
        /// </summary>
        /// <returns>True if successful, otherwise false.</returns>
        protected virtual bool ApplyChanges()
        {
            return true;
        }

        /// <summary>
        /// Discards all changes made by this window.
        /// </summary>
        protected virtual void DiscardChanges()
        { }
        #endregion
    }

    /// <summary>
    /// <see cref="Window"/> class that implements the <see cref="InfoBarMessageControl"/> and
    /// the default buttons, according to the <see cref="DataWindowMode"/>. Also supports MVVM out
    /// of the box by using the <see cref="ViewModelBase"/>.
    /// </summary>
    /// <typeparam name="TViewModel">The type of the view model.</typeparam>
    public abstract class DataWindow<TViewModel> : DataWindow, IViewModelContainer where TViewModel : class, IViewModel
    {
        #region Variables
        private readonly IViewModel _viewModel;

        private bool _isFirstValidation = true;
        private bool _closeInitiatedByViewModel;

#if SILVERLIGHT
        private bool _isClosed;
#endif
        #endregion

        #region Constructor & destructor
        #region Constructor without view models
        /// <summary>
        /// Initializes a new instance of this class with the <see cref="DataWindowMode.OkCancel"/> mode.
        /// </summary>
        protected DataWindow()
            : this(null) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        protected DataWindow(DataWindowMode mode)
            : this(null, mode) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        protected DataWindow(DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons)
            : this(null, mode, additionalButtons) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(DataWindowMode mode, bool setOwnerAndFocus)
            : this(null, mode, setOwnerAndFocus) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons, bool setOwnerAndFocus)
            : this(null, mode, additionalButtons, setOwnerAndFocus) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="defaultButton">The default button.</param>
        protected DataWindow(DataWindowMode mode, DataWindowDefaultButton defaultButton)
            : this(null, mode, defaultButton) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        /// <param name="defaultButton">The default button.</param>
        protected DataWindow(DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons, DataWindowDefaultButton defaultButton)
            : this(null, mode, additionalButtons, defaultButton) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="defaultButtons">The default buttons.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(DataWindowMode mode, DataWindowDefaultButton defaultButtons, bool setOwnerAndFocus)
            : this(null, mode, null, defaultButtons, setOwnerAndFocus) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        /// <param name="defaultButton">The default button.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons, DataWindowDefaultButton defaultButton, bool setOwnerAndFocus)
            : this(null, mode, additionalButtons, defaultButton, setOwnerAndFocus) { }
        #endregion

        #region Constructors with view models
        /// <summary>
        /// Initializes a new instance of this class with the <see cref="DataWindowMode.OkCancel"/> mode.
        /// </summary>
        /// <param name="viewModel">The view model.</param>
        protected DataWindow(IViewModel viewModel)
            : this(viewModel, DataWindowMode.OkCancel) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="viewModel">The view model.</param>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        protected DataWindow(IViewModel viewModel, DataWindowMode mode)
            : this(viewModel, mode, null) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="viewModel">The view model.</param>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        protected DataWindow(IViewModel viewModel, DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons)
            : this(viewModel, mode, additionalButtons, DataWindowDefaultButton.OK) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="viewModel">The view model.</param>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(IViewModel viewModel, DataWindowMode mode, bool setOwnerAndFocus)
            : this(viewModel, mode, null, setOwnerAndFocus) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="viewModel">The view model.</param>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(IViewModel viewModel, DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons, bool setOwnerAndFocus)
            : this(viewModel, mode, additionalButtons, DataWindowDefaultButton.OK, setOwnerAndFocus) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="viewModel">The view model.</param>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="defaultButton">The default button.</param>
        protected DataWindow(IViewModel viewModel, DataWindowMode mode, DataWindowDefaultButton defaultButton)
            : this(viewModel, mode, null, defaultButton) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="viewModel">The view model.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="defaultButton">The default button.</param>
        protected DataWindow(IViewModel viewModel, DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons, DataWindowDefaultButton defaultButton)
            : this(viewModel, mode, additionalButtons, defaultButton, true) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="viewModel">The view model.</param>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="defaultButtons">The default buttons.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(IViewModel viewModel, DataWindowMode mode, DataWindowDefaultButton defaultButtons, bool setOwnerAndFocus)
            : this(viewModel, mode, null, defaultButtons, setOwnerAndFocus) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataWindow"/> class.
        /// </summary>
        /// <param name="viewModel">The view model.</param>
        /// <param name="mode"><see cref="DataWindowMode"/>.</param>
        /// <param name="additionalButtons">The additional buttons.</param>
        /// <param name="defaultButton">The default button.</param>
        /// <param name="setOwnerAndFocus">if set to <c>true</c>, set the main window as owner window and focus the window.</param>
        protected DataWindow(IViewModel viewModel, DataWindowMode mode, IEnumerable<DataWindowButton> additionalButtons,
            DataWindowDefaultButton defaultButton, bool setOwnerAndFocus)
            : base(mode, additionalButtons, defaultButton, setOwnerAndFocus)
        {
            _viewModel = viewModel ?? (IViewModel)Activator.CreateInstance(typeof(TViewModel), true);
            if (_viewModel == null)
            {
                throw new InvalidViewModelException();
            }

            DataContext = _viewModel;

            _viewModel.PropertyChanged += OnViewModelPropertyChanged;
            Loaded += DataWindow_Loaded;

            if (ViewModelChanged != null)
            {
                ViewModelChanged(this, EventArgs.Empty);
            }

            SetBinding(TitleProperty, new Binding("Title"));
        }
        #endregion
        #endregion

        #region Properties
        /// <summary>
        /// Gets the view model that is contained by the container.
        /// </summary>
        /// <value>The view model.</value>
        IViewModel IViewModelContainer.ViewModel
        {
            get { return ViewModel; }
        }

        /// <summary>
        /// Gets the view model.
        /// </summary>
        /// <value>The view model.</value>
        public TViewModel ViewModel
        {
            get { return (TViewModel)_viewModel; }
        }
        #endregion

        #region Events
#if !SILVERLIGHT
        /// <summary>
        /// Occurs when a property on the container has changed.
        /// </summary>
        /// <remarks>
        /// This event makes it possible to externally subscribe to property changes of a <see cref="DependencyObject"/>
        /// (mostly the container of a view model) because the .NET Framework does not allows us to.
        /// </remarks>
        public event EventHandler<PropertyChangedEventArgs> PropertyChanged;
#endif

        /// <summary>
        /// Occurs when the <see cref="ViewModel"/> property has changed.
        /// </summary>
        public event EventHandler<EventArgs> ViewModelChanged;
        #endregion

        #region Methods
        /// <summary>
        /// Called when the <see cref="DataWindow"/> is loaded.
        /// </summary>
        protected override void OnLoaded()
        {
            ControlToViewModelMappingHelper.InitializeControlToViewModelMappings(this);
        }

        /// <summary>
        /// Called when the <see cref="DataWindow"/> is unloaded.
        /// </summary>
        protected override void OnUnloaded()
        {
            ControlToViewModelMappingHelper.UninitializeControlToViewModelMappings(this);
        }

        /// <summary>
        /// Called when the internal grid has changed.
        /// </summary>
        /// <remarks>
        /// This method is only invoked when the grid is set, not when the grid is cleared (which is something that should never happen).
        /// </remarks>
        protected override void OnInternalGridChanged()
        {
            if (InternalGrid != null)
            {
                WarningAndErrorValidator validator = new WarningAndErrorValidator();
                InternalGrid.Children.Add(validator);
                //validator.SetBinding(WarningAndErrorValidator.SourceProperty, "DataContext");
                validator.Source = DataContext;
            }
        }

        /// <summary>
        /// Called when a property on the current view model has changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            foreach (ICommand command in Commands)
            {
                if (command is ICatelCommand)
                {
                    ((ICatelCommand)command).RaiseCanExecuteChanged();
                }
            }
        }

        /// <summary>
        /// Handles the Loaded event of the DataWindow control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void DataWindow_Loaded(object sender, RoutedEventArgs e)
        {
            _viewModel.Initialize();

            _viewModel.Closed += ViewModel_Closed;

            // Revalidate since the control already initialized the view model before the control
            // was visible, therefore the WPF engine does not show warnings and errors
            _viewModel.Validate(true, false);
        }

        /// <summary>
        /// Handles the Closed event of the ViewModel control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void ViewModel_Closed(object sender, EventArgs e)
        {
            _viewModel.PropertyChanged -= OnViewModelPropertyChanged;
            _viewModel.Closed -= ViewModel_Closed;

            _closeInitiatedByViewModel = true;

#if SILVERLIGHT
            // This code is implemented due to a bug in the ChildWindow of silverlight, see:
            // http://silverlight.codeplex.com/workitem/7935

            // Only handle this once
            if (_isClosed)
            {
                return;
            }
#endif

            Close();
        }

        /// <summary>
        /// Validates the data.
        /// </summary>
        /// <returns>True if successful, otherwise false.</returns>
        protected override bool ValidateData()
        {
            if (_viewModel.IsInitialized)
            {
                _viewModel.Validate(_isFirstValidation, false);
                _isFirstValidation = false;
            }

            return !_viewModel.HasErrors;
        }

        /// <summary>
        /// Discards all changes made by this window.
        /// </summary>
        protected override void DiscardChanges()
        {
            _viewModel.Cancel();
        }

        /// <summary>
        /// Applies all changes made by this window.
        /// </summary>
        /// <returns>True if successful, otherwise false.</returns>
        protected override bool ApplyChanges()
        {
            return _viewModel.Save();
        }

#if !SILVERLIGHT
        /// <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)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(e.Property.Name));
            }

            base.OnPropertyChanged(e);
        }
#endif

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Window.Closed"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnClosed(EventArgs e)
        {
#if SILVERLIGHT
            // This code is implemented due to a bug in the ChildWindow of silverlight, see:
            // http://silverlight.codeplex.com/workitem/7935

            // Only handle this once
            if (_isClosed)
            {
                return;
            }

            _isClosed = true;
#endif

            base.OnClosed(e);

            if (!_closeInitiatedByViewModel)
            {
                _viewModel.Close();
            }

            if (_viewModel is IDisposable)
            {
                ((IDisposable)_viewModel).Dispose();
            }
        }
        #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)

Share

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
Web02 | 2.8.140921.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