像有一個(gè)類似于:
public class Book
{
public int ID { get; set; }
public string Name { get; set; }
public Dictionary<string, object> ExtendProperties { get; set; }
}
其中ExtendProperties 是用來存儲(chǔ)不同用戶自定義的擴(kuò)展屬性的,用鍵值對(duì)的形式保存,Key保存擴(kuò)展屬性名,Value保存擴(kuò)展屬性值,每個(gè)用戶可能的擴(kuò)展屬性個(gè)數(shù)和鍵值都不一樣。
現(xiàn)在,如果我們需要把List<Book>綁定在GridView上面,但是希望將ExtendProperties中的擴(kuò)展屬性也都顯示出來,而且相應(yīng)的Key顯示在Column的Header,而Value則顯示在相應(yīng)的Cell中。
起初想沿著GridView綁定一般數(shù)據(jù)的思路考慮,看是否能夠通過動(dòng)態(tài)屬性的方式將ExtendProperties中的鍵值對(duì)匹配成相應(yīng)的對(duì)象屬性,(沒有測(cè)試是否能夠成功),但是又覺得數(shù)據(jù)綁定是控件的事情,這個(gè)不應(yīng)該由數(shù)據(jù)本身來負(fù)責(zé),那我們能否通過修改GridView本身的行為來達(dá)到這個(gè)目的呢?? 答案是肯定的!
我們都知道,GridView本身是有一個(gè)AutoGenerateColumns的屬性,如果設(shè)置為True的話,GridView將自動(dòng)添加一些列,這些列對(duì)應(yīng)Book中的每一個(gè)可綁定的屬性,這里是用PropertyDescriptor來實(shí)現(xiàn)的(好像是考慮到DesignMode,而沒有用Type來做反射)。根據(jù)這個(gè)思路,我們可以先看看AutoGenerateColumns到底對(duì)GridView有哪些影響以及GridView本身是如何生成Columns的。
通過查看GridView的源碼,我們可以看到幾個(gè)重要的方法:
代碼
protected virtual ICollection CreateColumns(PagedDataSource dataSource, bool useDataSource);
protected virtual AutoGeneratedField CreateAutoGeneratedColumn(AutoGeneratedFieldProperties fieldProperties);
其中 CreateColumns方法是GridView用來生成列的,而在其中會(huì)根據(jù)AutoGenerateColumns屬性來判斷是否調(diào)用CreateAutoGenerateColumn來自動(dòng)生成相應(yīng)屬性的列。而且這兩個(gè)方法都是可以重載的,根據(jù)我們的需求,我們可以構(gòu)建一個(gè)GridView的子類,重載 CreateColumns 方法,在 調(diào)用 base.CreateColumns的返回結(jié)果中插入我們希望生成的Column即可。
代碼如下:
代碼
#region Properties
public bool AutoGenerateExtendPropertiesColumn { get; set; }
public int AutoGenerateColumnsAfter { get; set; }
public string ExtendPropertiesDataField { get; set; }
#endregion
protected override ICollection CreateColumns(PagedDataSource dataSource, bool useDataSource)
{
ICollection collection = base.CreateColumns(dataSource, useDataSource);
if (AutoGenerateExtendPropertiesColumn )
{
ArrayList list = new ArrayList();
ICollection extendPropertiesCollection = CreateExtendPropertiesColumns(dataSource, useDataSource);
ArrayList list1 = new ArrayList();
foreach (var c in collection)
{
list1.Add(c);
}
ArrayList list2 = new ArrayList();
if (extendPropertiesCollection != null)
{
foreach (var c in extendPropertiesCollection)
{
list2.Add(c);
}
}
int copyFrom = AutoGenerateColumnsAfter < list1.Count ? AutoGenerateColumnsAfter : list1.Count - 1;
copyFrom = copyFrom >= 0 ? copyFrom : -1;
for (int i = 0; i <= copyFrom; i++)
{
list.Add(list1[i]);
}
list.AddRange(list2.ToArray());
for (int i = copyFrom + 1; i < list1.Count; i++)
{
list.Add(list1[i]);
}
return list;
}
return collection;
}
protected virtual ICollection CreateExtendPropertiesColumns(PagedDataSource dataSource, bool useDataSource);
其中我添加了幾個(gè)屬性和一個(gè)新的方法:
代碼
public bool AutoGenerateExtendPropertiesColumn { get; set; }
public int AutoGenerateColumnsAfter { get; set; }
public string ExtendPropertiesDataField { get; set; }
protected virtual ICollection CreateExtendPropertiesColumns(PagedDataSource dataSource, bool useDataSource);
AutoGenerateExtendPropertiesColumn是用來判斷是否要生成擴(kuò)展屬性對(duì)應(yīng)的Column,
而 AutoGenerateColumnsAfter是用來判斷將自動(dòng)生成的Column應(yīng)該從什么位置開始插入,
ExtendPropertiesDataField 是用來指明在綁定的對(duì)象中,哪個(gè)屬性是存儲(chǔ)擴(kuò)展屬性的信息的,
方法 CreateExtendPropertiesColumns是用來生成與ExtendProperties對(duì)應(yīng)的Columns.
這里只是簡單的舉個(gè)例子,如何通過重載 CreateColumns 方法來實(shí)現(xiàn)自己動(dòng)態(tài)添加Column 的目的。
現(xiàn)在我們看看如何實(shí)現(xiàn) CreateExtendPropertiesColumns 方法的:
代碼
protected virtual ICollection CreateExtendPropertiesColumns(PagedDataSource dataSource, bool useDataSource)
{
ArrayList array = new ArrayList();
if (dataSource.DataSourceCount > 0 && !String.IsNullOrEmpty(ExtendPropertiesDataField))
{
var enumerator = dataSource.DataSource.GetEnumerator();
enumerator.Reset();
if (enumerator.MoveNext())
{
object firstData = enumerator.Current;
if (firstData != null)
{
PropertyDescriptor extendPropertiesDesc = TypeDescriptor.GetProperties(firstData).Find(ExtendPropertiesDataField, false);
if (extendPropertiesDesc != null && typeof(IEnumerable<KeyValuePair<string, object>>).IsAssignableFrom(extendPropertiesDesc.PropertyType))
{
IEnumerable<KeyValuePair<string, object>> extendProperties = (IEnumerable<KeyValuePair<string, object>>)extendPropertiesDesc.GetValue(firstData);
if (extendProperties != null)
{
foreach (var exProperty in extendProperties)
{
ExtBouldField bouldField = new ExtBouldField();
bouldField.UseMethodBinding = true;
bouldField.MethodName = "GetExtendProperty";
bouldField.MethodParam = exProperty.Key;
bouldField.HeaderText = exProperty.Key;
array.Add(bouldField);
}
}
}
}
}
}
return array;
}
上面的代碼很簡單,就是根據(jù)擴(kuò)展屬性在對(duì)象中的屬性名,獲取相應(yīng)的值,然后再根據(jù)擴(kuò)展屬性構(gòu)建對(duì)應(yīng)的Column,這里有一個(gè)新的自定義的 BouldField: ExtBouldField. 我們知道,一般的 BouldField 都是對(duì)對(duì)象的property進(jìn)行綁定,而我們的數(shù)據(jù)存儲(chǔ)在一個(gè)類型為 Dictionary<string,Object>的 ExtendProperties中,而且希望該Column的HeaderText是Key, Cell中顯示的是Value,這個(gè)是系統(tǒng)的BouldField不能實(shí)現(xiàn)的,那我就自己實(shí)現(xiàn)了一個(gè)可以綁定方法的ExtBouldField (這里只是起到一個(gè)拋磚引玉的作用,功能很簡單,指定一個(gè) MethodName, 和一個(gè)暫時(shí)只能為String類型的MethodParam,這里只能為 String(或者其他基本類型),是為了能夠讓其在ASPX頁面上直接進(jìn)行設(shè)置,如果這些屬性只是純代碼設(shè)置,那么MethodParam可以為任何類型).
通過分析 BouldField 的源碼,可以了解到 BouldField 對(duì)每一個(gè) DataCell 的數(shù)據(jù)獲取路徑大概是這樣的:
InitializeCell() -> InitializeDataCell() -> OnDataBouldField() -> GetValue(),
最后的數(shù)據(jù)獲取途徑是 GetValue() 方法,而且 GetValue() 方法也是可以重載的,那么我們可以重載 GetValue() 方法來實(shí)現(xiàn)按照自己的需求(通過綁定方法而不是綁定屬性的形式)來獲取DataCell的綁定數(shù)據(jù)。為了考慮到原來的基于對(duì)象屬性的綁定形式還可以使用,我還對(duì) InitializeDataCell() 方法做了一些稍微的修改,該 ExtBouldField 的詳細(xì)代碼如下:
代碼
public class ExtBouldField :BoundField
{
#region Ctor
public ExtBouldField()
: base()
{
UseMethodBinding = false;
}
#endregion
#region Properties
public bool UseMethodBinding
{
get;
set;
}
public string MethodName
{
get;
set;
}
public string MethodParam
{
get;
set;
}
#endregion
#region Overrided methods
protected override void InitializeDataCell(DataControlFieldCell cell, DataControlRowState rowState)
{
Control child = null;
Control control2 = null;
bool needBinding = (!UseMethodBinding && !String.IsNullOrEmpty(DataField))
||(UseMethodBinding && !String.IsNullOrEmpty(MethodName));
if ((((rowState & DataControlRowState.Edit) != DataControlRowState.Normal) && !this.ReadOnly) || ((rowState & DataControlRowState.Insert) != DataControlRowState.Normal))
{
TextBox box = new TextBox();
box.ToolTip = this.HeaderText;
child = box;
if ((needBinding) && ((rowState & DataControlRowState.Edit) != DataControlRowState.Normal))
{
control2 = box;
}
}
else if (needBinding)
{
control2 = cell;
}
if (child != null)
{
cell.Controls.Add(child);
}
if ((control2 != null) && base.Visible)
{
control2.DataBinding += new EventHandler(this.OnDataBindField);
}
}
protected override object GetValue(Control controlContainer)
{
if (UseMethodBinding)
{
if (String.IsNullOrEmpty(MethodName))
{
throw new HttpException("DataItem No MethodName");
}
object component = null;
if (controlContainer == null)
{
throw new HttpException("DataControlField_NoContainer");
}
component = DataBinder.GetDataItem(controlContainer);
if (component != null)
{
MethodInfo bindingMethodInfo = component.GetType().GetMethod(MethodName);
if (bindingMethodInfo == null)
{
throw new HttpException(String.Format("Not Found the Method:{0}", MethodName));
}
return bindingMethodInfo.Invoke(component, new object[] { MethodParam });
}
return component;
}
return base.GetValue(controlContainer);
}
#endregion
}
其中:
UsingMethodBinding 用來指明是用方法綁定還是原來的基于屬性的綁定;
MethodName 用來指明通過綁定對(duì)象的哪個(gè)方法來獲取綁定數(shù)據(jù);
MethodParam 用來指明方法的參數(shù)(如果是代碼設(shè)置該屬性的話,可以讓他為任何類型,但是這里只是用string,為了使ASPX頁面也能直接設(shè)置該參數(shù)的值)。
到這里,原來碰到的問題都解決了:
通過繼承 GridView 并且重載 CreateColumns() 添加自己想要的 Column,根據(jù)擴(kuò)展屬性中的鍵值對(duì)生成Column。
通過繼承 BouldField,并且重載 InitializeDataCell() 和 GetValue() 實(shí)現(xiàn)自己希望的綁定方法,實(shí)現(xiàn)可以基于方法的數(shù)據(jù)綁定,從而可以將擴(kuò)展屬性中的鍵值綁定到對(duì)應(yīng)的Cell中。