让 GridView 在没有数据时显示 HeaderRow 和 FooterRow

来源:互联网 发布:高中数学软件手机 编辑:程序博客网 时间:2024/05/18 02:42

GridView 和 FormView 的结合方式,为我们呈现数据列和编辑一个数据行带来了极大的方便。可非常不幸的是,当 DataSourceControl 没有从数据库中取到任何数据时,GridView 的整个 table 在最终呈现的 HTML 中是不存在的,无论怎样 ShowFooter 和 ShowHeader 都没有办法让它出现。

曾经想过使用 EmptyDataTemplate,然后画一张与 GridView 一样的表来让它出现。但这种做法相当不优雅,尤其是当你使用了排序功能和 HeaderTemplate or FooterTemplate 的情况下。而且你也不得不为它们应用同样的 CSS。一旦你改变了 GridView 的 Header 或 Footer,你也不得不在 EmptyDataTemplate 中应用同样的更改。

继承 GridView 生成新的控件的方法,确实能够解决问题,但实现起来非常麻烦。

当然,使用 DataSet 而不是 DataSourceControl 的朋友要方便得多,他们只需要在 DataSet 中 Rows.Count 为 0 时主动产生一个新行就可以了,可是这并不能成为我们要放弃 DataSourceControl 使用 DataSet 的理由。那么,如何让 GridView 在无数据时显示表头呢?

我的思路是,使用 DataView 来向 GridView 插入一个空行,并使 Visible = false。不过,在不继承 GridView 或 SqlDataSource 的情况下要拿到 DataView 可不是一件容易的事,所幸我在 MSDN 中有关 SqlDataSource 的 public 函数 Select 中找到了这样的说明:

“在页生命周期的 PreRender 阶段中会自动调用 Select 方法。该方法由已通过其 DataSourceID 属性附加到 SqlDataSource 控件的数据绑定控件调用。

如果 DataSourceMode 属性设置为 DataSet 值,则 Select 方法会返回 DataView 对象。如果 DataSourceMode 属性设置为 DataReader 值,则 Select 方法会返回 IDataReader 对象。数据读取完毕后,关闭 IDataReader 对象。”

呃,好极了!那么剩下的事情就简单了,写一个通用函数,专门用来处理此类事宜:

view plaincopy to clipboardprint?
public static void GridViewShowAt0Row(GridView gv)  
{  
    Debug.Assert(gv != null && gv.DataSourceObject != null && gv.DataSourceObject is SqlDataSource);  
    if (gv.PageCount > 0)  
        return;  
    DataView dv = ((SqlDataSource)gv.DataSourceObject).Select(new DataSourceSelectArguments()) as DataView;  
    if (dv != null && dv.Table != null)  
    {  
        if (dv.Table.Rows.Count == 0)  
        {  
            DataRow dr = dv.Table.NewRow();  
            // 如果列允许为 DBNull,则赋 DBNull.Value,否则利用反射使用类型的默认构造函数构造对象  
            foreach (DataColumn dc in dv.Table.Columns)  
                dr[dc] = dc.AllowDBNull ? DBNull.Value : dc.DataType.GetConstructor(Type.EmptyTypes).Invoke(null);  
            dv.Table.Rows.Add(dr);  
            gv.DataSourceID = null;  
        }  
        // 不管有没有数据,这样结果都是正确的  
        gv.DataSource = dv.Table;  
        gv.DataBind();  
        gv.Rows[0].Visible = false;  
    }  

    public static void GridViewShowAt0Row(GridView gv)
    {
        Debug.Assert(gv != null && gv.DataSourceObject != null && gv.DataSourceObject is SqlDataSource);

        if (gv.PageCount > 0)
            return;

        DataView dv = ((SqlDataSource)gv.DataSourceObject).Select(new DataSourceSelectArguments()) as DataView;
        if (dv != null && dv.Table != null)
        {
            if (dv.Table.Rows.Count == 0)
            {
                DataRow dr = dv.Table.NewRow();

                // 如果列允许为 DBNull,则赋 DBNull.Value,否则利用反射使用类型的默认构造函数构造对象
                foreach (DataColumn dc in dv.Table.Columns)
                    dr[dc] = dc.AllowDBNull ? DBNull.Value : dc.DataType.GetConstructor(Type.EmptyTypes).Invoke(null);

                dv.Table.Rows.Add(dr);
                gv.DataSourceID = null;
            }

            // 不管有没有数据,这样结果都是正确的
            gv.DataSource = dv.Table;
            gv.DataBind();
            gv.Rows[0].Visible = false;
        }
    }

然后在 GridView 进行数据绑定之前重新将 DataSource 置空(因为 ViewState 的关系,必须这么做!除非 GridView 的 EnableViewState 为 false),并为其设置 DataSourceID 让其从数据源控件中读数据。可用的地方除了 GridView 的 Load 事件就是 Page_Load 最佳了:

view plaincopy to clipboardprint?
 
   

现在事情可以告一段落了。BUT……在哪里调用我们的 GridViewShowAt0Row 最合适呢?

数据绑定之前我们不知道 GridView 的行数,这显然不可行。我首先想到的是 GridView 的 DataBound 事件,但 IE 给了我一张黄脸告诉我:数据绑定其间不可再调用 DataBind 函数。

其次我又想到了 SqlDataSource 的 Selected 函数,结论是一样的。那么,最终的地方只有一个了:GridView 的 PreRender 事件:

view plaincopy to clipboardprint?
protected void GridView_DepartList_PreRender(object sender, EventArgs e)  
{  
    GridView gv = sender as GridView;  
    // 保存在 ViewState 里的原因是这样会更通用一些,这样我们可以随意修改 DataSourceControl 的 ID  
    if (!string.IsNullOrEmpty(gv.DataSourceID))  
        ViewState["DataSourceID"] = gv.DataSourceID;  
    GridViewShowAt0Row(gv);  

        protected void GridView_DepartList_PreRender(object sender, EventArgs e)
        {
            GridView gv = sender as GridView;

            // 保存在 ViewState 里的原因是这样会更通用一些,这样我们可以随意修改 DataSourceControl 的 ID
            if (!string.IsNullOrEmpty(gv.DataSourceID))
                ViewState["DataSourceID"] = gv.DataSourceID;

            GridViewShowAt0Row(gv);
        }

总结:使用这个方法,最大的遗憾就是 GridViewShowAt0Row 会重复产生一次对数据库的 Select 操作。但这是没有办法的事,除非改用 DataSet 或继承 GridView。绑定的 SqlDataSource Mode 属性需要为 DataSet,DataReader 肯定不行。

还有,我没有为支持所有的 IDataSource 接口的组件提供支持——GridViewShowAt0Row 目前只能支持 SqlDataSource,如果要支持所有的 IDataSource,只需要使用 IDataSource.GetView 获得一个 DataSourceView,再通过 Select 并提供一个接收 IEnumerable 接口的委托的函数,然后将 IEnumerable 转化为相应的类型再添加数据即可。

最后,GridView 的 DataSourceObject 属性需要 .Net Framework 3.5 SP1 版本,不带 SP1 的版本是没有这个属性的,那就需要用 GridView.Page.FindControl 来找 SqlDataSource 控件了。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Lighterz/archive/2009/08/21/4471082.aspx