在读Clinglingboy的asp.net控件开发基础(18)时,Clinglingboy对其进行了重点讲解。可是我感觉在如何将具有IListSource接口的数据源最终转化为DataView说的还不是十分清楚,下面我这一部分再详细的说一下。
首先还是贴一下关键的DataSourceHelper类
DataSourceHelper
public class DataSourceHelper
{
public static object ResolveDataSource(object dataSource, string dataMember)
{
如果数据源为空,则返回空值#region 如果数据源为空,则返回空值
if (dataSource == null)
return null;
#endregion
如果数据源不为空,且为IEnumerable类型,则返回IEnumerable#region 如果数据源不为空,且为IEnumerable类型,则返回IEnumerable
if (dataSource is IEnumerable)
{
return (IEnumerable)dataSource;
}
#endregion
如果数据源不为空,且为IListSource类型,则返回IListSource#region 如果数据源不为空,且为IListSource类型,则返回IListSource
else if (dataSource is IListSource)
{
IList list = null;
IListSource listSource = (IListSource)dataSource;
list = listSource.GetList();
判断是否为IList对象集合的值#region 判断是否为IList对象集合的值
if (listSource.ContainsListCollection)
{
//提供发现可绑定列表架构的功能,其中可用于绑定的属性不同于要绑定到的对象的公共属性
ITypedList typedList = (ITypedList)list;
//返回表示用于绑定数据的每项上属性集合
//PropertyDescriptorCollection propDescCol =
// typedList.GetItemProperties(new PropertyDescriptor[0]); //was (null)
PropertyDescriptorCollection propDesCol=new PropertyDescriptorCollection();
//如果属性说明符数目为0
if (propDescCol.Count == 0)
throw new Exception("ListSource without DataMembers");
PropertyDescriptor propDesc = null;
判断dataMember字符数给propDesc赋值#region 判断dataMember字符数给propDesc赋值
//获取属性描述符
//若不指定dataMember属性则获取默认数据成员
if ((dataMember == null) || (dataMember.Length < 1))
{
propDesc = propDescCol[0];
}
else
//尝试在属性集合中寻找数据成员
propDesc = propDescCol.Find(dataMember, true);
#endregion
if (propDesc == null)
throw new Exception("ListSource missing DataMember");
object listitem = list[0];
//获取组件属性当前值
object member = propDesc.GetValue(listitem);
if ((member == null) || !(member is IEnumerable))
throw new Exception("ListSource missing DataMember");
return (IEnumerable)member;
}
else
//若不包含Ilist集合,则直接返回
return (IEnumerable)list; //robcamer added (IEnumerable)
#endregion
}
#endregion
return null;
}
}
(1)如果传入的数据源类型是IEnumerable的话,可以直接返回
if
(dataSource
is
IEnumerable)
{
return (IEnumerable)dataSource;
}
这里像Array、ArrayList、SqlDataReader、DataView等都直接或者间接的实现了IEnumerable接口。
(2)如果传入的类型非IEnumerable,那么代码会判断数据源是否实现了IListSource接口,因为如果实现了IListSource接口,那么我们同样可以利用此接口的GetList方法返回一个IList,而IList继承IEnumerable,同样可以进行数据绑定。当然如果数据源没有实现IEnumerable和IListSource,数据源就不可绑定。
这里像DataTable、DataSet都实现了IListSource接口。
DataTable实现的GetList方法
IList IListSource.GetList()
{
return this.DefaultView;
}
返回了一个DataView
DataSet实现的GetList方法
IList IListSource.GetList()
{
return this.DefaultViewManager;
}
返回了一个DataViewManager。
通过判断IListSource中的ContainsListCollection,我们可以知道包含多个DataTable的DataSet还是只有一个DataTable,对于后者,由于已经通过GetList方法得到了它的DataView,而DataView又实现了IEnumerable接口,问题也解决了。
问题现在集中到如何处理DataSet的数据源,我们来看一下DataViewManager类,除了几个public的属性,还有一个DataViewManagerListItemTypeDescriptor类型的Item值得我们注意,后面会讲解此类。同时DataViewManager类实现了ITypedList接口,接下来利用ITypedList.GetItemProperties(object)得到PropertyDescriptorCollection.
我们看一下ITypedList.GetItemProperties(object)的代码,其中关键一句
return ((ICustomTypeDescriptor) new DataViewManagerListItemTypeDescriptor(this)).GetProperties();
看来DataViewManagerListItemTypeDescriptor的GetProperties方法可以得到PropertyDescriptorCollection。此类是Framework的一个内部类,实现了ICustomTypeDescriptor接口。
那么ICustomTypeDescriptor是做什么用的呢。我们来看一下msdn:
ICustomTypeDescriptor 使对象得以提供有关自身的类型信息。通常,当对象需要动态类型信息时使用此接口。相反,TypeDescriptor 提供从元数据获得的静态类型信息。
大家可能对这句话不太明白,我解释一下,这里我用PropertyGrid举例,不熟悉的可以在网上查,实际上我感觉PropertyGrid在和某个类绑定的时候,默认的是用TypeDescriptor 提供从元数据获得的静态类型信息。如下图
但是有些情况,你需要用到 PropertyGrid 去绑定一个属性/值的集合,但是这个属性/值的集合并不适合写成一个固定的类。
比如你想用 PropertyGrid 绑定XML 里的数据。或者数据库的某个表。
假设你有 1000 个XML 文件,每个 XML 所取到的属性集合各不一样,你不可能为每个XML 文件都写一个类 。
或者你的某个数据表有1000 条记录,该表有 a 字段的值表示属性名称, b字段的值表示属性值,你不可能写一个类,定义1000个属性。
这时候,我们就希望是否能够将一个动态的属性/值的集合与Property 绑定。通过实现ICustomTypeDescriptor,我们就可以完成动态的属性/值的集合与Property 绑定。这里参考了PropertyGrid 绑定动态的属性与值的集合文章,这篇文章对大家理解ICustomTypeDescriptor会有很大的帮助,文章的代码是VB2005,我用c#2003重新写了一下,这两段代码我会在文章后面给出下载,建议大家先读这篇文章以帮助理解。我把这篇文章的几个类的关键部分列出来。
XProp
public class XProp
{
private string theName;
private object theValue;
public string Name
{
get
{
return this.theName;
}
set
{
this.theName = value;
}
}
public object Value
{
get
{
return this.theValue;
}
set
{
this.theValue = value;
}
}
public override string ToString()
{
return "Name: " +Name +",Value: "+Value;
}
public XProp()
{
this.theName = "";
this.theValue = null;
}
}
XPropDescriptor
public class XPropDescriptor:PropertyDescriptor
{
private XProp theProp;
public override Type ComponentType
{
get
{
return this.GetType();
}
}
public override bool IsReadOnly
{
get
{
return false;
}
}
public override Type PropertyType
{
get
{
return this.theProp.Value.GetType();
}
}
public XPropDescriptor(XProp prop, Attribute[] attrs) : base(prop.Name, attrs)
{
this.theProp = prop;
}
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
return this.theProp.Value;
}
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
this.theProp.Value = value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
XProps
public class XProps:CollectionBase,ICustomTypeDescriptor
{
public XProps()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
IList实现#region IList实现
public int Add(XProp prop)
{
return base.List.Add(prop);
}
public XProp FindXProp(string name)
{
name = name.Trim().ToLower();
foreach (XProp prop in base.List)
{
if (prop.Name.ToLower() == name)
{
return prop;
}
}
return null;
}
public void Insert(int index, XProp prop)
{
base.List.Insert(index, prop);
}
public void Remove(XProp prop)
{
base.List.Remove(prop);
}
public XProp this[int index]
{
get
{
return (XProp) base.List[index];
}
set
{
base.List[index] = value;
}
}
#endregion
ICustomTypeDescriptor实现#region ICustomTypeDescriptor实现
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public string GetComponentName()
{
return TypeDescriptor.GetClassName(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public PropertyDescriptorCollection GetProperties()
{
return TypeDescriptor.GetProperties(this, true);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptor[] props = new PropertyDescriptor[this.Count + 1];
int count = this.Count - 1;
for (int i = 0; i <= count; i++)
{
props[i] = new XPropDescriptor(this[i], attributes);
}
return new PropertyDescriptorCollection(props);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
public override string ToString()
{
StringBuilder sbld = new StringBuilder();
int count = this.Count - 1;
for (int i = 0; i <= count; i++)
{
sbld.Append("[" + i + "] " + this[i].ToString() + "\r\n");
}
return sbld.ToString();
}
}
回到问题上来,在我们实现了ICustomTypeDescriptor,不需要和PropertyGrid绑定,我们可以得到一个PropertyDescriptorCollection。那么就来具体看看对比。
其中上文的XProp --> DataTable
XProps 的GetProperties方法--> ((ITypedList) DataViewManager).GetItemProperties方法
XPropDescriptor--> DataTablePropertyDescriptor
大家会看到((ITypedList) DataViewManager).GetItemProperties方法返回了DataTablePropertyDescriptor的PropertyDescriptorCollection集合;XProps的GetProperties方法返回了XPropDescriptor的PropertyDescriptorCollection集合
在DataTablePropertyDescriptor会有一个DataTable的属性,并且该类复写了GetValue方法,取得值,这个和XPropDescriptor中有XProp属性,且复写了GetValue方法是一致的。唯一不同的是XPropDescriptor的GetValue方法只是将具体的XProp的Value返回,而DataTablePropertyDescriptor中的GetValue方法又利用DataTable进一步操作返回了DataView。
我们现在知道ITypedList.GetItemProperties(object)是怎么得到PropertyDescriptorCollection(确切的说是DataTablePropertyDescriptor),我们接着利用propDesc = propDescCol.Find(dataMember, true)去在集合中查找名字为dataMember值也就是具体的表名,以返回待操作的DataTablePropertyDescriptor。在((ICustomTypeDescriptor) new DataViewManagerListItemTypeDescriptor(this)).GetProperties()方法建立集合的时候采用了表名作为名值对的名,大家可以对照代码看看。接下来再看这段代码
object
listitem
=
list[
0
];
//
获取组件属性当前值
object
member
=
propDesc.GetValue(listitem);
list是什么?实际上是我们在前面得到的DataViewManager.IListSource listSource = (IListSource)dataSource;
list = listSource.GetList();
由于DataViewManager实现了IList接口,因此我们可以用list[index]的形式取得具体的元素,这里我们看到是取得了item的值,还记得我们前面让大家留意DataViewManager的Item属性,实际上它就是一个DataViewManagerListItemTypeDescriptor。propDesc是一个DataTablePropertyDescriptor,来看一下他的GetValue(object)代码
public
override
object
GetValue(
object
component)
{
DataViewManagerListItemTypeDescriptor descriptor = (DataViewManagerListItemTypeDescriptor) component;
return descriptor.GetDataView(this.table);
}
而DataViewManagerListItemTypeDescriptor的GetDataView的代码
internal
DataView GetDataView(DataTable table)
{
DataView view = new DataView(table);
view.SetDataViewManager(this.dataViewManager);
return view;
}
实际上这一步就是利用DataTable构建DataView,我觉得也可以用其他的方法完成,给DataViewManagerListItemTypeDescriptor增加一个内部的GetDataView方法反而弱化了TypeDescriptor的功能。
到这里,我们就可以返回一个(IEnumberable)DataView了。
PropertyGrid 绑定动态的属性与值的集合文章代码下载:
ICustomTypeDescriptorTestVB.zip(VB2005)
ICustomTypeDescriptorTestCSharp.zip(C#2003,其中VB2005使用了范型,改写的时候用了CollectionBase,效果一样)