Search code examples
c#wpflistviewinotifypropertychanged

identify what property changed, and edit all selected Items


I recently found how to add an event when changing a property of an Item in my ListView :

foreach (Profil item in gp.ListProfils)
{
    if (item is INotifyPropertyChanged)
    {
        INotifyPropertyChanged observable = (INotifyPropertyChanged)item;
        observable.PropertyChanged +=
            new PropertyChangedEventHandler(ItemPropertyChanged);
    }
}

Now, I would like to apply the modify to all selected items.

I found how to identify the name of property changed :

private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if(sender is Profil)
    {
        string myPropertyName = e.PropertyName;
        Profil editedProfile = (Profil)sender;
        foreach(Profil prf in this.SelectedProfiles)
        {
            //prf.NAME_PROPERTY = editedProfile.NAME_PROPERTY
            if (!this.ListProfilToEdit.Contains(editedProfile))
            {
                this.ListProfilToEdit.Add(editedProfile);
            }
        }
    }
}

But how can I replace the line in comment. Concretely if I edited the field "Width", I want to change the width for all selected elements. Is there a way to do it, without creating a separated function EditProperty(string nameProperty)?


Solution

  • Here you need to be able to read a property dynamically from an object and then write it dynamically on another object.

    You can use Expression Trees to generate Getters & Setters dynamically and them put in the cache.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    
    namespace CoreAppMain
    {
        public class TypeAccessors<T> where T : class
        {
            //Getters
            public static Dictionary<string, Func<T, object>> Getters = GetTypeGetters();
    
            private static Dictionary<string, Func<T, object>> GetTypeGetters()
            {
                var _getters = new Dictionary<string, Func<T, object>>();
                var props = typeof(T).GetProperties();
                foreach (var p in props)
                {
                    var entityParam = Expression.Parameter(typeof(T), "e");
                    Expression columnExpr = Expression.Property(entityParam, p);
                    Expression conversion = Expression.Convert(columnExpr, typeof(object));
                    var fct = Expression.Lambda<Func<T, object>>(conversion, entityParam).Compile();
                    _getters.Add(p.Name, fct);
                }
    
                return _getters;
            }
    
            //setters
            public static Dictionary<string, Action<T, object>> Setters = GetTypeSetters();
    
            public static Dictionary<string, Action<T, object>> GetTypeSetters()
            {
                var setters = new Dictionary<string, Action<T, object>>();
                const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
                var propsToSet = typeof(T).GetProperties(flags)
                    .Where(x => x.CanWrite)
                    .ToList();
    
                foreach (var field in propsToSet)
                {
                    var propertyInfo = typeof(T).GetProperty(field.Name);
                    setters.Add(field.Name, GetPropertySetter<T>(propertyInfo));
                }
                return setters;
            }
    
            public static Action<TModel, object> GetPropertySetter<TModel>(PropertyInfo propertyInfo)
            {
                // Note that we are testing whether this is a value type
                bool isValueType = propertyInfo.DeclaringType.IsValueType;
                var method = propertyInfo.GetSetMethod(true);
                var obj = Expression.Parameter(typeof(object), "o");
                var value = Expression.Parameter(typeof(object));
    
                // Note that we are using Expression.Unbox for value types
                // and Expression.Convert for reference types
                Expression<Action<TModel, object>> expr =
                    Expression.Lambda<Action<TModel, object>>(
                        Expression.Call(
                            isValueType ?
                                Expression.Unbox(obj, method.DeclaringType) :
                                Expression.Convert(obj, method.DeclaringType),
                            method,
                            Expression.Convert(value, method.GetParameters()[0].ParameterType)),
                            obj, value);
                Action<TModel, object> action = expr.Compile();
                return action;
            }
    
        }
    }
    

    Usage Example:

    var cust1 = new Customer()
    {
        FirstName = "TOTOT",
        LastName = "TITI"
    };
    
    var cust2 = new Customer()
    {
        FirstName = "XXXXXX",
        LastName = "YYYYYY"
    };
    
    var value = TypeAccessors<Customer>.Getters[nameof(Customer.FirstName)](cust2);
    TypeAccessors<Customer>.Setters[nameof(Customer.FirstName)](cust1, value);
    
    Console.WriteLine(cust1.FirstName);
    Console.ReadKey();