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