綁定資料
在本文的需求中,在資料庫取出的資料并沒有固定的格式,這導緻不能使用固定的靜态結構傳輸服務資料。為了傳輸這樣的資料,需要設計一個類似System.Data.DataTable這樣的資料結構,如下:
[DataContract]
public class SimpleTable
{
[DataMember]
public string[] Columns
{ get; set; }
[DataMember]
public Row[] Rows
{ get; set; }
}
[DataContract]
public struct Row
{
[DataMember]
public RowItem[] Items
{ get; set; }
}
[DataContract]
public struct RowItem
{
[DataMember]
public string Name
{ get; set; }
[DataMember]
public string Content
{ get; set; }
}
SimpleTable.columns屬性記錄資料有多少列,然後每行的資料都放入SimpleTable.Rows中。這樣任意形式的行列資料都可以處理了。
服務把資料傳遞給silverlight後,DataGrid可以綁定這些資料。但這裡遇到了一個問題,DataGrid可以綁定靜态類型的資料,而我們的資料是動态的。為了能讓DataGrid使用這些資料,我想到過下面兩個方法:
1.使用List<object>做資料源,資料源裡放匿名對象:
List<object> list = new List<object>();
foreach (Row row in Rows)
{
list.Add(new { ColumnName1 = row.column1Value, ColumnName2 = row.column2Value });
}
實際做時就會發現這樣根本行不通,這種做法要求ColumnName是可以直接寫在代碼中的簡單名稱,但我們的ColumnName是動态的。這時類似javascript的Eval機制或許能派上用場,但在.net的編譯器作為服務(Complier as a Servie)功能推出以前,很好的實作這個機制并不容易。最重要的是,就算能做到也不應該這樣去做,這樣把原本問題複雜化是在鑽牛角尖,會過度設計,誤入歧途。
2.使用List<dynamic>做資料源,資料源裡放ExpandoObject對象:
List<dynamic> list = new List<dynamic>();
foreach (Row row in Rows)
{
dynamic data = new ExpandoObject();
(data as ICollection<KeyValuePair<string, object>>).Add(new KeyValuePair<string, object>(row.columnName, row.columaValue));
list.Add(data);
}
這樣倒是沒有第1種方法那樣問題,但依然失敗了。DataGrid不識别dynamic類型的資料類型。
看來動态資料源是不行了,如果必須使用靜态類型資料,剩下的路還有一條:靜态資料也是可以用代碼動态建立的。.net提供了“反射發出”機制,可以使用代碼在程式中生成程式集、子產品、類、方法等,這成了能解決需求的唯一救命稻草。
下面是具體做法:
public static IEnumerable GetDataGridSource(SimpleTable simpleTable)
{
//動态定義程式集、子產品、類
AssemblyName assemblyName = new AssemblyName("DynamicGridViewModelAssembly");
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicGridViewModelModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicGridViewModelType", TypeAttributes.Public);
//動态定義類的構造方法
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
ILGenerator constructorIL = constructorBuilder.GetILGenerator();
constructorIL.Emit(OpCodes.Ldarg_0);
constructorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
constructorIL.Emit(OpCodes.Ret);
//動态定義類的屬性,每個屬性對應資料集中的一列
foreach (string column in simpleTable.Columns)
{
FieldBuilder fieldBuidler = typeBuilder.DefineField("m_" + column, typeof(string), FieldAttributes.Private);
PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(column, PropertyAttributes.HasDefault, typeof(string), null);
MethodAttributes methodAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
MethodBuilder methodGetBuilder = typeBuilder.DefineMethod("get_" + column, methodAttributes, typeof(string), Type.EmptyTypes);
ILGenerator methodGetIL = methodGetBuilder.GetILGenerator();
methodGetIL.Emit(OpCodes.Ldarg_0);
methodGetIL.Emit(OpCodes.Ldfld, fieldBuidler);
methodGetIL.Emit(OpCodes.Ret);
MethodBuilder methodSetBuilder = typeBuilder.DefineMethod("set_" + column, methodAttributes, null, new Type[] { typeof(string) });
ILGenerator methodSetIL = methodSetBuilder.GetILGenerator();
methodSetIL.Emit(OpCodes.Ldarg_0);
methodSetIL.Emit(OpCodes.Ldarg_1);
methodSetIL.Emit(OpCodes.Stfld, fieldBuidler);
methodSetIL.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(methodGetBuilder);
propertyBuilder.SetSetMethod(methodSetBuilder);
}
//動态建立類
Type modelType = typeBuilder.CreateType();
Type listType = typeof(List<>).MakeGenericType(modelType);
object list = Activator.CreateInstance(listType);
MethodInfo methodAdd = listType.GetMethod("Add");
//動态建立類的執行個體,每個執行個體對應資料集中的一行
foreach (Row row in simpleTable.Rows)
{
object model = Activator.CreateInstance(modelType);
foreach (RowItem item in row.Items)
{
PropertyInfo propertyInfo = modelType.GetProperty(item.Name);
propertyInfo.SetValue(model, item.Content, null);
}
methodAdd.Invoke(list, new object[] { model });
}
return (IEnumerable)list;
}
通過這個方法建立DataGrid的資料源,雖然類型是動态發出的,但它确實是靜态的:List<DynamicGridViewModelType>,這樣就可以綁定了:
client.GetTableCompleted += (s, a) =>
{
dataGrid1.ItemsSource = GetDataGridSource(a.Result);
};
client.GetTableAsync("ExampleTable");
其實這套解決方法也不直覺簡單,但我沒能找到更靠的方案。
到此,silverlight的DataGrid綁定動态類型的工作也完成了,整個需求全部完工。如果大家有什麼建議或指導,請指教,謝謝。
本文示例下載下傳位址:http://download.csdn.net/detail/wantalcs/4494183