不知道微軟為什么在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綁定,沒辦法,讓我再研究下。。。