在sliverlight 客户端实现DataTable的尝试,让自定义DataGrid能够轻松绑定DataTable
不知道微软为什么在sl中不支持DataTable,不给老程序员面子么?
好吧,既然不行那我们只能DIY一个,这就开始干活:
第一步:创建 DataRow
既然是表格那我们必须有列,行和他们的集合,咳..什么都没,那我们先创建列:
public class DataColumn
{
#region "Properties"
/// <summary>
/// 列名
/// </summary>
public string ColumnName { get; set; }
/// <summary>
/// 类型
/// </summary>
public Type DataType { get; set; }
/// <summary>
/// 列标题
/// </summary>
public string Caption { get; set; }
/// <summary>
/// 是否允许用户改变列的大小
/// </summary>
public bool AllowResize { get; set; }
/// <summary>
///是否允许用户进行排序
/// </summary>
public bool AllowSort { get; set; }
/// <summary>
/// 是否允许用户进行重新排序
/// </summary>
public bool AllowReorder { get; set; }
/// <summary>
/// 是否只读
/// </summary>
public bool ReadOnly { get; set; }
#endregion
/// <summary>
/// 构造并且赋初始值
/// </summary>
/// <param name="columnName">列名</param>
public DataColumn(string columnName)
{
this.ColumnName = columnName;
this.Caption = columnName;
this.AllowResize = true;
this.AllowSort = true;
this.AllowReorder = true;
this.ReadOnly = false;
}
/// <summary>
/// 重载构造
/// </summary>
/// <param name="columnName">列名</param>
/// <param name="caption">列标题</param>
/// <param name="allowResize">是否允许改变列大小</param>
/// <param name="allowSort">是否允许排序</param>
/// <param name="allowReorder">是否允许重新排序</param>
/// <param name="readOnly">列只读</param>
public DataColumn(string columnName, string caption, bool allowResize, bool allowSort, bool allowReorder, bool readOnly)
{
this.ColumnName = columnName;
this.Caption = caption;
this.AllowResize = allowResize;
this.AllowSort = allowSort;
this.AllowReorder = allowReorder;
this.ReadOnly = readOnly;
}
}
光有列还不够啊,必须要拿出一捆列才行,好吧上列集合:
第二步:创建 DataColumnCollecion:
/// <summary>
/// DataColumn集合,继承与list
/// </summary>
public class DataColumnCollection : List<DataColumn>
{
/// <summary>
/// 隐藏List类中add方法,重新定义Add方法,判断有重复列的时候报出异常
/// </summary>
/// <param name="dc"></param>
public new void Add(DataColumn dc)
{
foreach (DataColumn curColumn in this)
{
if (dc.ColumnName == curColumn.ColumnName)
{
throw new Exception(String.Format("该列已经存在", dc.ColumnName));
}
}
base.Add(dc);
}
}
列都已经准备好了,接下来是伟大的行了,早在很久之前DataRow曾经一度风靡,我突然想到了一些很矜持的老程序员。。想到了久违的Ado...
第三步:创建行
public class DataRow
{
public Dictionary<string, object> items { set; get; }
public DataRow()
{
this.items = new Dictionary<string, object>();
}
/// <summary>
/// DataRow类索引器 (DataRow[.....])
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public object this[string key]
{
set { items[key] = value; }
get { return items[key]; }
}
/// <summary>
/// 通过emit反射在内存中创建出一个包含属性的类
/// </summary>
/// <returns></returns>
public Assembly EmitAssembly()
{
AssemblyName assemblyName = new AssemblyName("DataRowAssembly");
AssemblyBuilder assemblyBuilder=Thread.GetDomain().DefineDynamicAssembly(assemblyName,AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DataRowModel",true);
TypeBuilder typeBuilder = moduleBuilder.DefineType("DataRowObject",TypeAttributes.Public|TypeAttributes.Class);
foreach(KeyValuePair<string, object> pair in items)
{
BuilderFieldsAndProperty(typeBuilder, pair.Key,pair.Value.GetType());
}
typeBuilder.CreateType();
return assemblyBuilder;
}
/// <summary>
/// 通过emit反射创建字段和属性
/// </summary>
/// <param name="myTypeBuilder">TypeBuilder</param>
/// <param name="name">需要创建的属性名</param>
/// <param name="type">包含该属性的类的类型</param>
public void BuilderFieldsAndProperty(TypeBuilder myTypeBuilder, string name, Type type)
{
FieldBuilder myFieldBuilder = myTypeBuilder.DefineField(name, type, FieldAttributes.Private);
PropertyBuilder myPropertyBuilder = myTypeBuilder.DefineProperty(name.ToUpper(), PropertyAttributes.HasDefault, type, null);
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
MethodBuilder getMethodBuilder = myTypeBuilder.DefineMethod("get_" + name, getSetAttr, type, Type.EmptyTypes);
ILGenerator custNameGetIL = getMethodBuilder.GetILGenerator();
custNameGetIL.Emit(OpCodes.Ldarg_0);
custNameGetIL.Emit(OpCodes.Ldfld, myFieldBuilder);
custNameGetIL.Emit(OpCodes.Ret);
MethodBuilder setMethodBuilder = myTypeBuilder.DefineMethod("set_" + name, getSetAttr, null, new Type[] { type });
ILGenerator custNameSetIL = setMethodBuilder.GetILGenerator();
custNameSetIL.Emit(OpCodes.Ldarg_0);
custNameSetIL.Emit(OpCodes.Ldarg_1);
custNameSetIL.Emit(OpCodes.Stfld, myFieldBuilder);
custNameSetIL.Emit(OpCodes.Ret);
myPropertyBuilder.SetGetMethod(getMethodBuilder);
myPropertyBuilder.SetSetMethod(setMethodBuilder);
}
}
好吧,但是老程序员也必须面对对象才是王道的事实。。回归正题:怎么让程序自动创建实体而不需要些在.cs文件中?
聪明的你一定会想到反射,的确使用Emit反射就能帮助我们在内存中创建我们需要的类类型而无需写在.cs文件中,
EmitAssembly()方法能够帮助我们将Dictionary集合中的数据在内存中
用代码直观的理解:
Assembly rowAssembly = row.EmitAssembly();
object c=rowAssembly.CreateInstance("DataRowObject");
Type type = rowAssembly.GetType("DataRowObject");
执行完这段代码后 "DataRowObject"类会在内存中制造完毕而且能使用,程序执行完毕后变从人间蒸发
关于怎么使用Emit,大家可以到博客园上找相关文章,这里不再细说了s
下面是行集合:
第四步:创建行集合
public class DataRowCollection:List<DataRow>
{
}
行集合和list基本一致所以就靠他父亲吧。
一切准备就绪:DataTable 光荣登场:
第四步:创建DataTable
public class DataTable
{
/// <summary>
/// DataTable 的名字
/// </summary>
public string Name { get; set; }
/// <summary>
/// DataRow的集合
/// </summary>
public DataRowCollection Rows { get; set; }
/// <summary>
/// DataColumn的集合
/// </summary>
public DataColumnCollection Columns { get; set; }
/// <summary>
/// 构造函数并且赋初始值或创建对象
/// </summary>
/// <param name="name"></param>
public DataTable(string name )
{
this.Name = name;
this.Rows = new DataRowCollection();
this.Columns = new DataColumnCollection();
}
}
OK一切准备工作完毕,只剩东风了,可惜微软给我们的DataGrid根本就不支持DataTable的绑定,怎么办?分手?不,我要改造你!
第四步:创建自己的DataGrid:
/// <summary>
/// 自定义MyDataGrid 继承自DataGrid
/// </summary>
public class MyDataGrid:DataGrid
{
public MyDataGrid()
{
//重新定义触发AutoGeneratingColumn时的创建列的方法
this.AutoGeneratingColumn += new EventHandler<DataGridAutoGeneratingColumnEventArgs>
(
(o, e) =>
{
//将dataSource赋给自定义的Datatable
DataTable dt = ((DataTable)this.DataSoruce);
//通过自定义DataColumn设置对dataGrid的Cloumn进行相应的修改
foreach (DataColumn dc in dt.Columns)
{
if (dc.ColumnName.ToUpper() == e.Column.Header.ToString())
{
e.Column.Header = dc.Caption;
e.Column.IsReadOnly = dc.ReadOnly;
e.Column.CanUserResize = dc.AllowResize;
e.Column.CanUserSort = dc.AllowSort;
e.Column.CanUserReorder = dc.AllowReorder;
break;
}
}
}
);
}
public object DataSoruce { set; get; }
/// <summary>
/// 将DataTable转换成list<object>
/// </summary>
/// <param name="table">自定义DataTable</param>
/// <returns></returns>
public List<object> GetDataFromDataTable(DataTable table )
{
List<object> list = new List<object>();
foreach (DataRow row in table.Rows)
{
Assembly rowAssembly = row.EmitAssembly();
object c=rowAssembly.CreateInstance("DataRowObject");
Type type = rowAssembly.GetType("DataRowObject");
foreach (string key in row.items.Keys)
{
PropertyInfo properInfo = type.GetProperty(key.ToUpper());
properInfo.SetValue(c, row.items[key], null);
}
list.Add(c);
}
return list;
}
public void DataBind()
{
this.ItemsSource = this.GetDataFromDataTable((DataTable)DataSoruce);
}
}
首先我们先继承DataGrid 创建自己的DataGrid
第二步就是利用反射将DataTable的内容转化成List<Object>,这还不够,我们必须重定义AutoGeneratingColumn 事件的绑定方法,
自定义DataGrid的列属性,改变DataGrid原来的生命周期
最后一笔就是当年很流行的DataBind方法,超怀念吧。
最后一步:实际使用:
首先在页面上放置自定义DataGrid
<Grid x:Name="LayoutRoot" Background="White">
<bDG:MyDataGrid x:Name="MyDataGrid" Width="640" Height="400" />
</Grid>
其实到这步对于元老级程序员来说很熟悉了:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
List<string> firstNames = new List<string>() { "a","b","c"};
List<string> lastNames = new List<string>() { "Jimmy","Jimmy2","Jimmy3"};
DataTable dt = new DataTable("MyDataTable");
DataColumn dc1 = new DataColumn("col1");
dc1.Caption = "First Name";
dc1.ReadOnly = true;
dc1.DataType = typeof(String);
dc1.AllowResize = true;
dc1.AllowSort = true;
dc1.AllowReorder = true;
dt.Columns.Add(dc1);
DataColumn dc2 = new DataColumn("col2");
dc2.Caption = "Last Name";
dc2.ReadOnly = true;
dc2.DataType = typeof(String);
dc2.AllowResize = true;
dc2.AllowSort = true;
dc2.AllowReorder = true;
dt.Columns.Add(dc2);
Random r = new Random();
for (int i = 0; i < 15; i++)
{
DataRow dr = new DataRow();
dr["col1"] = firstNames[r.Next(firstNames.Count)];
dr["col2"] = lastNames[r.Next(lastNames.Count)];
dt.Rows.Add(dr);
}
this.MyDataGrid.DataSoruce = dt;
this.MyDataGrid.DataBind();
}
}
好了,大功告成,Asp.net GridView和Sliverlight DataGrid 可以拥抱了。
以上方案和WCF 没有任何关系,老程序员又给我出难题了。。他希望从服务端直接通过wcf传DataTable 然后到前台还是通过DataTable绑定,没办法,让我再研究下。。。