智能表单设计器Web Free Form Designer:FreeForm动态数据验证和动态自动计算
Posted on 2011-06-25 17:37 智能在线表单设计器 Web Form Builder 阅读(3864) 评论(7) 编辑 收藏 举报FreeForm自动计算及数据验证
Silverlight中动态数据验证和动态自动计算的Reflection反射实现
上周在博客园发了个首页随笔,因为被误认为是广告而被移出首页,这次发首页,特地备足了技术材料,结合FreeForm实际的开发情况,从技术上分析在Silverlight中实现动态数据验证和自动计算的方法。我们知道在.Net 4.0标准类库中,反射的类很全,非常好用,但在Silverlight类库中涉及反射的命名空间虽然也有System.Reflection 和System.Reflection.Emit ,但类和方法大大的缩减了。以前我们在WinForm或者ASP.Net中实现动态验证和动态计算非常容易,可以通过类似如下代码实现:
// compile string code to create class, generate an intance and mothod
public Expression(string expression, string fieldID, AppCom appcom)
{
this.Com = appcom;
if (expression.IndexOf("return") < 0) expression = "return " + expression + ";";
string className = "ExpressionValidate";
string methodName = "Compute";
if (!string.IsNullOrEmpty(fieldID))
{
methodName = ("Validate_" + fieldID).Replace(":", "_");
methodName = methodName.Replace("@", "Att_");
}
CompilerParameters cp = new CompilerParameters();
cp.GenerateInMemory = true;
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("mscorlib.dll");
cp.ReferencedAssemblies.Add("System.Data.dll");
cp.ReferencedAssemblies.Add("System.Xml.dll");
// cp.ReferencedAssemblies.Add(Com.ClassDir + "ComFun_cs.dll");
CompilerResults cr = new CSharpCodeProvider()
.CompileAssemblyFromSource(cp, string.
Format(@"using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Xml;
sealed class {0}{{
XmlDocument xdoc;
XmlNamespaceManager nsmgr;
public bool {1}(){{
{2}
}}
{3}
}}",
className, methodName, expression, appcom.ValidateComFuns) + Com.GetComFunInfo());
instance = cr.CompiledAssembly.CreateInstance(className);
method = instance.GetType().GetMethod(methodName);
{
MethodInfo SFun = instance.GetType().GetMethod("Initial");
if (SFun != null)
{
SFun.Invoke(instance, new object[] { AppCom.XDoc, AppCom.nsmgr });
}
}
CLog.Win_ClassExp.AppendText(appcom.ValidateComFuns);
}
// validate one validation method
public bool Compute()
{
return (bool)method.Invoke(instance, null);
}
其中
sealed class {0}{{
XmlDocument xdoc;
XmlNamespaceManager nsmgr;
public bool {1}(){{
{2}
}}
{3}
}}
就是我们运行时从XML动态生成的类,从XML动态传入的参数,返回一个True 或 False的验证结果。参数分别对应如下:
{0}:className 类名
{1}:methodName 方法名
{2}:expression 动态验证表达式
{3}:动态加入的调用代码
很遗憾,这是在标准类库的快捷方便的写法,但是到了Silverlight微缩类库这里,很多类已不存在了。CodeDomProvider类没有了,Microsoft.VisualBasic
命名空间没有了,CompilerParameters 类没有了,CompilerResults类没有了,Microsoft.CSharp.CSharpCodeProvider类没有了,Microsoft.CSharp
下还有2个类:Microsoft.CSharp.RuntimeBinder. RuntimeBinderException类和Microsoft.CSharp.RuntimeBinder.RuntimeBinderInternalCompilerException,很明显这2个类是处理异常的。真的要绝望了。
经过捣腾System.Reflection.Assembly类,我们发现有4个方法:
System.Reflection.Assembly.GetCallingAssembly();
System.Reflection.Assembly.GetExecutingAssembly();
System.Reflection.Assembly.Load(assemblyName);
System.Reflection.Assembly.LoadFrom(assemblyFile);
发现Silverlight中的反射与.NET FRAMEWORK中的略有不同。以下分三种情况描述:
1:动态创建当前执行的程序集中的类实例。
2:动态创建XAP包中其它SILVERLIGHT程序集中的类实例。
3:动态下载并创建网站上其它SILVERLIGHT程序集中的类实例。
我们可以利用后两种方法来动态加载程序集,CSharpCodeProvider那种方便快捷的写法不可能用了,于是只能用System.Reflection.Emit 命名空间,定义动态程序集,关于这方面,微软有详细介绍:
http://msdn.microsoft.com/zh-cn/library/4xtysk39%28v=VS.95%29.aspx
首先需要了解生成方法体的 MSIL 指令代码,感觉和大学学的汇编差不多,
http://msdn.microsoft.com/zh-cn/library/system.reflection.emit.opcodes%28v=VS.95%29.aspx
贴一个通过Emit动态生成程序集的例子。其实是动态生成了这个类:
public class MyDynamicType
{
private int m_number;
public MyDynamicType() : this(42) {}
public MyDynamicType(int initNumber)
{
m_number = initNumber;
}
public int Number
{
get { return m_number; }
set { m_number = value; }
}
public int MyMethod(int multiplier)
{
return m_number * multiplier;
}
}
class Example
{
public static void Demo(System.Windows.Controls.TextBlock outputBlock)
{
AssemblyName aName = new AssemblyName("DynamicAssemblyExample");
AssemblyBuilder ab =
AppDomain.CurrentDomain.DefineDynamicAssembly(
aName,
AssemblyBuilderAccess.Run);
// Create the module.
ModuleBuilder mb = ab.DefineDynamicModule(aName.Name);
TypeBuilder tb = mb.DefineType(
"MyDynamicType",
TypeAttributes.Public);
// Add a private field of type int (Int32).
FieldBuilder fbNumber = tb.DefineField(
"m_number",
typeof(int),
FieldAttributes.Private);
// Define a constructor that takes an integer argument and
// stores it in the private field.
Type[] parameterTypes = { typeof(int) };
ConstructorBuilder ctor1 = tb.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
parameterTypes);
ILGenerator ctor1IL = ctor1.GetILGenerator();
// For a constructor, argument zero is a reference to the new
// instance. Push it on the stack before calling the base
// class constructor. Specify the default constructor of the
// base class (System.Object) by passing an empty array of
// types (Type.EmptyTypes) to GetConstructor.
ctor1IL.Emit(OpCodes.Ldarg_0);
ctor1IL.Emit(OpCodes.Call,
typeof(object).GetConstructor(Type.EmptyTypes));
// Push the instance on the stack before pushing the argument
// that is to be assigned to the private field m_number.
ctor1IL.Emit(OpCodes.Ldarg_0);
ctor1IL.Emit(OpCodes.Ldarg_1);
ctor1IL.Emit(OpCodes.Stfld, fbNumber);
ctor1IL.Emit(OpCodes.Ret);
// Define a default constructor that supplies a default value
// for the private field. For parameter types, pass the empty
// array of types or pass null.
ConstructorBuilder ctor0 = tb.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
Type.EmptyTypes);
ILGenerator ctor0IL = ctor0.GetILGenerator();
// For a constructor, argument zero is a reference to the new
// instance. Push it on the stack before pushing the default
// value on the stack, then call constructor ctor1.
ctor0IL.Emit(OpCodes.Ldarg_0);
ctor0IL.Emit(OpCodes.Ldc_I4_S, 42);
ctor0IL.Emit(OpCodes.Call, ctor1);
ctor0IL.Emit(OpCodes.Ret);
// Define a property named Number that gets and sets the private
// field.
//
// The last argument of DefineProperty is null, because the
// property has no parameters. (If you don't specify null, you must
// specify an array of Type objects. For a parameterless property,
// use the built-in array with no elements: Type.EmptyTypes)
PropertyBuilder pbNumber = tb.DefineProperty(
"Number",
PropertyAttributes.HasDefault,
typeof(int),
null);
// The property "set" and property "get" methods require a special
// set of attributes.
MethodAttributes getSetAttr = MethodAttributes.Public |
MethodAttributes.SpecialName | MethodAttributes.HideBySig;
// Define the "get" accessor method for Number. The method returns
// an integer and has no arguments. (Note that null could be
// used instead of Types.EmptyTypes)
MethodBuilder mbNumberGetAccessor = tb.DefineMethod(
"get_Number",
getSetAttr,
typeof(int),
Type.EmptyTypes);
ILGenerator numberGetIL = mbNumberGetAccessor.GetILGenerator();
// For an instance property, argument zero is the instance. Load the
// instance, then load the private field and return, leaving the
// field value on the stack.
numberGetIL.Emit(OpCodes.Ldarg_0);
numberGetIL.Emit(OpCodes.Ldfld, fbNumber);
numberGetIL.Emit(OpCodes.Ret);
// Define the "set" accessor method for Number, which has no return
// type and takes one argument of type int (Int32).
MethodBuilder mbNumberSetAccessor = tb.DefineMethod(
"set_Number",
getSetAttr,
null,
new Type[] { typeof(int) });
ILGenerator numberSetIL = mbNumberSetAccessor.GetILGenerator();
// Load the instance and then the numeric argument, then store the
// argument in the field.
numberSetIL.Emit(OpCodes.Ldarg_0);
numberSetIL.Emit(OpCodes.Ldarg_1);
numberSetIL.Emit(OpCodes.Stfld, fbNumber);
numberSetIL.Emit(OpCodes.Ret);
// Last, map the "get" and "set" accessor methods to the
// PropertyBuilder. The property is now complete.
pbNumber.SetGetMethod(mbNumberGetAccessor);
pbNumber.SetSetMethod(mbNumberSetAccessor);
// Define a method that accepts an integer argument and returns
// the product of that integer and the private field m_number. This
// time, the array of parameter types is created on the fly.
MethodBuilder meth = tb.DefineMethod(
"MyMethod",
MethodAttributes.Public,
typeof(int),
new Type[] { typeof(int) });
ILGenerator methIL = meth.GetILGenerator();
// To retrieve the private instance field, load the instance it
// belongs to (argument zero). After loading the field, load the
// argument one and then multiply. Return from the method with
// the return value (the product of the two numbers) on the
// execution stack.
methIL.Emit(OpCodes.Ldarg_0);
methIL.Emit(OpCodes.Ldfld, fbNumber);
methIL.Emit(OpCodes.Ldarg_1);
methIL.Emit(OpCodes.Mul);
methIL.Emit(OpCodes.Ret);
// Finish the type.
Type t = tb.CreateType();
// The code can be executed immediately. Start by getting reflection
// objects for the method and the property.
MethodInfo mi = t.GetMethod("MyMethod");
PropertyInfo pi = t.GetProperty("Number");
// Create an instance of MyDynamicType using the default
// constructor.
object o1 = Activator.CreateInstance(t);
// Display the value of the property, then change it to 127 and
// display it again. Use null to indicate that the property
// has no index.
outputBlock.Text += String.Format("o1.Number: {0}\n", pi.GetValue(o1, null));
pi.SetValue(o1, 127, null);
outputBlock.Text += String.Format("o1.Number: {0}\n", pi.GetValue(o1, null));
// Call MyMethod, passing 22, and display the return value, 22
// times 127. Arguments must be passed as an array, even when
// there is only one.
object[] arguments = { 22 };
outputBlock.Text += String.Format("o1.MyMethod(22): {0}\n",
mi.Invoke(o1, arguments));
// Create an instance of MyDynamicType using the constructor
// that specifies m_Number. The constructor is identified by
// matching the types in the argument array. In this case,
// the argument array is created on the fly. Display the
// property value.
object o2 = Activator.CreateInstance(t,
new object[] { 5280 });
outputBlock.Text += String.Format("o2.Number: {0}\n", pi.GetValue(o2, null));
}
}
好了,原理清晰了,一起看看在Siverlight上实现的例子吧(一定要看最后一节的“动态自定义验证
”),感谢大家。
到这里还是请管理员不要把我的帖子当做广告,我想这么实实在在的Siverlight广告实在是没有的。
FreeForm自动计算
创建2个控件用于演示自动计算:
进入Runtime Design 添加自动计算
字符型公式
选择ToUpper
然后将鼠标放在括号()的中间
请按键盘 Ctrl 键,将自动出现当前的控件列表,选择FieldA
点击 Add ,并点击 OK
在FieldA输入abcd,然后进入Runtime Design 测试自动计算
点击“Test Calculation”
可以看到效果:
其他操作的方法也是类似。
时间公式
为FieldA添加公式ShortDateString:
效果:
选择 Now
点击“Test Calculation”
可以看到效果:
数字公式
还记得圆面积的计算公式吗?是πr²(圆周率*半径的平方)
添加公式:
我们现在要试验一下,半径输入5
点击“Test Calculation”
可以看到效果:
FreeForm提供了2种数据验证方式:一般验证和自定义验证,前者是为较简单的验证提供支持,后者可以编写复杂的组合,可以用于较复杂的业务系统。
FreeForm一般验证
进入Runtime Design 添加验证:
一些例子
比如我们要规定Min Age最小年龄必须等于1,最大年龄必须小于等于150,那么可以这么定义:
如果我们输入了错误的值,点击“Test Validations”或者“Design Check”中的“Check”。
会自动出现错误提示。
支持的验证方法还有很多,不一一介绍,其中就包括几乎万能的正则表达式。
FreeForm动态自定义验证
一般验证有局限性,那就是只能一个语句一个语句的录入,一些复杂的验证难以实现,于是我们就开发了动态的客户自定义验证方式.
进入Runtime Design 添加自定义验证:
一些例子
比如我们规定在性别选择了Male或者Female的时候,real Age必须大于Min Age,同时,必须小于Max Age,在不选Male和Female的时候,不予验证。
我们可以这样定义(选择简练的3目运算符):
( [Male] == 'True' || [Female] == 'True') ?
( [Male] == 'True' || [Female] == 'True') && ( [RealAge] >= [MinAge]) && ( [RealAge] <= [MaxAge] ) : true
或者用更直观的If-Else写法:
if ([Male] == 'True' ||[Female] == 'True')
{( [RealAge] >= [MinAge]) && ( [RealAge] <= [MaxAge] ) ;}
else {true;}
声明变量也是可以的:
var MaleorFemale=([Male] == 'True' ||[Female] == 'True');
if (MaleorFemale == true)
{( [RealAge] >= [MinAge]) && ( [RealAge] <= [MaxAge] ) ;}
else {true;}
千万不要忘了键盘 Ctrl 键,它将自动出现当前的控件列表,非常方便。效果如下:
测试一下效果
当有一项性别选中时,年龄大于150
提示错误:
并且显示红框:
当性别不选的时候,即使大于150也不会抱错。
这个例子我们放在Demo菜单里,您可以测试。
验证可以通过点击“Test Validations”或者“Design Check”中的“Check”。
或
Demonstrate(ver2011):
.