// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ControlToViewModelMappingHelper.cs" company="Catel development team">
// Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
// Helper class to fix <see cref="ControlToViewModelMapping"/> for <see cref="FrameworkElement"/>.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using Catel.Reflection;
using Catel.Windows.Properties;
using log4net;
namespace Catel.MVVM.UI
{
/// <summary>
/// Helper class to fix <see cref="ControlToViewModelMapping"/> for <see cref="FrameworkElement"/>.
/// </summary>
internal class ControlToViewModelMappingHelper
{
#region Variables
/// <summary>
/// The <see cref="ILog">log</see> object.
/// </summary>
protected static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// Dictionary of <see cref="IViewModelContainer"/> instances managed by this helper class.
/// </summary>
private static readonly Dictionary<IViewModelContainer, ControlToViewModelMappingHelper> _viewModelContainers = new Dictionary<IViewModelContainer, ControlToViewModelMappingHelper>();
/// <summary>
/// Dictionary of <see cref="ControlToViewModelMappingContainer"/> instances per type (which should be a <see cref="DependencyObject"/>).
/// </summary>
private static readonly Dictionary<Type, ControlToViewModelMappingContainer> _controlToViewModelMappingContainers = new Dictionary<Type, ControlToViewModelMappingContainer>();
/// <summary>
/// List of properties in the view model that should be ignored.
/// </summary>
private readonly List<string> _ignoredViewModelChanges = new List<string>();
/// <summary>
/// List of properties in the user control that should be ignored.
/// </summary>
private readonly List<string> _ignoredControlChanges = new List<string>();
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new instance of the <see cref="ControlToViewModelMappingHelper"/> class.
/// </summary>
/// <param name="viewModelContainer">The view model container.</param>
/// <exception cref="ArgumentNullException">when <paramref name="viewModelContainer"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">when <paramref name="viewModelContainer"/> is not a <see cref="DependencyObject"/>.</exception>
public ControlToViewModelMappingHelper(IViewModelContainer viewModelContainer)
{
if (viewModelContainer == null)
{
throw new ArgumentNullException("viewModelContainer");
}
if (!(viewModelContainer is DependencyObject))
{
throw new ArgumentException(Exceptions.ViewModelContainerMustBeOfTypeDependencyObject, "viewModelContainer");
}
ViewModelContainer = viewModelContainer;
if (!_controlToViewModelMappingContainers.ContainsKey(ViewModelContainerType))
{
_controlToViewModelMappingContainers.Add(ViewModelContainerType, new ControlToViewModelMappingContainer((DependencyObject)viewModelContainer));
}
ViewModelContainer.ViewModelChanged += OnViewModelChanged;
#if !SILVERLIGHT
ViewModelContainer.PropertyChanged += OnViewModelContainerPropertyChanged;
#endif
InitializeViewModel(ViewModelContainer.ViewModel);
}
#endregion
#region Properties
/// <summary>
/// Gets the view model container.
/// </summary>
/// <value>The view model container.</value>
public IViewModelContainer ViewModelContainer { get; private set; }
/// <summary>
/// Gets the type of the view model container.
/// </summary>
/// <value>The type of the view model container.</value>
private Type ViewModelContainerType { get { return ViewModelContainer.GetType(); } }
/// <summary>
/// Gets or sets the previous view model.
/// </summary>
/// <value>The previous view model.</value>
private IViewModel PreviousViewModel { get; set; }
/// <summary>
/// Gets the current view model.
/// </summary>
/// <value>The current view model.</value>
private IViewModel CurrentViewModel { get { return ViewModelContainer.ViewModel; } }
#endregion
#region Methods
/// <summary>
/// Initializes the <see cref="ControlToViewModelMapping"/> for the specified <see cref="IViewModelContainer"/>.
/// </summary>
/// <param name="viewModelContainer">The view model container to initialize the mappings for.</param>
/// <exception cref="ArgumentNullException">when <paramref name="viewModelContainer"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">when <paramref name="viewModelContainer"/> is not a <see cref="DependencyObject"/>.</exception>
public static void InitializeControlToViewModelMappings(IViewModelContainer viewModelContainer)
{
#if SILVERLIGHT
throw new NotSupportedInSilverlightException("DependencyObject has no OnPropertyChanged method or PropertyChanged event");
#endif
if (viewModelContainer == null)
{
throw new ArgumentNullException("viewModelContainer");
}
if (_viewModelContainers.ContainsKey(viewModelContainer))
{
return;
}
Log.Debug(TraceMessages.InitializingViewModelContainer);
_viewModelContainers.Add(viewModelContainer, new ControlToViewModelMappingHelper(viewModelContainer));
Log.Debug(TraceMessages.InitializedViewModelContainer);
}
/// <summary>
/// Uninitializes the <see cref="ControlToViewModelMapping"/> for the specified <see cref="IViewModelContainer"/>.
/// </summary>
/// <param name="viewModelContainer">The view model container the uninitialize the mappings for.</param>
/// <exception cref="ArgumentNullException">when <paramref name="viewModelContainer"/> is <c>null</c>.</exception>
public static void UninitializeControlToViewModelMappings(IViewModelContainer viewModelContainer)
{
if (viewModelContainer == null)
{
throw new ArgumentNullException("viewModelContainer");
}
if (_viewModelContainers.ContainsKey(viewModelContainer))
{
_viewModelContainers[viewModelContainer].UninitializeControlToViewModelMappings();
}
}
/// <summary>
/// Uninitializes the <see cref="ControlToViewModelMapping"/> for the registered <see cref="IViewModelContainer"/>.
/// </summary>
private void UninitializeControlToViewModelMappings()
{
Log.Debug(TraceMessages.UninitializingViewModelContainer);
ViewModelContainer.ViewModelChanged -= OnViewModelChanged;
#if !SILVERLIGHT
ViewModelContainer.PropertyChanged -= OnViewModelContainerPropertyChanged;
#endif
UninitializeViewModel(CurrentViewModel);
Log.Debug(TraceMessages.UninitializedViewModelContainer);
}
/// <summary>
/// Initializes the specified view model.
/// </summary>
/// <param name="viewModel">The view model.</param>
private void InitializeViewModel(IViewModel viewModel)
{
Log.Debug(Resources.InitializingViewModel);
UninitializeViewModel(PreviousViewModel);
PreviousViewModel = viewModel;
if (viewModel != null)
{
// If there are mappings, sync them in the right way
foreach (ControlToViewModelMapping mapping in _controlToViewModelMappingContainers[ViewModelContainerType].GetAllControlToViewModelMappings())
{
try
{
if ((mapping.MappingType == ControlViewModelModelMappingType.TwoWayControlWins) ||
(mapping.MappingType == ControlViewModelModelMappingType.ControlToViewModel))
{
TransferValueFromControlToViewModel(viewModel, mapping.ControlPropertyName, mapping.ViewModelPropertyName);
}
else if ((mapping.MappingType == ControlViewModelModelMappingType.TwoWayViewModelWins) ||
(mapping.MappingType == ControlViewModelModelMappingType.ViewModelToControl))
{
TransferValueFromViewModelToControl(viewModel, mapping.ControlPropertyName, mapping.ViewModelPropertyName);
}
}
catch (Exception ex)
{
Log.Error(ex, TraceMessages.FailedToTransferValueForControlToViewModelMapping, mapping.ControlPropertyName, mapping.ViewModelPropertyName);
}
}
viewModel.PropertyChanged += OnViewModelPropertyChanged;
}
Log.Debug(Resources.InitializedViewModel);
}
/// <summary>
/// Uninitializes the specified view model.
/// </summary>
/// <param name="viewModel">The view model.</param>
private void UninitializeViewModel(IViewModel viewModel)
{
if (viewModel == null)
{
return;
}
Log.Debug(TraceMessages.UninitializingViewModel, viewModel.GetType().Name);
viewModel.PropertyChanged -= OnViewModelPropertyChanged;
Log.Debug(TraceMessages.UninitializedViewModel, viewModel.GetType().Name);
}
/// <summary>
/// Called when the view model on the view model container has changed.
/// </summary>
/// <param name="sender">The view model container.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void OnViewModelChanged(object sender, EventArgs e)
{
InitializeViewModel(CurrentViewModel);
}
/// <summary>
/// Called when a property on the view model has changed.
/// </summary>
/// <param name="sender">The view model.</param>
/// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_controlToViewModelMappingContainers[ViewModelContainerType].ContainsViewModelToControlMapping(e.PropertyName))
{
ControlToViewModelMapping mapping = _controlToViewModelMappingContainers[ViewModelContainerType].GetViewModelToControlMapping(e.PropertyName);
if (_ignoredViewModelChanges.Contains(mapping.ControlPropertyName))
{
Log.Debug(TraceMessages.IgnoredPropertyChangedEventForViewModel, mapping.ControlPropertyName);
}
else
{
if ((mapping.MappingType == ControlViewModelModelMappingType.TwoWayDoNothing) ||
(mapping.MappingType == ControlViewModelModelMappingType.TwoWayControlWins) ||
(mapping.MappingType == ControlViewModelModelMappingType.TwoWayViewModelWins) ||
(mapping.MappingType == ControlViewModelModelMappingType.ViewModelToControl))
{
TransferValueFromViewModelToControl(CurrentViewModel, mapping.ControlPropertyName, mapping.ViewModelPropertyName);
}
}
}
}
/// <summary>
/// Called when a property on the view model container has changed.
/// </summary>
/// <param name="sender">The view model container.</param>
/// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
private void OnViewModelContainerPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_controlToViewModelMappingContainers[ViewModelContainerType].ContainsControlToViewModelMapping(e.PropertyName))
{
ControlToViewModelMapping mapping = _controlToViewModelMappingContainers[ViewModelContainerType].GetControlToViewModelMapping(e.PropertyName);
if (_ignoredControlChanges.Contains(mapping.ControlPropertyName))
{
Log.Debug(TraceMessages.IgnoredPropertyChangedEventForControl, mapping.ControlPropertyName);
}
else
{
if ((mapping.MappingType == ControlViewModelModelMappingType.TwoWayDoNothing) ||
(mapping.MappingType == ControlViewModelModelMappingType.TwoWayControlWins) ||
(mapping.MappingType == ControlViewModelModelMappingType.TwoWayViewModelWins) ||
(mapping.MappingType == ControlViewModelModelMappingType.ControlToViewModel))
{
TransferValueFromControlToViewModel(CurrentViewModel, mapping.ControlPropertyName, mapping.ViewModelPropertyName);
}
}
}
}
/// <summary>
/// Transfers the value from a control property to the view model property.
/// <para/>
/// This method does nothing when <paramref name="viewModel"/> is <c>null</c>.
/// </summary>
/// <param name="viewModel">The view model.</param>
/// <param name="controlPropertyName">Name of the control property.</param>
/// <param name="viewModelPropertyName">Name of the view model property.</param>
/// <exception cref="ArgumentException">when <paramref name="controlPropertyName"/> is <c>null</c> or empty.</exception>
/// <exception cref="ArgumentException">when <paramref name="viewModelPropertyName"/> is <c>null</c> or empty.</exception>
/// <remarks>
/// This method does not check the type of the properties. If the types are incorrect, an exception will be thrown by
/// the .NET Framework.
/// </remarks>
protected void TransferValueFromControlToViewModel(IViewModel viewModel, string controlPropertyName, string viewModelPropertyName)
{
if (viewModel == null)
{
Log.Warn(TraceMessages.CannotTransferValueFromControlToViewModelBecauseViewModelIsNull);
return;
}
Log.Debug(TraceMessages.IgnoringNextPropertyChangedEventForViewModelProperty, viewModelPropertyName);
// Ignore this property (we will soon receive an event that it has changed)
if (!_ignoredViewModelChanges.Contains(viewModelPropertyName))
{
_ignoredViewModelChanges.Add(viewModelPropertyName);
}
TransferValue(ViewModelContainer, controlPropertyName, viewModel, viewModelPropertyName);
Log.Debug(TraceMessages.StoppedIgnoringNextPropertyChangedEventForViewModelProperty, viewModelPropertyName);
_ignoredViewModelChanges.Remove(viewModelPropertyName);
}
/// <summary>
/// Transfers the value from a view model property to the control property.
/// <para/>
/// This method does nothing when <paramref name="viewModel"/> is <c>null</c>.
/// </summary>
/// <param name="viewModel">The view model.</param>
/// <param name="controlPropertyName">Name of the control property.</param>
/// <param name="viewModelPropertyName">Name of the view model property.</param>
/// <exception cref="ArgumentException">when <paramref name="controlPropertyName"/> is <c>null</c> or empty.</exception>
/// <exception cref="ArgumentException">when <paramref name="viewModelPropertyName"/> is <c>null</c> or empty.</exception>
/// <remarks>
/// This method does not check the type of the properties. If the types are incorrect, an exception will be thrown by
/// the .NET Framework.
/// </remarks>
protected void TransferValueFromViewModelToControl(IViewModel viewModel, string controlPropertyName, string viewModelPropertyName)
{
if (viewModel == null)
{
Log.Warn(TraceMessages.CannotTransferValueFromViewModelToControlBecauseViewModelIsNull);
return;
}
Log.Debug(TraceMessages.IgnoringNextPropertyChangedEventForControlProperty, controlPropertyName);
if (!_ignoredControlChanges.Contains(controlPropertyName))
{
_ignoredControlChanges.Add(controlPropertyName);
}
TransferValue(viewModel, viewModelPropertyName, ViewModelContainer, controlPropertyName);
Log.Debug(TraceMessages.StoppedIgnoringNextPropertyChangedEventForControlProperty, controlPropertyName);
_ignoredControlChanges.Remove(controlPropertyName);
}
/// <summary>
/// Transfers a value from the source property to the target property.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="sourcePropertyName">Name of the source property.</param>
/// <param name="target">The target.</param>
/// <param name="targetPropertyName">Name of the target property.</param>
/// <exception cref="ArgumentNullException">when <paramref name="source"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">when <paramref name="target"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">when <paramref name="sourcePropertyName"/> is <c>null</c> or empty.</exception>
/// <exception cref="ArgumentException">when <paramref name="targetPropertyName"/> is <c>null</c> or empty.</exception>
/// <remarks>
/// This method does not check the type of the properties. If the types are incorrect, an exception will be thrown by
/// the .NET Framework.
/// </remarks>
private static void TransferValue(object source, string sourcePropertyName, object target, string targetPropertyName)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (target == null)
{
throw new ArgumentNullException("target");
}
if (string.IsNullOrEmpty(sourcePropertyName))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "sourcePropertyName");
}
if (string.IsNullOrEmpty(targetPropertyName))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "targetPropertyName");
}
object valueToTransfer = PropertyHelper.GetPropertyValue(source, sourcePropertyName);
Log.Debug(TraceMessages.TransferringValue, source.GetType().Name, sourcePropertyName, target.GetType().Name, targetPropertyName);
PropertyHelper.SetPropertyValue(target, targetPropertyName, valueToTransfer);
Log.Debug(TraceMessages.TransferredValue);
}
#endregion
}
}