// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UIVisualizerService.cs" company="Catel development team">
// Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
// Service to show modal or non-modal popup windows.
// This implementation of the <see cref="IUIVisualizerService" /> automatically adds all instances of the
// <see cref="DataWindow{TViewModel}" /> class that are found in the current application domain.
// All other windows will have to be registered manually.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using Catel.Windows;
using Catel.Windows.Properties;
using log4net;
namespace Catel.MVVM.Services
{
/// <summary>
/// Service to show modal or non-modal popup windows.
/// <para />
/// This implementation of the <see cref="IUIVisualizerService"/> automatically adds all instances of the
/// <see cref="DataWindow{TViewModel}"/> class that are found in the current application domain.
/// <para />
/// All other windows will have to be registered manually.
/// </summary>
/// <remarks>
/// This implementation is based on the implementation that can be found in Cinch
/// (see http://www.codeproject.com/KB/WPF/CinchIII.aspx#PopServ).
/// </remarks>
public class UIVisualizerService : ViewModelServiceBase, IUIVisualizerService
{
#region Variables
private readonly Dictionary<string, Type> _registeredWindows = new Dictionary<string, Type>();
#endregion
#region Constructor & destructor
/// <summary>
/// Initializes a new instance of the <see cref="UIVisualizerService"/> class.
/// </summary>
public UIVisualizerService()
{
// Register the types automatically
RegisterTypesAutomatically();
}
#endregion
#region Properties
#endregion
#region Methods
/// <summary>
/// Registers the specified view model and the window type. This way, Catel knowns what
/// window to show when a specific view model window is requested.
/// </summary>
/// <param name="viewModelType">Type of the view model.</param>
/// <param name="windowType">Type of the window.</param>
/// <exception cref="ArgumentException">when <paramref name="viewModelType"/> does not implement <see cref="IViewModel"/>.</exception>
/// <exception cref="ArgumentException">when <paramref name="windowType"/> is not of type <see cref="Window"/>.</exception>
public void Register(Type viewModelType, Type windowType)
{
if (viewModelType.GetInterface(typeof(IViewModel).FullName, false) == null)
{
throw new ArgumentException(Exceptions.ArgumentMustImplementIViewModelInterface, "viewModelType");
}
// Register
Register(viewModelType.FullName, windowType);
}
/// <summary>
/// Registers the specified view model and the window type. This way, Catel knowns what
/// window to show when a specific view model window is requested.
/// </summary>
/// <param name="name">Name of the registered window.</param>
/// <param name="windowType">Type of the window.</param>
/// <exception cref="ArgumentException">when <paramref name="name"/> is <c>null</c> or empty.</exception>
/// <exception cref="ArgumentException">when <paramref name="windowType"/> is not of type <see cref="Window"/>.</exception>
public void Register(string name, Type windowType)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "name");
}
if (windowType.IsAssignableFrom(typeof(ChildWindow)))
{
throw new ArgumentException(Exceptions.ArgumentMustBeOfTypeWindow, "windowType");
}
// Lock
lock (_registeredWindows)
{
// Make sure the view model isn't registered yet
if (_registeredWindows.ContainsKey(name))
{
throw new InvalidOperationException(Exceptions.ViewModelAlreadyRegistered);
}
// Register
_registeredWindows.Add(name, windowType);
// Log
Log.Debug(TraceMessages.RegisteredViewModelInUIVisualizerService, name, windowType.FullName);
}
}
/// <summary>
/// This unregisters the specified view model.
/// </summary>
/// <param name="viewModelType">Type of the view model to unregister.</param>
/// <returns>
/// <c>true</c> if the view model is unregistered; otherwise <c>false</c>.
/// </returns>
public bool Unregister(Type viewModelType)
{
// Unregister
return Unregister(viewModelType.FullName);
}
/// <summary>
/// This unregisters the specified view model.
/// </summary>
/// <param name="name">Name of the registered window.</param>
/// <returns>
/// <c>true</c> if the view model is unregistered; otherwise <c>false</c>.
/// </returns>
public bool Unregister(string name)
{
lock (_registeredWindows)
{
// Unregister
bool result = _registeredWindows.Remove(name);
if (result)
{
// Log
Log.Debug(TraceMessages.UnregisteredViewModelInUIVisualizerService, name);
}
// Return result
return result;
}
}
/// <summary>
/// Shows a window that is registered with the specified view model in a non-modal state.
/// </summary>
/// <param name="viewModel">The view model.</param>
/// <returns>
/// <c>true</c> if the popup window is successfully opened; otherwise <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">when <paramref name="viewModel"/> is <c>null</c>.</exception>
/// <exception cref="ViewModelNotRegisteredException">when <paramref name="viewModel"/> is not registered by the <see cref="Register(System.Type,System.Type)"/> method first.</exception>
public bool Show(IViewModel viewModel)
{
if (viewModel == null)
{
throw new ArgumentNullException("viewModel");
}
// Show
return Show(viewModel.GetType().FullName, viewModel);
}
/// <summary>
/// Shows a window that is registered with the specified name in a non-modal state.
/// </summary>
/// <param name="name">The name that the window is registered with.</param>
/// <param name="data">The data to set as data context. If <c>null</c>, the data context will be untouched.</param>
/// <returns>
/// <c>true</c> if the popup window is successfully opened; otherwise <c>false</c>.
/// </returns>
/// <exception cref="ArgumentException">when <paramref name="name"/> is <c>null</c> or empty.</exception>
public bool Show(string name, object data)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "name");
}
// Show
return Show(name, data, null);
}
/// <summary>
/// Shows a window that is registered with the specified view model in a non-modal state.
/// </summary>
/// <param name="viewModel">The view model.</param>
/// <param name="completedProc">The callback procedure that will be invoked as soon as the window is closed. This value can be <c>null</c>.</param>
/// <returns>
/// <c>true</c> if the popup window is successfully opened; otherwise <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">when <paramref name="viewModel"/> is <c>null</c>.</exception>
/// <exception cref="ViewModelNotRegisteredException">when <paramref name="viewModel"/> is not registered by the <see cref="Register(System.Type,System.Type)"/> method first.</exception>
public bool Show(IViewModel viewModel, EventHandler<UICompletedEventArgs> completedProc)
{
if (viewModel == null)
{
throw new ArgumentNullException("viewModel");
}
// Show
return Show(viewModel.GetType().FullName, viewModel, completedProc);
}
/// <summary>
/// Shows a window that is registered with the specified view model in a non-modal state.
/// </summary>
/// <param name="name">The name that the window is registered with.</param>
/// <param name="data">The data to set as data context. If <c>null</c>, the data context will be untouched.</param>
/// <param name="completedProc">The callback procedure that will be invoked as soon as the window is closed. This value can be <c>null</c>.</param>
/// <returns>
/// <c>true</c> if the popup window is successfully opened; otherwise <c>false</c>.
/// </returns>
/// <exception cref="ArgumentException">when <paramref name="name"/> is <c>null</c> or empty.</exception>
/// <exception cref="WindowNotRegisteredException">when <paramref name="name"/> is not registered by the <see cref="Register(string,System.Type)"/> method first.</exception>
public bool Show(string name, object data, EventHandler<UICompletedEventArgs> completedProc)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "name");
}
lock (_registeredWindows)
{
if (!_registeredWindows.ContainsKey(name))
{
throw new WindowNotRegisteredException(name);
}
}
// Create & show the window
ChildWindow window = CreateWindow(name, data, completedProc, false);
if (window != null)
{
window.Show();
}
// Return result
return (window != null);
}
/// <summary>
/// Shows a window that is registered with the specified view model in a modal state.
/// </summary>
/// <param name="viewModel">The view model.</param>
/// <returns>
/// Nullable boolean representing the dialog result.
/// </returns>
/// <exception cref="ArgumentNullException">when <paramref name="viewModel"/> is <c>null</c>.</exception>
/// <exception cref="WindowNotRegisteredException">when <paramref name="viewModel"/> is not registered by the <see cref="Register(string,System.Type)"/> method first.</exception>
public bool? ShowDialog(IViewModel viewModel)
{
// Call overload
return ShowDialog(viewModel.GetType().FullName, viewModel, null);
}
/// <summary>
/// Shows a window that is registered with the specified view model in a modal state.
/// </summary>
/// <param name="viewModel">The view model.</param>
/// <param name="completedProc">The callback procedure that will be invoked as soon as the window is closed. This value can be <c>null</c>.</param>
/// <returns>
/// Nullable boolean representing the dialog result.
/// </returns>
/// <exception cref="ArgumentNullException">when <paramref name="viewModel"/> is <c>null</c>.</exception>
/// <exception cref="WindowNotRegisteredException">when <paramref name="viewModel"/> is not registered by the <see cref="Register(string,System.Type)"/> method first.</exception>
public bool? ShowDialog(IViewModel viewModel, EventHandler<UICompletedEventArgs> completedProc)
{
if (viewModel == null)
{
throw new ArgumentNullException("viewModel");
}
// Show dialog
return ShowDialog(viewModel.GetType().FullName, viewModel, completedProc);
}
/// <summary>
/// Shows a window that is registered with the specified view model in a modal state.
/// </summary>
/// <param name="name">The name that the window is registered with.</param>
/// <param name="data">The data to set as data context. If <c>null</c>, the data context will be untouched.</param>
/// <returns>
/// Nullable boolean representing the dialog result.
/// </returns>
/// <exception cref="ArgumentException">when <paramref name="name"/> is <c>null</c> or empty.</exception>
/// <exception cref="WindowNotRegisteredException">when <paramref name="name"/> is not registered by the <see cref="Register(string,System.Type)"/> method first.</exception>
public bool? ShowDialog(string name, object data)
{
// Call overload
return ShowDialog(name, data, null);
}
/// <summary>
/// Shows a window that is registered with the specified view model in a modal state.
/// </summary>
/// <param name="name">The name that the window is registered with.</param>
/// <param name="data">The data to set as data context. If <c>null</c>, the data context will be untouched.</param>
/// <param name="completedProc">The callback procedure that will be invoked as soon as the window is closed. This value can be <c>null</c>.</param>
/// <returns>
/// Nullable boolean representing the dialog result.
/// </returns>
/// <exception cref="ArgumentException">when <paramref name="name"/> is <c>null</c> or empty.</exception>
/// <exception cref="WindowNotRegisteredException">when <paramref name="name"/> is not registered by the <see cref="Register(string,System.Type)"/> method first.</exception>
public bool? ShowDialog(string name, object data, EventHandler<UICompletedEventArgs> completedProc)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "name");
}
lock (_registeredWindows)
{
if (!_registeredWindows.ContainsKey(name))
{
throw new WindowNotRegisteredException(name);
}
}
//// Since Silverlight doesn't support ShowDialog, we have to be creative
//// In the end, it would be great if this service would behave the same as the WPF one
//ChildWindow window = null;
//AutoResetEvent waitHandle = new AutoResetEvent(false);
//// Create a thread that the dialog will run on (so we can block the current one)
//Thread dialogThread = new Thread(() =>
//{
// // Create & show the window on the separate thread
// window = CreateWindow(name, data, null, true);
// if (window != null)
// {
// window.Closed += (sender, e) => waitHandle.Set();
// window.Show();
// }
// else
// {
// waitHandle.Set();
// }
//});
//// Start the thread
//dialogThread.Start();
//// Wait in the UI thread until the thread with the dialog is finished
//waitHandle.WaitOne();
ChildWindow window = CreateWindow(name, data, completedProc, true);
if (window != null)
{
window.Show();
}
// Return result
return (window != null) ? window.DialogResult : false;
}
/// <summary>
/// This creates the WPF window from a key.
/// </summary>
/// <param name="name">The name that the window is registered with.</param>
/// <param name="data">The data that will be set as data context.</param>
/// <param name="completedProc">Callback</param>
/// <param name="isModal">True if this is a ShowDialog request</param>
/// <returns>The created window.</returns>
private ChildWindow CreateWindow(string name, object data, EventHandler<UICompletedEventArgs> completedProc, bool isModal)
{
// Get window type
Type windowType;
lock (_registeredWindows)
{
if (!_registeredWindows.TryGetValue(name, out windowType))
{
return null;
}
}
// Define the window
ChildWindow window = null;
// First, try to constructor directly with the data context
if (data != null)
{
ConstructorInfo constructorInfo = windowType.GetConstructor(new[] { data.GetType() });
if (constructorInfo != null)
{
// Construct
window = constructorInfo.Invoke(new[] { data }) as ChildWindow;
}
else
{
// Log
Log.Debug(TraceMessages.NoConstructorWithViewModelInjectionFound, data.GetType());
}
}
// Check if there is a window constructed
if (window == null)
{
// Find default constructor
ConstructorInfo constructorInfo = windowType.GetConstructor(Type.EmptyTypes);
if (constructorInfo == null)
{
// Log
Log.Error(TraceMessages.NoInjectionOrDefaultConstructorFoundForWindow, windowType);
// Exit
return null;
}
// Constructor
window = constructorInfo.Invoke(new object[] { }) as ChildWindow;
if (window != null)
{
// Set data context manually
window.DataContext = data;
}
}
// Subscribe to events
if ((window != null) && (completedProc != null))
{
// Subscribe to closed event if required
window.Closed += (s, e) => completedProc(this, new UICompletedEventArgs(data, isModal ? window.DialogResult : null));
// Due to a bug in the latest version of the Silverlight toolkit, set parent to enabled again
// TODO: After every toolkit release, check if this code can be removed
window.Closed += (s, e) => Application.Current.RootVisual.SetValue(Control.IsEnabledProperty, true);
}
// Return the created window
return window;
}
/// <summary>
/// Registers the types automatically.
/// </summary>
private void RegisterTypesAutomatically()
{
// Log
Log.Debug(TraceMessages.RegisteringAllDataWindowImplementationsAutomatically);
// Find all implementations of the DataWindow<TViewModel>
foreach (Assembly assembly in AssemblyHelper.GetLoadedAssemblies())
{
try
{
foreach (Type type in assembly.GetTypes())
{
try
{
if (!type.IsAbstract && (type != typeof(DataWindow<>)) && TypeHelper.IsSubclassOfRawGeneric(typeof(DataWindow<>), type))
{
// Find the subtype
Type baseType = type.BaseType;
while (true)
{
// If the base is null, we can stop
if (baseType == null)
{
break;
}
// If the base type is generic and of the type we are looking for, we can stop
if (baseType.IsGenericType && (baseType.GetGenericTypeDefinition() == typeof(DataWindow<>)))
{
break;
}
// Get next base
baseType = baseType.BaseType;
}
// Make sure that we have a valid type
if (baseType == null)
{
// Log
Log.Warn(TraceMessages.CouldNotFindGenericDefinition, type.Name);
// Continue
continue;
}
// Get view model type
Type viewModelType = baseType.GetGenericArguments()[0];
// Register type
Register(viewModelType, type);
// Log
Log.Debug(TraceMessages.AutomaticallyRegisteredViewModel, viewModelType.Name, type.Name);
}
}
catch (Exception innerEx)
{
// Log
Log.Error(innerEx, TraceMessages.FailedToRegisterType, type);
}
}
}
catch (Exception ex)
{
// Log & continue
Log.Error(ex, TraceMessages.FailedToGetTypesOfAssembly, assembly.FullName);
}
}
// Log
Log.Debug(TraceMessages.RegisteredAllDataWindowImplementationsAutomatically);
}
#endregion
}
}