source: branches/ParameterBinding/HeuristicLab.Core/3.3/ItemBinding.cs @ 4790

Last change on this file since 4790 was 4790, checked in by abeham, 12 years ago

#1258

  • Added detection if a certain link in the chain implements INotifyPropertyChanged (still missing -> fire only on a change to the "right" property)
  • Added optional parameter LambdaExpression in the binding
  • Changed cloning behavior of binding -> bindings have to be cloned only after the clone is fully constructed
File size: 8.3 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
6using HeuristicLab.Common;
7using System.Reflection;
8using System.Reflection.Emit;
9using System.ComponentModel;
10using System.Linq.Expressions;
11
12namespace HeuristicLab.Core {
13  [StorableClass]
14  public sealed class ItemBinding : IItemBinding {
15    private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
16    [Storable]
17    private IDeepCloneable target;
18    [Storable]
19    private string targetPath;
20    [Storable]
21    private IDeepCloneable source;
22    [Storable]
23    private string sourcePath;
24    [Storable]
25    private bool bound;
26    [Storable]
27    private LambdaExpression bindingExpression;
28
29    private Delegate bindingFunc;
30    private static object evhCacheLock = new object();
31    private static Dictionary<Type, DynamicMethod> eventHandlerCache = new Dictionary<Type, DynamicMethod>();
32
33    [StorableConstructor]
34    private ItemBinding(bool deserializing) { }
35    private ItemBinding(ItemBinding original, Cloner cloner) {
36      this.target = cloner.Clone(original.target);
37      this.targetPath = original.targetPath;
38      this.source = cloner.Clone(original.source);
39      this.sourcePath = original.sourcePath;
40      this.bindingExpression = original.bindingExpression;
41      if (this.bindingExpression != null)
42        this.bindingFunc = this.bindingExpression.Compile();
43      if (original.bound) Bind();
44    }
45    public ItemBinding(IDeepCloneable target, string targetPath, IDeepCloneable source, string sourcePath) {
46      this.target = target;
47      this.targetPath = targetPath;
48      this.source = source;
49      this.sourcePath = sourcePath;
50      this.bound = false;
51    }
52    public ItemBinding(IDeepCloneable target, string targetPath, IDeepCloneable source, string sourcePath, LambdaExpression func)
53      : this(target, targetPath, source, sourcePath) {
54      this.bindingExpression = func;
55      this.bindingFunc = this.bindingExpression.Compile();
56    }
57
58    public IDeepCloneable Clone(Cloner cloner) {
59      return new ItemBinding(this, cloner);
60    }
61
62    public object Clone() {
63      return Clone(new Cloner());
64    }
65
66    [StorableHook(HookType.AfterDeserialization)]
67    private void AfterDeserialization() {
68      if (bound) RegisterEventHandlers();
69      if (bindingExpression != null)
70        bindingFunc = bindingExpression.Compile();
71    }
72
73    public void Bind() {
74      RegisterEventHandlers();
75      bound = true;
76      ExecuteBinding(null);
77    }
78
79    public void Unbind() {
80      DeregisterEventHandlers();
81      bound = false;
82    }
83
84    private void RegisterEventHandlers() {
85      string[] properties = sourcePath.Split('.');
86      object current = source;
87      foreach (string property in properties) {
88        TryHookToChangedEvent(current, property);
89        MethodInfo mInfo = current.GetType().GetProperty(property, Flags).GetGetMethod(true);
90        object next = mInfo.Invoke(current, null);
91        if (next == null) return;
92        current = next;
93      }
94    }
95
96    private void DeregisterEventHandlers() {
97      string[] properties = sourcePath.Split('.');
98      object current = source;
99      foreach (string property in properties) {
100        TryUnhookFromChangedEvent(current, property);
101        MethodInfo mInfo = current.GetType().GetProperty(property, Flags).GetGetMethod(true);
102        object next = mInfo.Invoke(current, null);
103        if (next == null) return;
104        current = next;
105      }
106    }
107
108    // DO NOT RENAME (string reference to this method name in CreateAndAddDynamicEventHandler)
109    private void ExecuteBinding(object sender) {
110      string[] sourceProperties = sourcePath.Split('.');
111      object cSource = source;
112      #region Navigate to source property (readd bindings if necessary)
113      bool reregister = false;
114      for (int i = 0; i < sourceProperties.Length; i++) {
115        if (cSource == null) return;
116        string property = sourceProperties[i];
117        if (reregister) TryHookToChangedEvent(cSource, property);
118        if (cSource == sender) reregister = true;
119        PropertyInfo pInfo = cSource.GetType().GetProperty(property, Flags);
120        if (pInfo == null) return;
121        object next = pInfo.GetGetMethod(true).Invoke(cSource, null);
122        cSource = next;
123      }
124      #endregion
125
126      string[] targetProperties = targetPath.Split('.');
127      object cTarget = target;
128      MethodInfo targetSetter = null;
129      #region Navigate to target property
130      for (int i = 0; i < targetProperties.Length - 1; i++) {
131        PropertyInfo pInfo = cTarget.GetType().GetProperty(targetProperties[i], Flags);
132        if (pInfo == null) return;
133        cTarget = pInfo.GetGetMethod(true).Invoke(cTarget, null);
134        if (cTarget == null) return;
135      }
136      targetSetter = cTarget.GetType().GetProperty(targetProperties.Last(), Flags).GetSetMethod(true);
137      if (targetSetter == null) return;
138      #endregion
139
140      if (bindingFunc != null) {
141        targetSetter.Invoke(cTarget, new object[] { bindingFunc.DynamicInvoke(new object[] { cSource }) });
142      } else if (cSource == null && !targetSetter.GetParameters().First().ParameterType.IsValueType
143        || cSource != null && cSource.GetType().IsValueType)
144        targetSetter.Invoke(cTarget, new object[] { cSource });
145      else if (cSource is ICloneable)
146        targetSetter.Invoke(cTarget, new object[] { ((ICloneable)cSource).Clone() });
147      else throw new InvalidOperationException("Can only bind to targets which are either a ValueType or an ICloneable.");
148    }
149
150    private bool TryHookToChangedEvent(object obj, string property) {
151      INotifyPropertyChanged npc = (obj as INotifyPropertyChanged);
152      if (npc != null) {
153        npc.PropertyChanged += new PropertyChangedEventHandler(source_PropertyChanged);
154        return true;
155      } else {
156        EventInfo eInfo = obj.GetType().GetEvent(property + "Changed", Flags);
157        if (eInfo != null) {
158          Type[] methodParams = eInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray();
159          if (!eventHandlerCache.ContainsKey(methodParams[1]))
160            CreateAndAddDynamicEventHandler(eInfo, methodParams);
161          eInfo.AddEventHandler(obj, eventHandlerCache[methodParams[1]].CreateDelegate(eInfo.EventHandlerType, this));
162        }
163        return eInfo != null;
164      }
165    }
166
167    private bool TryUnhookFromChangedEvent(object obj, string property) {
168      INotifyPropertyChanged npc = (obj as INotifyPropertyChanged);
169      if (npc != null) {
170        npc.PropertyChanged -= new PropertyChangedEventHandler(source_PropertyChanged);
171        return true;
172      } else {
173        EventInfo eInfo = obj.GetType().GetEvent(property, Flags);
174        if (eInfo != null) {
175          Type[] methodParams = eInfo.EventHandlerType.GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray();
176          if (eventHandlerCache.ContainsKey(methodParams[1]))
177            eInfo.RemoveEventHandler(obj, eventHandlerCache[methodParams[1]].CreateDelegate(eInfo.EventHandlerType, this));
178        }
179        return eInfo != null;
180      }
181    }
182
183    private void source_PropertyChanged(object sender, PropertyChangedEventArgs e) {
184      ExecuteBinding(sender);
185    }
186
187    private void CreateAndAddDynamicEventHandler(EventInfo eInfo, Type[] methodParams) {
188      lock (evhCacheLock) {
189        if (!eventHandlerCache.ContainsKey(methodParams[1])) {
190          Type[] instanceMethodParams = new Type[] { GetType() }.Concat(methodParams).ToArray();
191          DynamicMethod dynamicEventHandler = new DynamicMethod("source_Changed", null, instanceMethodParams, GetType());
192          MethodInfo executeBindingMethodInfo = GetType().GetMethod("ExecuteBinding", Flags);
193          ILGenerator iLG = dynamicEventHandler.GetILGenerator();
194          iLG.Emit(OpCodes.Ldarg_0); // load 'this' pointer
195          iLG.Emit(OpCodes.Ldarg_1); // load sender parameter
196          iLG.Emit(OpCodes.Call, executeBindingMethodInfo); // call method to execute the binding
197          iLG.Emit(OpCodes.Ret);
198          eventHandlerCache.Add(methodParams[1], dynamicEventHandler);
199        }
200      }
201    }
202  }
203}
Note: See TracBrowser for help on using the repository browser.