From: Jean-Philippe Bruyère Date: Sat, 15 Dec 2018 06:13:54 +0000 (+0100) Subject: first tests with DataSourceType to prevent using reflexion too often in bindings X-Git-Tag: v0.9.5-beta~143 X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=cba179de19b7d32ba1ca659652a88442e867fe08;p=jp%2Fcrow.git first tests with DataSourceType to prevent using reflexion too often in bindings --- diff --git a/Crow/src/CompilerServices/CompilerServices.cs b/Crow/src/CompilerServices/CompilerServices.cs index ee6770a8..80e47e9d 100644 --- a/Crow/src/CompilerServices/CompilerServices.cs +++ b/Crow/src/CompilerServices/CompilerServices.cs @@ -382,8 +382,6 @@ namespace Crow.IML tmp = gi.Invoke(instance, null); dstType = gi.ReturnType; } - - if (tmp != null) return tmp; if (dstType == typeof(string) || dstType == typeof (object))//TODO:object should be allowed to return null and not "" @@ -397,23 +395,6 @@ namespace Crow.IML return null; } - - /// - /// search for extentions method in entry assembly then in assembly where the type is defined - /// - /// Extention MethodInfo - /// Extended type - /// Extention method name - //internal static MethodInfo SearchExtMethod(Type t, string methodName){ - // MethodInfo mi = null; - // mi = GetExtensionMethods (Assembly.GetEntryAssembly(), t) - // .Where (em => em.Name == methodName).FirstOrDefault (); - // if (mi != null) - // return mi; - - // return GetExtensionMethods (t.Module.Assembly, t) - // .Where (em => em.Name == methodName).FirstOrDefault (); - //} internal static MethodInfo SearchExtMethod (Type t, string methodName) { string key = t.Name + "." + methodName; @@ -997,6 +978,77 @@ namespace Crow.IML return fi.GetValue (data); } + internal static MemberInfo GetMemberInfo (Type dataType, string member, out Type returnType) + { + MethodInfo miGetDatas = dataType.GetMethod (member, new Type [] { }); + if (miGetDatas != null) { + returnType = miGetDatas.ReturnType; + return miGetDatas; + } + + MemberInfo mbi = dataType.GetMember (member).FirstOrDefault (); + if (mbi == null) + miGetDatas = CompilerServices.SearchExtMethod (dataType, member); + else { + if (mbi is FieldInfo) { + FieldInfo fi = mbi as FieldInfo; + returnType = fi.FieldType; + return mbi; + } + if (mbi.MemberType == MemberTypes.Property) + miGetDatas = (mbi as PropertyInfo).GetGetMethod (); + else + miGetDatas = mbi as MethodInfo; + } + returnType = miGetDatas?.ReturnType; + return miGetDatas; + + } + internal static void emitGetMemberValue (ILGenerator il, Type dataType, MemberInfo mi) + { + switch (mi.MemberType) { + case MemberTypes.Method: + MethodInfo mim = mi as MethodInfo; + if (mim.IsStatic) + il.Emit (OpCodes.Call, mim); + else + il.Emit (OpCodes.Callvirt, mim); + break; + case MemberTypes.Field: + il.Emit (OpCodes.Ldfld, mi as FieldInfo); + break; + } + } + //object is already on the stack + internal static void emitGetMemberValue (ILGenerator il, Type dataType, string member) + { + MethodInfo miGetDatas = dataType.GetMethod (member, new Type [] { }); + if (miGetDatas != null) { + il.Emit (OpCodes.Callvirt, miGetDatas); + return; + } + MemberInfo mbi = dataType.GetMember (member).FirstOrDefault (); + if (mbi == null) { + MethodInfo miExt = CompilerServices.SearchExtMethod (dataType, member); + if (miExt == null)//and among fields + throw new Exception ($"member {member} not found in {dataType}"); + il.Emit (OpCodes.Call, miExt); + return; + } + if (mbi.MemberType == MemberTypes.Property) { + miGetDatas = (mbi as PropertyInfo)?.GetGetMethod (); + if (miGetDatas == null) + throw new Exception ($"no getter found for property {member} in {dataType}"); + il.Emit (OpCodes.Callvirt, miGetDatas); + return; + } + + FieldInfo fi = mbi as FieldInfo; + if (fi == null) + throw new Exception ($"member {member} not found in {dataType}"); + + il.Emit (OpCodes.Ldfld, fi); + } } } diff --git a/Crow/src/IML/IMLContext.cs b/Crow/src/IML/IMLContext.cs index 8e12c41b..a0e95bef 100644 --- a/Crow/src/IML/IMLContext.cs +++ b/Crow/src/IML/IMLContext.cs @@ -80,6 +80,39 @@ namespace Crow.IML CompilerServices.emitSetCurInterface (il); } + Type curDataSourceType = null; + /// + /// Pushs new node and set datasourcetype to current ds type + /// + /// Crow type. + /// Index. + public void PushNode (Type crowType, int _index = 0) { + nodesStack.Push (new Node (crowType, _index, curDataSourceType)); + } + /// + /// Pops node and set curDS type to previous one in node on top of the stack + /// + /// The node. + public Node PopNode () { + Node n = nodesStack.Pop (); + if (nodesStack.Count > 0) + curDataSourceType = nodesStack.Peek().DataSourceType; + return n; + } + public bool CurrentNodeHasDataSourceType { + get { return curDataSourceType != null; } + } + public Type CurrentDataSourceType { + get { return curDataSourceType; } + } + public void SetDataSourceTypeForCurrentNode (Type dsType) + { + Node n = nodesStack.Pop (); + n.DataSourceType = dsType; + nodesStack.Push (n); + curDataSourceType = dsType; + } + public NodeAddress CurrentNodeAddress { get { Node[] n = nodesStack.ToArray (); @@ -185,7 +218,6 @@ namespace Crow.IML /// Emits the handler method addition, done at end of parsing, Loc_0 is root node instance /// /// Bd. - /// passed as arg to prevent refetching it for the 3rd time public void emitHandlerMethodAddition(EventBinding bd){ //fetch source instance with address for handler addition (as 1st arg of handler.add) diff --git a/Crow/src/IML/Node.cs b/Crow/src/IML/Node.cs index 2ca90e8c..5ba93968 100644 --- a/Crow/src/IML/Node.cs +++ b/Crow/src/IML/Node.cs @@ -37,17 +37,23 @@ namespace Crow.IML public struct Node { #region CTOR - public Node (Type crowType, int _index = 0) + public Node (Type crowType, int _index = 0, Type dsType = null) { CrowType = crowType; Index = _index; + DataSourceType = dsType; } #endregion /// Current node type - public Type CrowType; + public readonly Type CrowType; /// Index in parent, -1 for template - public int Index; + public readonly int Index; + /// + /// DataSourceType attribute if set + /// + public Type DataSourceType; + /// /// retrieve the child addition method depending on the type of this node @@ -68,7 +74,7 @@ namespace Crow.IML return null; } - #region Equality Compare +#region Equality Compare public override bool Equals (object obj) { if (obj == null) @@ -80,7 +86,7 @@ namespace Crow.IML { return CrowType.GetHashCode () ^ Index.GetHashCode (); } - #endregion +#endregion public bool HasTemplate { get { return typeof (TemplatedControl).IsAssignableFrom (CrowType);} @@ -88,6 +94,9 @@ namespace Crow.IML public bool IsTemplatedGroup { get { return typeof (TemplatedGroup).IsAssignableFrom (CrowType);} } + public bool HasDataSourceType { + get { return DataSourceType != null; } + } public static implicit operator string (Node sn) { diff --git a/Crow/src/Instantiator.cs b/Crow/src/Instantiator.cs index 14218bfc..8c5a4d46 100644 --- a/Crow/src/Instantiator.cs +++ b/Crow/src/Instantiator.cs @@ -206,9 +206,9 @@ namespace Crow.IML void parseIML (XmlReader reader) { IMLContext ctx = new IMLContext (findRootType (reader)); - ctx.nodesStack.Push (new Node (ctx.RootType)); + ctx.PushNode (ctx.RootType); emitLoader (reader, ctx); - ctx.nodesStack.Pop (); + ctx.PopNode (); foreach (int idx in templateCachedDelegateIndices) ctx.emitCachedDelegateHandlerAddition(idx, CompilerServices.eiLogicalParentChanged); @@ -429,7 +429,7 @@ namespace Crow.IML using (XmlTextReader reader = new XmlTextReader (tmpXml, XmlNodeType.Element, null)) { reader.Read (); - #if DESIGN_MODE +#if DESIGN_MODE IXmlLineInfo li = (IXmlLineInfo)reader; ctx.il.Emit (OpCodes.Ldloc_0); ctx.il.Emit (OpCodes.Ldstr, this.NextDesignID); @@ -450,23 +450,33 @@ namespace Crow.IML //first check for Style attribute then trigger default value loading if (reader.HasAttributes) { string style = reader.GetAttribute ("Style"); - if (!string.IsNullOrEmpty (style)){ + if (!string.IsNullOrEmpty (style)) { CompilerServices.EmitSetValue (ctx.il, CompilerServices.piStyle, style); - #if DESIGN_MODE +#if DESIGN_MODE emitSetDesignAttribute (ctx, "Style", style); - #endif +#endif } + //check for dataSourceType, if set, datasource bindings will use direct setter/getter + //instead of reflexion + string dataSourceType = reader.GetAttribute ("DataSourceType"); + if (string.IsNullOrEmpty (dataSourceType)) { + //if not set but dataSource is not null, reset dsType to null + string ds = reader.GetAttribute ("DataSource"); + if (!string.IsNullOrEmpty (ds)) + ctx.SetDataSourceTypeForCurrentNode (null); + } else + ctx.SetDataSourceTypeForCurrentNode(CompilerServices.getTypeFromName (dataSourceType)); } - ctx.il.Emit (OpCodes.Ldloc_0); + ctx.il.Emit (OpCodes.Ldloc_0); ctx.il.Emit (OpCodes.Callvirt, CompilerServices.miLoadDefaultVals); - #endregion +#endregion #region Attributes reading if (reader.HasAttributes) { while (reader.MoveToNextAttribute ()) { - if (reader.Name == "Style") + if (reader.Name == "Style" || reader.Name == "DataSourceType") continue; #if DESIGN_MODE @@ -575,13 +585,16 @@ namespace Crow.IML NodeAddress sourceNA = ctx.CurrentNodeAddress; BindingDefinition bindingDef = sourceNA.GetBindingDef (sourceMember, expression); - #if DEBUG_BINDING +#if DEBUG_BINDING Debug.WriteLine("Property Binding: " + bindingDef.ToString()); - #endif +#endif - if (bindingDef.IsDataSourceBinding)//bind on data source - emitDataSourceBindings (ctx, bindingDef); - else + if (bindingDef.IsDataSourceBinding) {//bind on data source + if (ctx.CurrentNodeHasDataSourceType) + emitDataSourceBindings (ctx, bindingDef, ctx.CurrentDataSourceType); + else + emitDataSourceBindings (ctx, bindingDef); + } else ctx.StorePropertyBinding (bindingDef); } @@ -591,7 +604,7 @@ namespace Crow.IML (dataSource as IValueChange).ValueChanged += (EventHandler)dsValueChangedDynMeths [dynMethIdx].CreateDelegate (typeof(EventHandler), dscSource); } - /// Emits remove old data source event handler./summary> + /// Emits remove old data source event handler. void emitRemoveOldDataSourceHandler(ILGenerator il, string eventName, string delegateName, bool DSSide = true){ System.Reflection.Emit.Label cancel = il.DefineLabel (); @@ -968,6 +981,165 @@ namespace Crow.IML ctx.emitCachedDelegateHandlerAddition(delDSIndex, CompilerServices.eiLogicalParentChanged); } /// + /// data source binding with known data type + /// + void emitDataSourceBindings (IMLContext ctx, BindingDefinition bindingDef, Type dsType) + { +#if DEBUG_BINDING_FUNC_CALLS + Console.WriteLine ($"emitDataSourceBindings with data type knows: {bindingDef}"); +#endif + DynamicMethod dm = null; + ILGenerator il = null; + int dmVC = 0; + PropertyInfo piSource = ctx.CurrentNodeType.GetProperty (bindingDef.SourceMember); + //if no dataSource member name is provided, valuechange is not handle and datasource change + //will be used as origine value + string delName = "dyn_DSvalueChanged" + NewId; + if (!string.IsNullOrEmpty (bindingDef.TargetMember)) { + #region create valuechanged method + dm = new DynamicMethod (delName, + typeof (void), + CompilerServices.argsBoundValueChange, true); + + il = dm.GetILGenerator (256); + + System.Reflection.Emit.Label endMethod = il.DefineLabel (); + + il.DeclareLocal (typeof (object)); + + il.Emit (OpCodes.Nop); + + //load value changed member name onto the stack + il.Emit (OpCodes.Ldarg_2); + il.Emit (OpCodes.Ldfld, CompilerServices.fiVCMbName); + + //test if it's the expected one + il.Emit (OpCodes.Ldstr, bindingDef.TargetMember); + il.Emit (OpCodes.Ldc_I4_4);//StringComparison.Ordinal + il.Emit (OpCodes.Call, CompilerServices.stringEquals); + il.Emit (OpCodes.Brfalse, endMethod); + //set destination member with valueChanged new value + //load destination ref + il.Emit (OpCodes.Ldarg_0); + //load new value onto the stack + il.Emit (OpCodes.Ldarg_2); + il.Emit (OpCodes.Ldfld, CompilerServices.fiVCNewValue); + + //by default, source value type is deducted from target member type to allow + //memberless binding, if targetMember exists, it will be used to determine target + //value type for conversion + CompilerServices.emitConvert (il, piSource.PropertyType); + + if (!piSource.CanWrite) + throw new Exception ("Source member of bindind is read only:" + piSource.ToString ()); + + il.Emit (OpCodes.Callvirt, piSource.GetSetMethod ()); + + il.MarkLabel (endMethod); + il.Emit (OpCodes.Ret); + + //vc dyn meth is stored in a cached list, it will be bound to datasource only + //when datasource of source graphic object changed + dmVC = dsValueChangedDynMeths.Count; + dsValueChangedDynMeths.Add (dm); + #endregion + } + + #region emit dataSourceChanged event handler + //now we create the datasource changed method that will init the destination member with + //the actual value of the origin member of the datasource and then will bind the value changed + //dyn methode. + //dm is bound to the instanciator instance to have access to cached dyn meth and delegates + dm = new DynamicMethod ("dyn_dschanged" + NewId, + typeof (void), + CompilerServices.argsBoundDSChange, true); + + il = dm.GetILGenerator (256); + + il.DeclareLocal (typeof (object));//used for checking propery less bindings + il.DeclareLocal (typeof (MemberInfo));//used for checking propery less bindings + il.DeclareLocal (typeof (object));//new datasource store, save one field access + System.Reflection.Emit.Label cancel = il.DefineLabel (); + System.Reflection.Emit.Label newDSIsNull = il.DefineLabel (); + System.Reflection.Emit.Label cancelInit = il.DefineLabel (); + + il.Emit (OpCodes.Nop); + + il.Emit (OpCodes.Ldarg_2);//load datasource change arg + il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); + il.Emit (OpCodes.Stloc_2);//new ds is now loc 2 + + emitRemoveOldDataSourceHandler (il, "ValueChanged", delName, true); + + if (!string.IsNullOrEmpty (bindingDef.TargetMember)) { + if (bindingDef.TwoWay) //remove handler + emitRemoveOldDataSourceHandler (il, "ValueChanged", delName, false); + //test if new ds is null + il.Emit (OpCodes.Ldloc_2); + il.Emit (OpCodes.Brfalse, newDSIsNull);//new ds is null + //test if new ds is of expected type + il.Emit (OpCodes.Ldloc_2); + il.Emit (OpCodes.Isinst, dsType); + //il.Emit (OpCodes.Call, CompilerServices.miGetMDToken); + //il.Emit (OpCodes.Ldc_I4, dsType.MetadataToken); + il.Emit (OpCodes.Brfalse, newDSIsNull); + } + + #region fetch initial Value + if (!string.IsNullOrEmpty (bindingDef.TargetMember)) { + Type mbType; + MemberInfo mi = CompilerServices.GetMemberInfo (dsType, bindingDef.TargetMember, out mbType); + if (mi != null) { + il.Emit (OpCodes.Ldarg_1);//load source of dataSourceChanged which is the dest instance + il.Emit (OpCodes.Ldloc_2);//load new ds + CompilerServices.emitGetMemberValue (il, dsType, mi); + if (mbType != piSource.PropertyType) + CompilerServices.emitConvert (il, mbType, piSource.PropertyType); + il.Emit (OpCodes.Callvirt, piSource.GetSetMethod ()); + } + } + #endregion + + if (!string.IsNullOrEmpty (bindingDef.TargetMember)) { + il.MarkLabel (cancelInit); + //check if new dataSource implement IValueChange + il.Emit (OpCodes.Ldloc_2);//load new datasource + il.Emit (OpCodes.Isinst, typeof (IValueChange)); + il.Emit (OpCodes.Brfalse, cancel); + + il.Emit (OpCodes.Ldarg_0);//load ref to this instanciator onto the stack + il.Emit (OpCodes.Ldarg_1);//load datasource change source + il.Emit (OpCodes.Ldloc_2);//load new datasource + il.Emit (OpCodes.Ldc_I4, dmVC);//load index of dynmathod + il.Emit (OpCodes.Call, CompilerServices.miDSChangeEmitHelper); + + il.MarkLabel (cancel); + + if (bindingDef.TwoWay) { + il.Emit (OpCodes.Ldarg_1);//arg1: dataSourceChange source, the origine of the binding + il.Emit (OpCodes.Ldstr, bindingDef.SourceMember);//arg2: orig member + il.Emit (OpCodes.Ldloc_2);//arg3: new datasource + il.Emit (OpCodes.Ldstr, bindingDef.TargetMember);//arg4: dest member + il.Emit (OpCodes.Call, CompilerServices.miDSReverseBinding); + } + + } + il.MarkLabel (newDSIsNull); + il.Emit (OpCodes.Ret); + + //store dschange delegate in instatiator instance for access while instancing graphic object + int delDSIndex = cachedDelegates.Count; + cachedDelegates.Add (dm.CreateDelegate (CompilerServices.ehTypeDSChange, this)); + #endregion + + ctx.emitCachedDelegateHandlerAddition (delDSIndex, CompilerServices.eiDSChange); + +#if DEBUG_BINDING + Debug.WriteLine("\tDataSource ValueChanged: " + delName); + Debug.WriteLine("\tDataSource Changed: " + dm.Name); +#endif + } + /// /// create the valuechanged handler, the datasourcechanged handler and emit event handling /// void emitDataSourceBindings(IMLContext ctx, BindingDefinition bindingDef){ @@ -1044,13 +1216,18 @@ namespace Crow.IML il.DeclareLocal (typeof(object));//used for checking propery less bindings il.DeclareLocal (typeof(MemberInfo));//used for checking propery less bindings + il.DeclareLocal (typeof (object));//new datasource store, save one field access System.Reflection.Emit.Label cancel = il.DefineLabel (); System.Reflection.Emit.Label newDSIsNull = il.DefineLabel (); System.Reflection.Emit.Label cancelInit = il.DefineLabel (); il.Emit (OpCodes.Nop); - emitRemoveOldDataSourceHandler(il, "ValueChanged", delName, true); + il.Emit (OpCodes.Ldarg_2);//load datasource change arg + il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); + il.Emit (OpCodes.Stloc_2);//new ds is now loc 2 + + emitRemoveOldDataSourceHandler (il, "ValueChanged", delName, true); if (!string.IsNullOrEmpty(bindingDef.TargetMember)){ if (bindingDef.TwoWay){ @@ -1070,15 +1247,13 @@ namespace Crow.IML // il.Emit (OpCodes.Call, CompilerServices.miRemEvtHdlByTarget); // il.MarkLabel(cancelRemove); } - il.Emit (OpCodes.Ldarg_2);//load datasource change arg - il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); + il.Emit (OpCodes.Ldloc_2); il.Emit (OpCodes.Brfalse, newDSIsNull);//new ds is null } #region fetch initial Value if (!string.IsNullOrEmpty(bindingDef.TargetMember)){ - il.Emit (OpCodes.Ldarg_2);//load new datasource - il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); + il.Emit (OpCodes.Ldloc_2); il.Emit (OpCodes.Ldstr, bindingDef.TargetMember);//load member name il.Emit (OpCodes.Call, CompilerServices.miGetMembIinfoWithRefx); il.Emit (OpCodes.Stloc_1);//save memberInfo @@ -1087,8 +1262,7 @@ namespace Crow.IML } il.Emit (OpCodes.Ldarg_1);//load source of dataSourceChanged which is the dest instance - il.Emit (OpCodes.Ldarg_2);//load new datasource - il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); + il.Emit (OpCodes.Ldloc_2);//load new datasource if (!string.IsNullOrEmpty(bindingDef.TargetMember)){ il.Emit (OpCodes.Ldloc_1);//push mi for value fetching il.Emit (OpCodes.Call, CompilerServices.miGetValWithRefx); @@ -1100,15 +1274,13 @@ namespace Crow.IML if (!string.IsNullOrEmpty(bindingDef.TargetMember)){ il.MarkLabel(cancelInit); //check if new dataSource implement IValueChange - il.Emit (OpCodes.Ldarg_2);//load new datasource - il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); + il.Emit (OpCodes.Ldloc_2);//load new datasource il.Emit (OpCodes.Isinst, typeof(IValueChange)); il.Emit (OpCodes.Brfalse, cancel); il.Emit(OpCodes.Ldarg_0);//load ref to this instanciator onto the stack il.Emit (OpCodes.Ldarg_1);//load datasource change source - il.Emit (OpCodes.Ldarg_2);//load new datasource - il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); + il.Emit (OpCodes.Ldloc_2);//load new datasource il.Emit(OpCodes.Ldc_I4, dmVC);//load index of dynmathod il.Emit (OpCodes.Call, CompilerServices.miDSChangeEmitHelper); @@ -1117,8 +1289,7 @@ namespace Crow.IML if (bindingDef.TwoWay){ il.Emit (OpCodes.Ldarg_1);//arg1: dataSourceChange source, the origine of the binding il.Emit (OpCodes.Ldstr, bindingDef.SourceMember);//arg2: orig member - il.Emit (OpCodes.Ldarg_2);//arg3: new datasource - il.Emit (OpCodes.Ldfld, CompilerServices.fiDSCNewDS); + il.Emit (OpCodes.Ldloc_2);//arg3: new datasource il.Emit (OpCodes.Ldstr, bindingDef.TargetMember);//arg4: dest member il.Emit (OpCodes.Call, CompilerServices.miDSReverseBinding); } diff --git a/Tests/BasicTests.cs b/Tests/BasicTests.cs index 7db6fad5..b4d7840c 100644 --- a/Tests/BasicTests.cs +++ b/Tests/BasicTests.cs @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; namespace tests { - class MainClass : Interface + public class MainClass : Interface { [DllImport ("dl", CallingConvention=CallingConvention.Cdecl)] static extern IntPtr dlopen (string file, dlmode mode); @@ -54,7 +54,7 @@ namespace tests /*[DllImport (cairoLibPath, EntryPoint="cairo_stroke")] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] public static extern void cairo_stroke_icall (IntPtr cr);*/ - + public string StringTest = "this is a string for testing"; static MainClass app; public Command CMDTest; @@ -115,9 +115,9 @@ namespace tests //app.AddWidget (@"Interfaces/Divers/testFocus.crow").DataSource = app; //app.AddWidget (@"Interfaces/Divers/testMenu.crow").DataSource = app; //app.AddWidget (@"Interfaces/Divers/testVisibility.crow").DataSource = app; - app.Load (@"Interfaces/Divers/0.crow").DataSource = app; + //app.Load (@"Interfaces/Divers/0.crow").DataSource = app; //app.AddWidget (@"Interfaces/Splitter/1.crow").DataSource = app; - //app.AddWidget (@"Interfaces/GraphicObject/0.crow").DataSource = app; + app.Load (@"Interfaces/GraphicObject/0.crow").DataSource = app; //app.AddWidget (@"Interfaces/TemplatedContainer/test_Listbox.crow").DataSource = app; /*app.instFileDlg = Crow.IML.Instantiator.CreateFromImlFragment diff --git a/Tests/Interfaces/GraphicObject/0.crow b/Tests/Interfaces/GraphicObject/0.crow index 97cda8a0..d608cf00 100755 --- a/Tests/Interfaces/GraphicObject/0.crow +++ b/Tests/Interfaces/GraphicObject/0.crow @@ -1,2 +1,4 @@ - \ No newline at end of file + +