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?
<script type="text/C#" runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
object o = ViewState["DataSourceID"];
if (o != null)
{
GridView_DepartList.DataSourceID = o as string;
GridView_DepartList.DataSource = null;
}
}
}
</script>
<script type="text/C#" runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
object o = ViewState["DataSourceID"];
if (o != null)
{
GridView_DepartList.DataSourceID = o as string;
GridView_DepartList.DataSource = null;
}
}
}
</script>
现在事情可以告一段落了。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