【www.gdgbn.com--组件控件开发】

gridview 动态添加 数据列,绑定方法

通过继承 gridview 并且重载  createcolumns() 添加自己想要的 column,根据扩展属性中的键值对生成column。
通过继承 bouldfield,并且重载 initializedatacell() 和 getvalue() 实现自己希望的绑定方法,实现可以基于方法的数据绑定,从而可以将扩展属性中的键值绑定到对应的cell中。

gridview 是asp教程.net中表格数据显示控件中的一种,可以支持数据绑定,绑定的数据源我们一般用实现ienumerable接口的对象,t可以是任何一个clr类(当然还有一些其他的数据源格式),这些大家基本都很熟悉,但是最近碰到一个新的需求:

     像有一个类似于:   

  

  public class book
    {
        public int id { get; set; }

        public string name { get; set; }

        public dictionary extendproperties { get; set; }
    }

 

  其中extendproperties 是用来存储不同用户自定义的扩展属性的,用键值对的形式保存,key保存扩展属性名,value保存扩展属性值,每个用户可能的扩展属性个数和键值都不一样。

     现在,如果我们需要把list绑定在gridview上面,但是希望将extendproperties中的扩展属性也都显示出来,而且相应的key显示在column的header,而value则显示在相应的cell中。

     起初想沿着gridview绑定一般数据的思路考虑,看是否能够通过动态属性的方式将extendproperties中的键值对匹配成相应的对象属性,(没有测试是否能够成功),但是又觉得数据绑定是控件的事情,这个不应该由数据本身来负责,那我们能否通过修改gridview本身的行为来达到这个目的呢?? 答案是肯定的!

     我们都知道,gridview本身是有一个autogeneratecolumns的属性,如果设置为true的话,gridview将自动添加一些列,这些列对应book中的每一个可绑定的属性,这里是用propertydescriptor来实现的(好像是考虑到designmode,而没有用type来做反射)。根据这个思路,我们可以先看看autogeneratecolumns到底对gridview有哪些影响以及gridview本身是如何生成columns的。

     通过查看gridview的源码,我们可以看到几个重要的方法:

代码

protected virtual icollection createcolumns(pageddatasource datasource, bool usedatasource);

protected virtual autogeneratedfield createautogeneratedcolumn(autogeneratedfieldproperties fieldproperties);
 

    其中 createcolumns方法是gridview用来生成列的,而在其中会根据autogeneratecolumns属性来判断是否调用createautogeneratecolumn来自动生成相应属性的列。而且这两个方法都是可以重载的,根据我们的需求,我们可以构建一个gridview的子类,重载 createcolumns 方法,在 调用 base.createcolumns的返回结果中插入我们希望生成的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);

 

 

 其中我添加了几个属性和一个新的方法:

代码
public bool autogenerateextendpropertiescolumn { get; set; }

public int autogeneratecolumnsafter { get; set; }
 
public string extendpropertiesdatafield { get; set; }

protected virtual icollection createextendpropertiescolumns(pageddatasource datasource, bool usedatasource);
 

    autogenerateextendpropertiescolumn是用来判断是否要生成扩展属性对应的column,

    而 autogeneratecolumnsafter是用来判断将自动生成的column应该从什么位置开始插入,

    extendpropertiesdatafield 是用来指明在绑定的对象中,哪个属性是存储扩展属性的信息的,

    方法 createextendpropertiescolumns是用来生成与extendproperties对应的columns.

     这里只是简单的举个例子,如何通过重载 createcolumns 方法来实现自己动态添加column 的目的。

       现在我们看看如何实现 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>).isassignablefrom(extendpropertiesdesc.propertytype))
                        {
                            ienumerable> extendproperties = (ienumerable>)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;
        }
 

      上面的代码很简单,就是根据扩展属性在对象中的属性名,获取相应的值,然后再根据扩展属性构建对应的column,这里有一个新的自定义的 bouldfield: extbouldfield. 我们知道,一般的 bouldfield 都是对对象的property进行绑定,而我们的数据存储在一个类型为 dictionary的 extendproperties中,而且希望该column的headertext是key, cell中显示的是value,这个是系统的bouldfield不能实现的,那我就自己实现了一个可以绑定方法的extbouldfield (这里只是起到一个抛砖引玉的作用,功能很简单,指定一个 methodname, 和一个暂时只能为string类型的methodparam,这里只能为 string(或者其他基本类型),是为了能够让其在aspx页面上直接进行设置,如果这些属性只是纯代码设置,那么methodparam可以为任何类型).

     通过分析 bouldfield 的源码,可以了解到 bouldfield 对每一个 datacell 的数据获取路径大概是这样的:

     initializecell() -> initializedatacell() -> ondatabouldfield() -> getvalue(),

     最后的数据获取途径是 getvalue() 方法,而且 getvalue() 方法也是可以重载的,那么我们可以重载 getvalue() 方法来实现按照自己的需求(通过绑定方法而不是绑定属性的形式)来获取datacell的绑定数据。为了考虑到原来的基于对象属性的绑定形式还可以使用,我还对 initializedatacell() 方法做了一些稍微的修改,该 extbouldfield 的详细代码如下:

 

代码


    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 用来指明通过绑定对象的哪个方法来获取绑定数据;

      methodparam 用来指明方法的参数(如果是代码设置该属性的话,可以让他为任何类型,但是这里只是用string,为了使aspx页面也能直接设置该参数的值)。

 

本文来源:http://www.gdgbn.com/asp/29104/