using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Developpez.Dotnet.Collections;
using Developpez.Dotnet.Reflection;
using Developpez.Dotnet.Windows.Input;
namespace Developpez.Dotnet.Windows.ViewModel
{
///
/// Fournit des méthodes d'extensions liées à l'utilisation du pattern MVVM
///
public static class ViewModelExtensions
{
///
/// Initialise automatiquement les commandes du ViewModel en se basant sur l'utilisation des attributs CommandExecuteAttribute
/// et CommandCanExecuteAttribute.
///
/// Type du ViewModel
/// ViewModel pour lequel les commandes doivent être initialisées
/// Cette méthode recherche toutes les méthodes du ViewModel marquées avec l'attribut CommandExecuteAttribute. Pour
/// chacune de ces méthodes, on cherche une propriété avec le nom CommandName spécifié dans l'attribut (ou à défaut,
/// une propriété nommée XXXCommand, XXX étant le nom de la méthode), et on affecte à cette propriété une DelegateCommand
/// qui appellera la méthode. Si une méthode avec l'attribute CommandCanExecuteAttribute est trouvée, elle est utilisée pour
/// vérifier si la command peut s'exécuter.
public static void InitializeViewModelCommands(this T viewModel)
where T : class
{
ViewModelCommandsInitializer.InitializeCommands(viewModel);
}
private static class ViewModelCommandsInitializer
{
private static readonly Action[] _initializers;
static ViewModelCommandsInitializer()
{
var typeMethods = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
var executeMethods =
from m in typeMethods
let a = m.GetAttribute()
where a != null
select new
{
Method = m,
CommandName = string.IsNullOrEmpty(a.CommandName)
? m.Name + "Command"
: a.CommandName
};
var canExecuteMethods =
from m in typeMethods
let a = m.GetAttribute()
where a != null
select new
{
Method = m,
a.CommandName
};
var commands =
from exec in executeMethods
join canExec in canExecuteMethods on exec.CommandName equals canExec.CommandName into tmp
from canExec in tmp.DefaultIfEmpty()
select new
{
exec.CommandName,
ExecuteMethod = exec.Method,
CanExecuteMethod = canExec != null ? canExec.Method : null
};
var initializers = new List>();
foreach (var cmd in commands)
{
var commandProperty = typeof(T).GetProperty(cmd.CommandName);
if (commandProperty == null)
throw new InvalidOperationException(string.Format("No property named {0} was found", cmd.CommandName));
if (!cmd.ExecuteMethod.ReturnType.Equals(typeof(void)))
throw new InvalidOperationException("Execute method must have void return type");
var executeParameters = cmd.ExecuteMethod.GetParameters();
if (cmd.CanExecuteMethod != null)
{
if (!cmd.CanExecuteMethod.ReturnType.Equals(typeof(bool)))
throw new InvalidOperationException("CanExecute method must have bool return type");
var canExecuteParameters = cmd.CanExecuteMethod.GetParameters();
if (!canExecuteParameters.SequenceEqualBy(executeParameters, p => p.ParameterType))
throw new InvalidOperationException("CanExecute parameter types don't match Execute parameter types");
}
Action initializer = null;
if (executeParameters.Length == 0)
{
initializer = CreateCommandInitializer(commandProperty, cmd.ExecuteMethod, cmd.CanExecuteMethod);
}
else if (executeParameters.Length == 1)
{
initializer = CreateCommandInitializerWithParam(commandProperty, cmd.ExecuteMethod, cmd.CanExecuteMethod, executeParameters[0].ParameterType);
}
else
{
throw new InvalidOperationException("Execute method must have 0 or 1 parameter");
}
initializers.Add(initializer);
}
_initializers = initializers.ToArray();
}
private static Action CreateCommandInitializer(PropertyInfo property, MethodInfo executeMethod, MethodInfo canExecuteMethod)
{
var dm = new DynamicMethod("Init" + property.Name, typeof(void), new[] { typeof(T) }, true);
var il = dm.GetILGenerator();
// Push first parameter for property setter
il.Emit(OpCodes.Ldarg_0);
// Create and push Execute delegate
il.Emit(executeMethod.IsStatic ? OpCodes.Ldnull : OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldftn, executeMethod);
il.Emit(OpCodes.Newobj, typeof(Action).GetConstructor(new[] { typeof(object), typeof(IntPtr) }));
// Create and push CanExecute delegate
if (canExecuteMethod != null)
{
il.Emit(canExecuteMethod.IsStatic ? OpCodes.Ldnull : OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldftn, canExecuteMethod);
il.Emit(OpCodes.Newobj, typeof(Func).GetConstructor(new[] { typeof(object), typeof(IntPtr) }));
}
else
{
il.Emit(OpCodes.Ldnull);
}
// Create and push DelegateCommand object
il.Emit(OpCodes.Newobj, typeof(DelegateCommand).GetConstructor(new[] { typeof(Action), typeof(Func) }));
// Call property setter
var setter = property.GetSetMethod(true);
il.Emit(OpCodes.Callvirt, setter);
// Return
il.Emit(OpCodes.Ret);
return (Action)dm.CreateDelegate(typeof(Action));
}
private static Action CreateCommandInitializerWithParam(PropertyInfo property, MethodInfo executeMethod, MethodInfo canExecuteMethod, Type parameterType)
{
Type actionType = typeof(Action<>).MakeGenericType(parameterType);
Type funcType = typeof(Func<,>).MakeGenericType(parameterType, typeof(bool));
Type commandType = typeof(DelegateCommand<>).MakeGenericType(parameterType);
var dm = new DynamicMethod("Init" + property.Name, typeof(void), new[] { typeof(T) }, true);
var il = dm.GetILGenerator();
// Push first parameter for property setter
il.Emit(OpCodes.Ldarg_0);
// Create and push Execute delegate
il.Emit(executeMethod.IsStatic ? OpCodes.Ldnull : OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldftn, executeMethod);
il.Emit(OpCodes.Newobj, actionType.GetConstructor(new[] { typeof(object), typeof(IntPtr) }));
// Create and push CanExecute delegate
if (canExecuteMethod != null)
{
il.Emit(canExecuteMethod.IsStatic ? OpCodes.Ldnull : OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldftn, canExecuteMethod);
il.Emit(OpCodes.Newobj, funcType.GetConstructor(new[] { typeof(object), typeof(IntPtr) }));
}
else
{
il.Emit(OpCodes.Ldnull);
}
// Create and push DelegateCommand object
il.Emit(OpCodes.Newobj, commandType.GetConstructor(new[] { actionType, funcType }));
// Call property setter
var setter = property.GetSetMethod(true);
il.Emit(OpCodes.Callvirt, setter);
// Return
il.Emit(OpCodes.Ret);
return (Action)dm.CreateDelegate(typeof(Action));
}
public static void InitializeCommands(T viewModel)
{
foreach (var initializer in _initializers)
{
initializer(viewModel);
}
}
}
}
}