samedi 22 avril 2017

Dynymic Runtime Wrapper around multiple objects C#

I'm trying to create a wrapper around multiple objects of different types. The objects are in tools that are attached to displays in order to perform inidivual tasks (like drawing a rectangle ro circle), or peforming some measurements. Each tool lives independently on the display. The user activates the tools by clicking a button where I activate every tool by settings it IsActive property in a for loop inside the button handler.

Since each tool has several properties (which can also change from tool to tool) I was thinking about a dynamic behavior that creates wrapper around a list of tools that automatically creates the properties. I was looking at the ExpandoObject and the DynamicObject, but the problem ist that I can only add properties using the dynamic keyword but I cannot set the properties from the inside by calling TrySetMember explicitly because I'm not able to setup the SetMemberBinder object.

I also want to connect the object to the PropertyGrid to visualize the properties when they have changed.

Attached you can see my currently "achievements", where I have marked the problem with comments.

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace WpfApp4
{
    /// <summary>
    /// Wrapper-Object to wrap multiple objects including their properties
    /// and make the object look as if it is a single object.
    /// </summary>
    public sealed class MockObject : DynamicObject, INotifyPropertyChanged
    {
        #region Events 

        /// <summary>
        /// INotiyPropertyChanged-Event
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the property change event.
        /// </summary>
        /// <param name="propertyName_in"></param>
        private void RaisePropertyChanged([CallerMemberName]string propertyName_in = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName_in));
        }

        #endregion

        /// <summary>
        /// Keeps track of properties and values.
        /// </summary>
        private readonly Dictionary<string, object> Properties = new Dictionary<string, object>();

        /// <summary>
        /// Contains the wrapped objects.
        /// </summary>
        [Browsable(false)]
        public ObservableCollection<object> Objects
        {
            get;
            private set;
        } = new ObservableCollection<object>();

        /// <summary>
        /// Creates a new instance of MockObject.
        /// </summary>
        public MockObject()
        {
            Objects.CollectionChanged += Objects_CollectionChanged;
        }

        /// <summary>
        /// Update the wrapper when objects have been added or removed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Objects_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                    foreach (object o in e.NewItems)
                    {
                        INotifyPropertyChanged aPropertyChanged = o as INotifyPropertyChanged;
                        if (aPropertyChanged == null)
                            continue;

                        aPropertyChanged.PropertyChanged += APropertyChanged_PropertyChanged;
                    }

                    UpdateProperties();
                    break;

                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                    foreach (object o in e.OldItems)
                    {
                        INotifyPropertyChanged aPropertyChanged = o as INotifyPropertyChanged;
                        if (aPropertyChanged == null)
                            continue;

                        aPropertyChanged.PropertyChanged -= APropertyChanged_PropertyChanged;
                    }
                    UpdateProperties();
                    break;
            }
        }

        /// <summary>
        /// Updates properties and values of the wrapper.
        /// </summary>
        private void UpdateProperties()
        {
            Dictionary<string, List<object>> TouchedProperties = new Dictionary<string, List<object>>();

            // Retriebve all property values from all objects
            foreach (object aObject in Objects.ToArray())
            {
                if (aObject == null)
                    continue;

                PropertyInfo[] aProperties = aObject.GetType().GetProperties();

                foreach (PropertyInfo aProperty in aProperties)
                {
                    List<object> Values;
                    if (!TouchedProperties.TryGetValue(aProperty.Name, out Values))
                    {
                        Values = new List<object>();
                        TouchedProperties.Add(aProperty.Name, Values);                        
                    }

                    Values.Add(aProperty.GetValue(aObject));
                }
            }

            // Now clear the old properties and set in the new properties.
            Properties.Clear();

            dynamic ThisObject = this;

            // Only take those values that have an equal amount of objects in the list
            foreach (var aProps in TouchedProperties.ToArray())
            {
                if (aProps.Value.Count != Objects.Count)
                {
                    TouchedProperties.Remove(aProps.Key);
                    continue;
                }

                if (aProps.Value.TrueForAll(o => object.Equals(o, aProps.Value.FirstOrDefault())))
                    Properties[aProps.Key] = aProps.Value.FirstOrDefault();
                else
                    Properties[aProps.Key] = null;

                // Here I fail, I do not know how to create the property
                ThisObject.(aProps.Key) = Properties[aProps.Key];

                // What would help is to use, but I don't know how to create the binder
                this.TrySetMember(/* what the binder?*/, Properties[aProps.Key]);

                RaisePropertyChanged(aProps.Key);
            }
        }

        /// <summary>
        /// React on property changes.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void APropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            base.TrySetMember(binder, value);

            if (!Properties.ContainsKey(binder.Name))
                return false;

            foreach (object o in Objects.ToArray())
            {
                o.GetType().GetProperty(binder.Name).SetValue(o, value);
            }

            return true;
        }
    }
}





Aucun commentaire:

Enregistrer un commentaire