GridView 增强和修复






4.92/5 (19投票s)
增加功能、增强和修复:对列宽有更多控制,防止文本换行,格式化自动生成的列。
引言
你是否曾经需要在 GridView
中控制列宽,特别是当 GridView
的宽度相对于容器(例如:width="100%")时,列总是会将 GridView
的总宽度平分给它们?你只希望每列都占用足够的宽度来显示其内容,而剩余的宽度可以分配给最后一列,但这并没有奏效,是吗?你是否曾经尝试在 GridView
的 RowStyle
或 HeaderStyle
中使用 Wrap="False"
,却惊讶地发现它并非在所有浏览器中都有效?本文旨在解决这两个问题,并且它在 IE 和 FF 中都有效。
此外,我还提出了一种在使用 GridView
中自动生成列时解决问题的方法。众所周知,GridView
有一个属性 AutoGenerateColumns
,当设置为 true 时,它会自动从 DataSource 生成列。问题在于我们对这些生成列的控制有限(甚至没有)。例如,如果 DataSource
包含一个日期列,显示的数据将不会被格式化,而是显示 (1/1/2008 12:00:00 AM) 而不是只显示 (1/1/2008) 或 (01/01/2008) 等等...这是一个问题,因为我们无法(直接)更改格式以满足我们的要求。解决这个问题的最佳方法是继承 GridView
并根据我们的喜好自定义 GridView
。对于那些(像我一样)不想在他们的项目中处理另一个额外引用的人,我还有另一种方法。
背景
我的解决方案依赖于 JavaScript 和 DOM。我正在将元素注入到现有的 HTML 中。代码并不复杂,你不需要被这些令人望而生畏的术语吓到。不过,你需要了解一些 JavaScript 以及 DOM(文档对象模型)的实际含义。如果你发现自己缺乏这些知识,花几分钟时间用 Google 搜索一下就能帮到你。
对于自动生成列的问题,我的解决方案依赖于反射来设置那些没有公共可访问性的成员的值。反射会带来性能损失,但在某些情况下是可以承受的。如果你发现我的解决方案性能较弱(我没有注意到,也没有测试过),那么请尝试替代方案:继承。
图示
以下是一些图片,展示了我的解决方案所实现的功能
列宽和换行
ColumnWidthsAndWrapping.aspx
解决方案分为两部分:JavaScript 位于 ASPX 页面中,注册这些函数调用的代码位于该页面的代码隐藏文件中。
让我们从 JavaScript 函数开始
function formatGrid(el)
{
el = document.getElementById(el);
var numOfCols =
el.getElementsByTagName("TR")[1].getElementsByTagName("TD").length;
var colGrp = document.createElement("colgroup");
var col = document.createElement("col");
col.setAttribute("span", numOfCols-1);
col.setAttribute("width", "1");
col.setAttribute("white-space", "nowrap");
col.setAttribute("padding-left", "2");
col.setAttribute("padding-right", "2");
col.setAttribute("border-width", "2");
colGrp.appendChild(col);
el.insertBefore(colGrp, el.firstChild);
}
函数 formatGrid
接收文档中元素的 ID,在我们的例子中通常是 GridView
。此函数的作用是格式化 GridView
,使每列获得足够的宽度来显示其内容,并且**最后一**列获得所有剩余的宽度。请记住,我们通常在这里处理宽度为 100% 的 GridView
。代码相对清晰,我将只指出以下几点
- 我们正在获取
GridView
中的列数,numOfCols
,并在后面使用numOfCols - 1
。 ColGroup
元素负责对列进行分组并为其分配通用属性。Col
元素是ColGroup
元素的子元素,它将覆盖在ColGroup
元素上设置的内容,这就是为什么在Col
元素上设置属性就足够了(我使用的是简单术语,欲了解更多信息:请查看 http://www.w3.org/TR/html401/struct/tables.html#h-11.2.4.1)。span
属性指示Col
元素将影响多少列;在我们的例子中,我们需要限制除最后一列之外的所有列的宽度。- 唯一必需的属性是
span
和width
属性,其他属性是可选的。
以下函数解决了换行问题,我们可能需要 GridView
标题中的“名字”等文本显示在同一行,而不是换行并分成两行。同样,这也适用于数据行,当数据很长时,可能会被迫换到另一行,从而分成两行或更多行。我们可能需要它们作为整体显示在一行中。现在,使用以下函数,实现起来更容易
function addNoWrapSpan(el, noWrap)
{
var span = document.createElement("span");
while(el.childNodes.length > 0)
{
var child = el.firstChild;
el.removeChild(child);
span.appendChild(child);
}
if(noWrap)
span.style.whiteSpace = "nowrap";
else
span.style.whiteSpace = "inherit";
el.appendChild(span);
}
function setNoWrap(el, noWrapTH, noWrapTD)
{
el = document.getElementById(el);
if(noWrapTH)
{
var allTHs = el.getElementsByTagName("TH");
for(var i = 0; i < allTHs.length; i++)
{
addNoWrapSpan(allTHs[i], noWrapTH);
}
}
if(noWrapTD)
{
var allTDs = el.getElementsByTagName("TD");
for(var i = 0; i < allTDs.length; i++)
{
addNoWrapSpan(allTDs[i], noWrapTD);
}
}
}
第一个函数 addNoWrapSpan
将提供的元素 el
(无论是 td
还是 th
)内的所有元素移动到一个动态生成的 span
元素中,该元素根据 noWrap
是 true 还是 false 具有适当的样式。使用的样式是
span.style.whiteSpace = "nowrap";
如果 noWrap
为 true,则设置以下样式,如果为 false,则设置以下样式
span.style.whiteSpace = "inherit";
setNoWrap
接收三个参数:el
,即我们要配置的元素;noWrapTH
,告诉我们是否允许子 TH
换行;以及 noWrapTD
,指示是否允许元素 el
内部的 TD
换行。正如你所注意到的,setNoWrap
会在必要时调用 addNoWrapSpan
。
所附示例项目包含一个示例 GridView
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" Width="100%"
CssClass="GridClass">
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="First Name" />
<asp:BoundField DataField="LastName" HeaderText="Last Name" />
<asp:BoundField DataField="Age" HeaderText="Age" />
<asp:CommandField ShowSelectButton="True" />
</Columns>
</asp:GridView>
它只是显示代码隐藏中设置的数据,并且还有一个带有选择 LinkButton
的 CommandField
。现在,代码隐藏如下
private DataTable BuildDataSource()
{
DataTable dt = new DataTable();
dt.Columns.Add("FirstName", typeof(string));
dt.Columns.Add("LastName", typeof(string));
dt.Columns.Add("Age", typeof(int));
DataRow dr;
dr = dt.NewRow();
dr["FirstName"] = "John the first";
dr["LastName"] = "Doe";
dr["Age"] = 23;
dt.Rows.Add(dr);
dr = dt.NewRow();
dr["FirstName"] = "Clark";
dr["LastName"] = "Kent";
dr["Age"] = 28;
dt.Rows.Add(dr);
return dt;
}
这是一种用于测试目的的简单数据显示方式;在实际场景中,这将替换为从数据库检索数据的代码
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
string formatScript = "";
formatScript += string.Format("formatGrid('{0}');" +
"setNoWrap('{0}', false, true);",
GridView1.ClientID);
ClientScript.RegisterStartupScript
(this.GetType(), "format", formatScript, true);
}
这是我们的 JavaScript 被投入使用的地方。请注意,我正在调用 setNoWrap
并为 noWrapTH
参数分配 false
;这意味着如果其内容很长,标题仍将换行,你可能需要将其更改为 true
。
自动生成列
AutoGeneratedColumns.aspx
这里我描述了一种允许我们在自动生成的列中格式化日期的方法(AutoGenerateColumns =true
)。
private void FormatDatesInGridView(GridView gv, GridViewRow gvr)
{
if (gv.DataSource != null)
{
DataTable dt = null;
if (gv.DataSource is DataView)
{
dt = ((DataView)gv.DataSource).Table;
}
else
if (gv.DataSource is DataSet)
{
dt = ((DataSet)gv.DataSource).Tables[0];
}
else
if (gv.DataSource is DataTable)
{
dt = (DataTable)gv.DataSource;
}
if (gvr.RowType == DataControlRowType.DataRow)
{
foreach (TableCell tc in gvr.Cells)
{
DataControlFieldCell dcfc = (DataControlFieldCell)tc;
if (dcfc.ContainingField is AutoGeneratedField)
{
AutoGeneratedField agf = (AutoGeneratedField)dcfc.ContainingField;
if (agf.DataType == typeof(DateTime))
{
System.Reflection.FieldInfo fi;
fi = typeof(BoundField).GetField("_dataFormatString",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic);
fi.SetValue(agf, "{0:dd/MM/yyyy}");
fi = typeof(DataControlField).GetField("_statebag",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic);
((StateBag)fi.GetValue(agf))["DataFormatString"] =
"{0:dd/MM/yyyy}";
System.Reflection.MethodInfo mi;
mi = typeof(BoundField).GetMethod("OnFieldChanged",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
mi.Invoke(agf, null);
((BoundField)agf).HtmlEncode = false; //Fix for Dates Formatting
}
}
}
}
}
}
名为 FormatDatesInGridView
的方法将从要格式化的 GridView
的 RowCreated
事件处理程序内部调用。此方法接收两个参数:GridView
和当前行。调用事件处理程序将如下所示
protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
{
if(e.Row.RowType == DataControlRowType.DataRow)
FormatDatesInGridView(GridView1, e.Row);
}
该方法首先确定 GridView
的 DataSource
(如果你有其他类型作为 DataSource
,可以修改)。然后,它检查当前 GridViewRow
的所有单元格,如果 AutoGeneratedField
的 ContainingField
类型为 DateTime
,我们使用反射执行以下操作
- 将
_dataFormatString
设置为"{0:dd/MM/yyyy}"
(可以根据需要更改)。 - 通过设置
_statebag["DataFormatString"]
的值,将Viewstate["DataFormatString"]
设置为相同的值"{0:dd/MM/yyyy}"
。 - 调用
OnFieldChanged
方法以应用这些更改(此方法最终会导致绑定发生)。 - 更新:显然,
HtmlEncode
在某些环境中有时为 true,这阻止了我们控制日期的格式。这就是为什么我添加了将其设置为false
的行。
就这样,日期字段现在已格式化。我如何想出这些步骤是通过使用 Reflector 并检查类如何在内部工作。
更新历史
- 2009 年 2 月 12 日:日期格式化的
HtmlEncode
修复。 - 2008 年 8 月 3 日:自动生成列。
- 2008 年 6 月 22 日:添加了插图部分。