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); } } } } }