Hello there stack overflow community, am new and English is not my native language so please be patient with me.
the problem:
i started developing a wpf application using mvvm pattern and faced a problem with having many settings (30+ setting rows some are user defined types that include lists) and needed to implement a way to detect settings changes, so using the "Settings.Default.ProperyChanged" event wasn't enough i needed to detect every value change in all objects that has a ProperyChanged/CollectionChanged event.
my solution:
a helper class that uses reflection to map all settings properties and hook every ProperyChanged/CollectionChanged event to 2 event handlers.
code below is only part of the class code (all explained in comments),please keep in mind that am still learning c# so it might be messy and buggy:
              struct ObservableCollectionInfo
             {
              public object obj { get; set; }
              public PropertyInfo PropertyInfo { get; set; }
              public EventInfo EventInfo { get; set; }
             }
      //mapped lists info 
      private Dictionary<string, ObservableCollectionInfo> ObservableCollectionList;
      //determines if Settings are Changed
      public bool SettingsChanged { get; set; }
      //global count var used in methods below
      int count = 0;
      //Starts mapping settings properties, its called in App class (OnLoadCompleted override)
      public void MappSettingsObjects()
      {
        HookObjectEvents(Settings.Default);
      }
      //object mapping method
      private void HookObjectEvents(object obj)
      {
       count = 0;
       //get object  Properties
       var Properties = obj.GetType().GetProperties();
       //get object  Events
       var Events = obj.GetType().GetEvents();
       //Hook object  Events
       HookEvents(Events, obj);
       foreach (var Property in Properties)
       {
        count++;
        try
        {
         //get Property  Events
         Events = Property.PropertyType.GetEvents();
         //add non observable lists (No NotifyCollectionChanged Event) to Dictionary
         if (Property.GetValue(obj) is IList && Events.Count() == 0)
         {
          var observableCollectionInfo = new ObservableCollectionInfo() { EventInfo = null, obj = obj, PropertyInfo = Property };
          //remove old value if exist
          if (ObservableCollectionList.ContainsKey(Property.Name))
          {
           ObservableCollectionList.Remove(Property.Name);
          }
          //add the new value
          ObservableCollectionList.Add(Property.Name, observableCollectionInfo);
          //maping list items too 
          HookAllListItems(observableCollectionInfo);
         }
         //add Event Handlers
         HookEvents(Events, obj, Property);
        }
        catch (Exception) { }
       }
      }
      private void HookAllListItems(ObservableCollectionInfo info)
      {
       try
       {
        if (info.obj != null)
        {
         var collectionList = info.PropertyInfo.GetValue(info.obj);
         if (collectionList is IList)
         {
          foreach (var item in (collectionList as IList))
          {
           HookObjectEvents(item);
          }
         }
        }
       }
       catch (Exception) { }
      }
      private void HookEvents(EventInfo[] Events, object obj, PropertyInfo Property = null)
      {
       foreach (var Event in Events)
       {
        try
        {
         //comparing events names
         if (Event.EventHandlerType.Name.Equals(nameof(PropertyChangedEventHandler)))
         {
          //add handler
          Event.AddEventHandler(Property == null ? obj : Property.GetValue(obj), new PropertyChangedEventHandler(Default_PropertyChanged));
         }
         else if (Event.EventHandlerType.Name.Equals(nameof(NotifyCollectionChangedEventHandler)))
         {
          var observableCollectionInfo = new ObservableCollectionInfo() { EventInfo = Event, obj = obj, PropertyInfo = Property };
          //add observable list to Dictionary  
          if (ObservableCollectionList.ContainsKey(Property.Name))
          {
           ObservableCollectionList.Remove(Property.Name);
          }
          ObservableCollectionList.Add(Property.Name, observableCollectionInfo);
          HookAllListItems(observableCollectionInfo);
          Event.AddEventHandler(Property == null ? obj : Property.GetValue(obj), new NotifyCollectionChangedEventHandler(Default_CollectionChanged));
         }
         //this condition is to avoid stackoverflow exception (the first Property is the object itself witch causes infinity loop, i dont know why yet)
         if (count > 1)
          //mapping the Property itself
          HookObjectEvents(Property == null ? obj : Property.GetValue(obj));
        }
        catch (TargetParameterCountException)
        {
         //i don't know what causes this exception yet 
        } 
       }
      }
      //Collection Change event handler
      private void Default_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
      {
       //hook new items
       if ((e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Replace) && e.NewItems != null)
        foreach (var item in e.NewItems)
        {
         HookObjectEvents(item);
        }
       //settings changed
       SettingsChanged = true;
      }
      //Property Change event handler
      private void Default_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
      {
       //rehook new lists and its items/events
       if (ObservableCollectionList.ContainsKey(e.PropertyName))
       {
        try
        {
         var info = ObservableCollectionList.FirstOrDefault(li => li.Key == e.PropertyName);
         if (info.Key != null)
         {
          //check if its an observable list or not
          if (info.Value.EventInfo != null)
           //add handler to list changes
           info.Value.EventInfo.AddEventHandler(info.Value.PropertyInfo.GetValue(info.Value.obj), new NotifyCollectionChangedEventHandler(Default_CollectionChanged));
          //hook list items
          HookAllListItems(info.Value);
         }
        }
        catch (Exception) { }
       }
       //settings changed
       SettingsChanged = true;
      }
The code above works fine, all changes are detected even within list items,new items here are some screenshots.
Default Settings:
[https://i.stack.imgur.com/eQPbb.png]
on property changes:
[https://i.stack.imgur.com/u2oK3.png]
on Collection Changes:
[https://i.stack.imgur.com/1Rln3.png]
problems :
- 
when using the application for long time while continuously changing the settings makes some changes undetected with no exceptions or warnings what causes this bug ?.
 - 
with 100+ settings attributes, it starts to slow startup time up to 2-5 seconds, how to improve the code ?.
 - 
my tutor told me that using reflection is a bad design, so am asking for other mvvm friendly ways if possible (no click/check event handlers in code behind) to detect settings changes i found nothing online.
 
thanks to anyone that help.
Aucun commentaire:
Enregistrer un commentaire