From: jpbruyere Date: Thu, 8 Oct 2015 07:42:37 +0000 (+0200) Subject: new binding system - Groupin in test case members of same target X-Git-Tag: 0.3~114 X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=15b8c20524fd7f5fd19c68d014a16bb6be6153e3;p=jp%2Fcrow.git new binding system - Groupin in test case members of same target - bound created delegate to instance of source, prevent need of reference table. --- diff --git a/Tests/GOLIBTests.cs b/Tests/GOLIBTests.cs index 2825246c..8ad93184 100644 --- a/Tests/GOLIBTests.cs +++ b/Tests/GOLIBTests.cs @@ -34,12 +34,11 @@ namespace test int frameCpt = 0; int idx = 0; string[] testFiles = { - "test4.goml", + "fps.goml", "testContainer.goml", "testBorder.goml", "testLabel.goml", "testCheckbox.goml", - "fps.goml", "testRadioButton.goml", "testSpinner.goml", "testPopper.goml", @@ -49,7 +48,7 @@ namespace test "testWindow.goml", "testMsgBox.goml", "testGrid.goml", - + "test4.goml", "testMeter.goml", }; @@ -100,12 +99,12 @@ namespace test protected override void OnLoad (EventArgs e) { base.OnLoad (e); - this.AddWidget(new test4()); - //LoadInterface("Interfaces/" + testFiles[idx]); + //this.AddWidget(new test4()); + LoadInterface("Interfaces/" + testFiles[idx]); } protected override void OnUpdateFrame (FrameEventArgs e) { - if (frameCpt % 8 == 0) + //if (frameCpt % 8 == 0) base.OnUpdateFrame (e); fps = (int)RenderFrequency; diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 1c88e560..725c9e80 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -8,7 +8,7 @@ Exe Tests Tests - test.GOLIBTest_4 + test.GOLIBTests v4.5 ..\bin\$(configuration) obj\$(configuration) diff --git a/src/CompilerServices/CompilerServices.cs b/src/CompilerServices/CompilerServices.cs index 4b3a4448..44b930e0 100644 --- a/src/CompilerServices/CompilerServices.cs +++ b/src/CompilerServices/CompilerServices.cs @@ -17,9 +17,99 @@ namespace go PropertyBinding } public class Binding{ - public BindingType BindingType; - public string MemberName; - public string BindingExpression; + public PropertyInfo Property; + public string Expression; + + public Binding(){ + } + public Binding(PropertyInfo _property, string _expression) + { + Property = _property; + Expression = _expression; + } + } + + public class ResolvedBinding + { + public object Target; + public MemberInfo Member; + + public Binding Binding; + + public ResolvedBinding(){ + } + public ResolvedBinding(Binding _binding){ + Binding = _binding; + } + + public bool FindTarget(GraphicObject source){ + string member = null; + + if (string.IsNullOrEmpty (Binding.Expression)) { + Target = source; + Member = null; + return true; + } + + string[] bindingExp = Binding.Expression.Split ('/'); + + if (bindingExp.Length == 1) { + //datasource binding + Target = source.DataSource; + member = bindingExp [0]; + } else { + int ptr = 0; + ILayoutable tmp = source; + if (string.IsNullOrEmpty (bindingExp [0])) { + //if exp start with '/' => Graphic tree parsing start at top container + tmp = source.TopContainer as ILayoutable; + ptr++; + } + while (ptr < bindingExp.Length - 1) { + if (tmp == null) + return false; + if (bindingExp [ptr] == "..") + tmp = tmp.Parent as GraphicObject; + else if (bindingExp [ptr] == ".") { + if (ptr > 0) + throw new Exception ("Syntax error in binding, './' may only appear in first position"); + tmp = source; + }else + tmp = (tmp as GraphicObject).FindByName (bindingExp [ptr]); + ptr++; + } + + string[] bindTrg = bindingExp [ptr].Split ('.'); + + if (bindTrg.Length == 2) { + tmp = (tmp as GraphicObject).FindByName (bindTrg [0]); + member = bindTrg [1]; + } else + throw new Exception ("Syntax error in binding, expected 'go dot member'"); + Target = tmp; + } + if (Target == null) { + Debug.WriteLine ("Binding Source is null: " + Binding.Expression); + return false; + } + Type targetType = Target.GetType (); + Member = targetType.GetMember (member).FirstOrDefault (); + + #region search for extensions methods if member not found in type + if (Member == null && !string.IsNullOrEmpty(member)) + { + Assembly a = Assembly.GetExecutingAssembly(); + Member = CompilerServices.GetExtensionMethods(a, targetType).FirstOrDefault(); + } + #endregion + + if (member == null) { + Debug.WriteLine ("Binding member not found: " + member); + return false; + } + + return true; + } } public static class CompilerServices2 @@ -153,6 +243,10 @@ namespace go } public static class CompilerServices { + public static void CreateBindingHandler() + { + } + static int dynHandleCpt = 0; /// diff --git a/src/GraphicObjects/GraphicObject.cs b/src/GraphicObjects/GraphicObject.cs index 0617e927..7aba98c8 100644 --- a/src/GraphicObjects/GraphicObject.cs +++ b/src/GraphicObjects/GraphicObject.cs @@ -16,12 +16,15 @@ using System.ComponentModel; using System.IO; //using System.Xml; using System.Xml; +using System.Runtime.CompilerServices; +using System.Reflection.Emit; namespace go { public class GraphicObject : IXmlSerializable, ILayoutable, IValueChange { internal List DynamicMethodIds = new List (); + internal List Bindings = new List (); #region IValueChange implementation public event EventHandler ValueChanged; @@ -329,7 +332,16 @@ namespace go [XmlIgnore]public object DataSource { set { + if (dataSource == value) + return; + + if (dataSource != null) + this.ClearBinding (); + dataSource = value; + + if (dataSource != null) + this.ResolveBindings(); } get { return dataSource == null ? @@ -775,7 +787,161 @@ namespace go tmp = Parent.ToString () + tmp; return Name == "unamed" ? tmp + "." + this.GetType ().Name : tmp + "." + Name; } - + + public virtual void ResolveBindings() + { + List resolved = new List (); + foreach (Binding b in Bindings) { + ResolvedBinding rb = new ResolvedBinding (b); + if (!rb.FindTarget (this)) + continue; + resolved.Add (rb); + } + IEnumerable groupedByTarget = resolved.GroupBy (g => g.Target, g => g, (k, g) => g.ToArray ()); + foreach (ResolvedBinding[] grouped in groupedByTarget) { + int i = 0; + object targetValue = grouped [0].Target;//empty binding exp=> bound to target object by default + Type targetType = targetValue.GetType(); + Type sourceType = this.GetType(); + + DynamicMethod dm = null; + ILGenerator il = null; + + MethodInfo stringEquals = typeof(string).GetMethod + ("op_Equality", new Type[2] {typeof(string), typeof(string)}); + + + System.Reflection.Emit.Label[] jumpTable = null; + System.Reflection.Emit.Label endMethod = new System.Reflection.Emit.Label(); + + LocalBuilder lbMemberName = null; + LocalBuilder lbValue = null; + + #region Retrieve EventHandler parameter type + EventInfo ei = targetType.GetEvent ("ValueChanged"); + //no dynamic update if ValueChanged interface is not implemented + if (ei != null){ + MethodInfo evtInvoke = ei.EventHandlerType.GetMethod ("Invoke"); + ParameterInfo[] evtParams = evtInvoke.GetParameters (); + Type handlerArgsType = evtParams [1].ParameterType; + + Type[] args = {typeof(object), typeof(object),handlerArgsType}; + dm = new DynamicMethod("dynHandle", + typeof(void), + args, + sourceType); + + + il = dm.GetILGenerator(256); + + + endMethod = il.DefineLabel(); + jumpTable = new System.Reflection.Emit.Label[grouped.Length]; + for (i = 0; i < grouped.Length; i++) + jumpTable [i] = il.DefineLabel (); + lbMemberName = il.DeclareLocal(typeof(string)); + lbValue = il.DeclareLocal(typeof(object)); + + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldarg_0); + //il.Emit(OpCodes.Isinst, sourceType); + //push new value onto stack + il.Emit(OpCodes.Ldarg_2); + FieldInfo fiNewValue = typeof(ValueChangeEventArgs).GetField("NewValue"); + il.Emit(OpCodes.Ldfld, fiNewValue); + il.Emit(OpCodes.Stloc, lbValue); + //push name + il.Emit(OpCodes.Ldarg_2); + FieldInfo fiMbName = typeof(ValueChangeEventArgs).GetField("MemberName"); + il.Emit(OpCodes.Ldfld, fiMbName); + il.Emit(OpCodes.Stloc, lbMemberName); + il.Emit(OpCodes.Ldloc, lbMemberName); + il.Emit(OpCodes.Brfalse, endMethod); + } + #endregion + + i = 0; + foreach (ResolvedBinding rb in grouped) { + #region initialize target with actual value + if (rb.Member != null){ + if (rb.Member.MemberType == MemberTypes.Property) + targetValue = (rb.Member as PropertyInfo).GetGetMethod ().Invoke (rb.Target, null); + else if (rb.Member.MemberType == MemberTypes.Field) + targetValue = (rb.Member as FieldInfo).GetValue (rb.Target); + else if (rb.Member.MemberType == MemberTypes.Method){ + MethodInfo mthSrc = rb.Member as MethodInfo; + if (mthSrc.IsDefined(typeof(ExtensionAttribute), false)) + targetValue = mthSrc.Invoke(null, new object[] {rb.Target}); + else + targetValue = mthSrc.Invoke(rb.Target, null); + }else + throw new Exception ("unandled source member type for binding"); + } + //TODO: handle other dest type conversions + if (rb.Binding.Property.PropertyType == typeof(string)){ + if (targetValue != null) + targetValue = targetValue.ToString (); + } + rb.Binding.Property.GetSetMethod ().Invoke (this, new object[] { targetValue }); + #endregion + + //if no dyn update, skip jump table + if (il == null) + continue; + + il.Emit (OpCodes.Ldloc, lbMemberName); + il.Emit (OpCodes.Ldstr, rb.Member.Name); + il.Emit (OpCodes.Callvirt, stringEquals); + il.Emit (OpCodes.Brtrue, jumpTable[i]); + i++; + } + + if (il == null) + continue; + + il.Emit (OpCodes.Br, endMethod); + + i = 0; + foreach (ResolvedBinding rb in grouped) { + + il.MarkLabel (jumpTable [i]); + il.Emit(OpCodes.Ldloc, lbValue); + + //by default, target value type is deducted from source member type to allow + //memberless binding, if targetMember exists, it will be used to determine target + //value type for conversion + Type targetValueType = rb.Binding.Property.PropertyType; + if (rb.Member != null) { + if (rb.Member.MemberType == MemberTypes.Property) + targetValueType = (rb.Member as PropertyInfo).PropertyType; + else if (rb.Member.MemberType == MemberTypes.Field) + targetValueType = (rb.Member as FieldInfo).FieldType; + else + throw new Exception ("unhandle target member type in binding"); + } + + if (!targetValueType.IsValueType) + il.Emit(OpCodes.Castclass, targetValueType); + else if (rb.Binding.Property.PropertyType != targetValueType) + il.Emit(OpCodes.Callvirt, CompilerServices.GetConvertMethod( rb.Binding.Property.PropertyType )); + else + il.Emit(OpCodes.Unbox_Any, rb.Binding.Property.PropertyType); + + il.Emit(OpCodes.Callvirt, rb.Binding.Property.GetSetMethod()); + il.Emit (OpCodes.Br, endMethod); + i++; + + } + il.MarkLabel(endMethod); + il.Emit(OpCodes.Pop); + il.Emit(OpCodes.Ret); + + Delegate del = dm.CreateDelegate(ei.EventHandlerType, this); + MethodInfo addHandler = ei.GetAddMethod (); + addHandler.Invoke(grouped [0].Target, new object[] {del}); + } + } + #region IXmlSerializable public virtual System.Xml.Schema.XmlSchema GetSchema () { @@ -848,18 +1014,11 @@ namespace go // } else { if (attValue.StartsWith("{")) { - if (Interface.DontResoveGOML) - continue; //binding if (!attValue.EndsWith("}")) throw new Exception (string.Format("GOML:Malformed binding: {0}", attValue)); - string strBinding = attValue.Substring (1, attValue.Length - 2); - Interface.GOMLResolver.Add (new DynAttribute () { - Source = this, - MemberName = attName, - Value = strBinding - }); + this.Bindings.Add (new Binding (pi, attValue.Substring (1, attValue.Length - 2))); continue; } @@ -976,26 +1135,26 @@ namespace go /// and delete ref of this in Shared interface refs /// public virtual void ClearBinding(){ - object ds = this.DataSource; - if (ds != null) { - Type dataSourceType = ds.GetType (); - EventInfo evtInfo = dataSourceType.GetEvent ("ValueChanged"); - if (evtInfo != null) { - FieldInfo evtFi = CompilerServices.GetEventHandlerField (dataSourceType, "ValueChanged"); - MulticastDelegate multicastDelegate = evtFi.GetValue (ds) as MulticastDelegate; - if (multicastDelegate != null) { - foreach (Delegate d in multicastDelegate.GetInvocationList()) { - string dn = d.Method.Name; - if (!dn.StartsWith ("dynHandle_")) - continue; - int did = int.Parse (dn.Substring (10)); - if (this.DynamicMethodIds.Contains (did)) - evtInfo.RemoveEventHandler (ds, d); - } - } - } - } - Interface.Unreference (this); +// object ds = this.DataSource; +// if (ds != null) { +// Type dataSourceType = ds.GetType (); +// EventInfo evtInfo = dataSourceType.GetEvent ("ValueChanged"); +// if (evtInfo != null) { +// FieldInfo evtFi = CompilerServices.GetEventHandlerField (dataSourceType, "ValueChanged"); +// MulticastDelegate multicastDelegate = evtFi.GetValue (ds) as MulticastDelegate; +// if (multicastDelegate != null) { +// foreach (Delegate d in multicastDelegate.GetInvocationList()) { +// string dn = d.Method.Name; +// if (!dn.StartsWith ("dynHandle_")) +// continue; +// int did = int.Parse (dn.Substring (10)); +// if (this.DynamicMethodIds.Contains (did)) +// evtInfo.RemoveEventHandler (ds, d); +// } +// } +// } +// } +// Interface.Unreference (this); } } diff --git a/src/GraphicObjects/Group.cs b/src/GraphicObjects/Group.cs index f711bb88..5c2b7df2 100644 --- a/src/GraphicObjects/Group.cs +++ b/src/GraphicObjects/Group.cs @@ -83,7 +83,12 @@ namespace go return true; } } - + public override void ResolveBindings () + { + base.ResolveBindings (); + foreach (GraphicObject w in Children) + w.ResolveBindings (); + } public override GraphicObject FindByName (string nameToFind) { if (Name == nameToFind) diff --git a/src/GraphicObjects/PrivateContainer.cs b/src/GraphicObjects/PrivateContainer.cs index 3d29177d..e6f826dc 100644 --- a/src/GraphicObjects/PrivateContainer.cs +++ b/src/GraphicObjects/PrivateContainer.cs @@ -67,6 +67,12 @@ namespace go } #region GraphicObject Overrides + public override void ResolveBindings () + { + base.ResolveBindings (); + if (child != null) + child.ResolveBindings (); + } public override GraphicObject FindByName (string nameToFind) { if (Name == nameToFind) diff --git a/src/Interface.cs b/src/Interface.cs index 9dccd969..304e48b6 100644 --- a/src/Interface.cs +++ b/src/Interface.cs @@ -438,22 +438,10 @@ namespace go XmlSerializerNamespaces xn = new XmlSerializerNamespaces (); xn.Add ("", ""); - //prevent unused ref in References created by xmlSerializer - Interface.DontResoveGOML = true; XmlSerializer xs = new XmlSerializer (type); - Interface.DontResoveGOML = false; - GOMLResolutionStack.Push (new List ()); result = (GraphicObject)xs.Deserialize (stream); - //result.DataSource = hostClass; - - if (hostClass == null) { - GOMLResolutionStack.Pop (); - return result; - } - - if (resolve) - resolveGOML (hostClass); + result.DataSource = hostClass; #if DEBUG_LOAD_TIME loadingTime.Stop ();