Click here to Skip to main content
15,893,381 members
Articles / Desktop Programming / WPF

Catel - Part 4 of n: Unit testing with Catel

Rate me:
Please Sign up or sign in to vote.
4.55/5 (10 votes)
28 Jan 2011CPOL11 min read 49.1K   572   11  
This article explains how to write unit tests for MVVM using Catel.
// --------------------------------------------------------------------------------------------------------------------
// <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)


Written By
Software Developer
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions