ListView详解
2012-04-06 20:41:31| 分类: .Net编程空间 | 标签: |字号大中小 订阅
在VS2008之前,要显示集合型数据,我们一般会采用Repeater、DataList或GridView等控件来实现。GridView为我们提供了强大的数据显示功能,但是它把界面限制在一个标准的行列Table表格中,比如难以实现在一行显示多条数据的功能;DataList的界面灵活度比GridView要强一些,但使用起来远没有GridView那样简单方便;而Repeater一般是用来显示数据,它内存的事件与模板太少,功能不如DataList强大。ListView兼有GridView的易用性,又拥有DataList的灵活性,是我们DotNew开发的好工具。
签于网上介绍此控件的中文文章太少、太肤浅,在这篇文章中我们来深入研究一下ListView+DataPager的强大功能。
一、ListView初登场
像GridView和DataList等控件都会在显示数据的时候,会自动我们添加一些额外的标记,如:GridView会在未经我们同意的情况下,自动生成<table><tr><td>等元素,并把数据绑定显示在<td>中,虽然可以使用模板列来自定义列的显示,但模板列仍逃不出GridView强行加入的<table>标记,所以GridView做出来的办面往往是规规正正的二维表格,要应对企业开发中的各种表格化界面显得为不从心(虽然GridView也可以实现单元格的合并、GridView的嵌套,但工作量很大,自由度也很受限制)。
而ListView在显示数据的时候不会为我们强行添加任何额外的HTML标记,ListView界面的HTML代码完全由开发人员通过以下11个模板来自行控制的。
AlternatingItemTemplate
EditItemTemplate
EmptyDataTemplate
EmptyItemTemplate
GroupSeparatorTemplate
GroupTemplate
InsertItemTemplate
ItemSeparatorTemplate
ItemTemplate
LayoutTemplate
SeletedItemTemplate
其中比较常用的模板有LayoutTemplate和ItemTemplate两个模板,LayoutTemplate用来设置ListView外围的标记,而ItemTemplate是用来生成ListView绑定项的内容。
如:使用ListView显示列表项时,模板代码如下:
《图1 》
从上图我们看出,LayoutTemplate和ItemTemplate分别独立定义,那怎么来把二者进行组合形成一个显示界面呢?我们只需要在LayoutTemplate模板中定义一个“runat=server”的元素,并把这个元素的ID设为ListVieiw控件的ItemPlaceholderID属性所指定的值即可(ListView控件的ItemPlaceholderID属性的默认值为“itemPlaceholder”,如上图中我们定义PlaceHolder的ID就是“itemPlaceholder”。当然我也可以把
PlaceHolder的ID指定为另一个值“aaa”,但需要同时把ListView控件的ItemPlaceholderID的属性设为“aaa”)。
在ListView呈显的时候,就会把LayoutTemplate和ItemTemplate结合起来一起呈现:
首先:用ItemTemplate定义的数据项模板来生成记录项集合
《图2》
然后:再把生成的记录项集合内容替代LayoutTemplate中ID=itemPlacehoder的元素
《图3》
下面我们来试着做个例子显示数据:
在这里我们还是使用“汽车表”作为示例数据。ListView代码如下图所示
《图4》
在这个例子中,我们把ListView控件的ItemPlaceholderID属性修改为“placeForItem”,并在LayoutTemplate模板中把第二<tr>设置为runat="server",并且把该<tr>的ID也设置为“placeForItem”。在ItemTemplate模板中我们从<tr>开始设置模板,在运行的时候会用ItemTemplate生成的数据项(<tr>...</tr>)去替换LayoutTemplate中的<tr runat="server" ID="placeForItem"></tr>
运行结果:
《图5》
《图6》
二、分组显示数据
上面我们使用LayoutTemplate和ItemTemplate实现数据的显示,就是在LayoutTemplate中把要引用ItemTemplate数据项的服务端元素的ID设置为ListView控件的ItemPlaceholderID的值。这种显示方式直接把ItemTemplate生成的内容嵌入到LayoutTemplate中。有的时候这种显示方式不能满足需求,比如:想在一行中显示两项或多项数据的话,上面的方式就无能为力了。
《图7》
要实现上面这种效果就需要使用GroupTemplate模板。
它的实现思想不再是在LayoutTemplate中直接引用ItemTemplate了,而是在LayoutTemplate中引用GroupTempate,在GroupTemplate中再引用ItemTemplate,然后再设置ListView控件的GroupItemCount属性。
在运行的时候,先把ItemTemplate和GroupTemplate整合在一起,然后再根据ListView控件的GroupItemCount属性值,决定每个GroupTemplate中放置几个ItemTemplate。这样我们就可以在LayoutTemplate一行中显示多组数据了。
《图8》
从上面的代码中我们看出,就是在LayoutTemplate和ItemTemplate中间加了一个GroupTemplate。在每个GroupTemplate中显示三个ItemTemplate,然后再把所有GroupTemplate生成的内容嵌入到LayoutTemplate中去。
《图9》
上面的例子我们使用列表项实现分组数据显示,下面我们使用<table>表格实现分组数据的显示。
还是以汽车表为例,我要实现一行显示两辆车的信息。因此我们可以把显示表格设计成这个样子:
<table id="listView">
<tr>
<td>
<table id="car">...</table>
</td>
<td>
<table id="car">...</table>
</td>
</tr>
</table>
外层<table>控制总体部局,外层<tr>控制一行显示几项信息,内层的<table>显示汽车详细信息。
1.ListView控件的GroupItemCount属性设置为2,这就意思着在一个GroupTemplate中显示两个ItemTemplate
2.ItemTemplate中设置如下:
<td>
<table id="car">...</table>
</td>
3.GroupTemplate设置如下:
<tr>
<td runat="server" ID="placeForItem"></td>
</tr>
4.LayoutTemplate设置如下:
<table id="listView">
<tr runat="server" ID="placeForGroup"></tr>
</table>
完整代码如下:
《图10》
运行结果:
《图11》(原创:灰灰虫的家http://hi.baidu.com/grayworm)
《图12》
三、排序数据
有的时候需要我们对数据进行排序显示,比如:论坛中按发表时间排序、按最后回复时间排序、按阅读次数排序、按回复数量排序等。ListView也像GridView一样为我们提供了免编码的排序功能,下面我们来看看:
在这里我们使用SqlDataSource为数据源,还是访问汽车表的数据,关于如何配置SqlDataSource数据源,在这里不再赘述。
我们要实现对两个字段的排序:“价格”和“油耗”
首选来看我们ListView模板的实现:
《图13》
从上图中我们看出,在汽车表格的上面我们加入了两个按钮,这两个按钮都设置了CommandName和CommandArgument。CommandName="Sort"代表这是个排序按钮,当它被点击的时候就会执行Sort()方法进行排序,并触发Sording和Sorted事件。CommandArgument用来指定按哪列排序,在这里我们指定列名。
这样就可以排序了,并且是升序、降序轮流执行的。
《图14》
这种排序功能实用性不大,主要原因是:
1.自动排序只能与DataSource控件结合使用,由DataSource控件排序后再绑定显示在List中,并且ListView排序对DataSource控件返回的数据类型有要求,即,它只能对DataSource控件返回的DataSet、DataTable和DataView进行自动排序,对理一般的泛型集合是不支持的。
2.需要把所有数据都读取到服务器内存(DataSet,DataTable,DataView)中,才能进行排序,资源利用效率不高。
如果要对泛型集合进行排序的话,那需要我们编写Sorting代码,把集合手动排序后再绑定到ListView中显示,同时要通过e.Cancel=true来阻止DataSource控件自动排序。
四、根据字段进行分组显示
上面我们介绍了如何对ListView进行分组,实现一行显示多条数据。但有的时候需要我们进行下面这种分组方式:
《图15》
即:根据某个字段的值不同,对数据分组显示。
ListView默认没有这个功能,但我们可以通过一系列的技巧来实现出来。
实现思路:
第一步、根据需要分组的字段把数据排序,这样就可以把该字段值相同的数据显示为相临的数据行,为我们以后分组提供方便。
这一步我们可以在BLL层中使用LinQ来实现
public class MyDBBF
{
private MyDBDataContext _Context = new MyDBDataContext();
public List<Car> GetCars()
{
return _Context.Car.OrderBy(p=>p.Brand1.Prod_Co
}
}
第二步、添加分组头。
我们可以在ItemTemplate中,在每次绑定数据行显示之前判断是否需要添加分组头。
《图16》
从上图中我们看到这样一段代码:
<%# ShowGroupHeader() %>
这是我在aspx.cs中定义的一个方法,此方法决定分组头的显示。该方法的代码如下:
《图17》
这个方法在每行绑定的时候都会被调用到
变量headerText代表上一行分组头的文字(即汽车厂商),变量currentHeader代表当前分组字段值,如果二者相等,说明这一行和上一行属于同一个分组,不需要加分组头,就返回空字符串。如果二者不等,说明这一行与上一行不属同一个分组,就添加一个<tr>表头。
第三步、运行。
运行效果如下:
五、分页
在GridView中,分页一般有两种方式:一种是利用GridView自身带有的分页功能与DataSource控件配合使用实现分页。这种分页方式几乎不用编写代码,实现简单,但它是把所有数据读取到内存中进行分页,对于大数据量的操作销耗内存较大,即使使用缓存技术,其性能也是很低;并且这种分页的导航与GridView集成在一起,导般模式太单调。另一种分页方式就是使用子查询或LInQ实现自定义分页,仅从数据库中检索当前页的数据返回,并自己制作分页导航界面实现分页功能。这种方式性能很高,界面也可以做得比较人性化,但复杂度较高,实现起来比较麻烦,在Repeater和DataList中我们一般使用这种方式进行分页(当然你还可以使用
PagedDataSource组件,它的实现原理与GridView一样,这里不单独列出来)。
在VS2008中为我们提供了一个分页工具DataPager控件,它把分页功能单独封装起来,我们把数据显示控件( Repeater/DataList/GridView/ListView)、数据源控件(SqlDataSource/ObjectDataSource等)和DataPager结合起使用就可以很简单地实现高效的分页功能。
两个常用的属性:
PagedControlID:要分页显示数据的控件,一般是Repeater、DataList、GridView、ListView等。
PageSize:每页的记录数
1.DataPager与SqlDataSource控件一起使用。
这种模式实现的分页仍然是把所有的数据都读取到内存中然后进行分页,对于数据总量不大,并可以预计情况(如:显示一个班的成绩)使用这种方式是可以的。但对于数据量庞大,并且不知道数据总量将膨胀到什么地步的情况下,千万不能使用这种方式。
这种方式实现起来很简单。
第一步:设计ListView模板,并把它与SqlDataSource控件一起绑定显示数据
第二步:把DataPager控件放在页面上(可以放在ListView的外面,也可以放在ListView的LayoutTemplate模板中)。
第三步:设置DataPager控件的两个属性:PagedControlID 和 PageSize。
第四步:设计DataPager的导航模式。一般DataPager有两种常用的导航模式(“上一页/下一页”和“数字页号”),另外还有一种模板导航模式。
《图19》
这几种导航模式可以同时混合使用如下图:
《图20》
这样就可以实现分页功能了
《图21》
2.DataPager与ObjectDataSource控件一起使用
这种使用方式可出现两种分页情况,一种情况就是像DataPager与SqlDataSource控件配合使用一样,把所有数据检索到内存中再进行分页,这种方式我们不再做讲解了。另一种情况就是每次换页的时候,只检索出要显示的数据,下面重点讲述这种方式。
要实现自定义分页,关键是如何配置ObjectDataSource控件向界面提供当前页的数据。
大家都知道ObjectDataSource控件不直接访问数据库,而是通过调用业务对象中的方法实现对数据库的操作。要实现自定义分页,那ObjectDataSource控件的这个业务对象必须要包含两个方法:
a.一个方法接收两个整型输入参数,返回集合类型。这两个参数分别是:“本页首行数据的索引号(从0开始)”和“每页最多显示的数据条数”。返回的是当前页的数据集合。
b.另一个方法也接收上面两个整型输入参数,参数意义与上面方法一样,返回整型值。该整型值就是当前数据集的总记录条数。
代码如下:
《图22》
需要注意的是:上面方法中的两个参数的名子不能随便命名,这两个参数的名子必须与ObjectDataSource 控件的StartRowIndexParameterName属性值和MaximumRowsParameterName属性值相对应。
《图23》
看懂了这些后,下面我们一步一步来实现自定义分页。
a.定义ObjectDataSource要调用的业务对象CarBll(如图22)
b.设置ObejctDataSource控件的EnablePaging=True;
c.设置ObjectDataSource控件的TypeName为业务对象的类名CallBll
d.设置ObjectDataSource控件的SelectMethod属性为业务对象的两个方法中的第一个方法名(即返回结果集的那个方法)
e.设置ObjectDataSource控件的SelectCountMethod属性为业务对象的两个方法中的第二个方法名(即返回整型值的那个方法)
f.检查StartRowIndexParameterName属性值是否与业务对象方法的第一个参数名一致;MaximumRowsParameterName属性值是否与业务对象方法的第二个参数名一致
g.制作ListView的模板,并设置其DataSourceID属性为ObjectDataSource控件的ID
h.把DataPager控件拖到页面上
i.设置DataPager控件的导航外观
j.设置DataPager控件的PagedControlID为ListView控件的ID,PageSize数性为页的大小。
k.运行页面出现我们的运行结果。
上面就是我们使用ListView+ObjectDataSource+DataPager控件来实现自定义分页。
六、增、删、改
在这个例子中我在业务逻辑层使用Linq To Sql 来实现数据库的增、删、改操作,代码如下:
《图30》
(原创:灰灰虫的家http://hi.baidu.com/grayworm)
(一)新增数据
与GridView不同的是ListView为我们提供了添加数据的功能,通过InsertItemTemplate来定义添加数据的界面,在运行的时候就可以在ListView的顶端或底端出现添加InsertItemTemplate的界面。
要在ListView中实现新增数据,需要实现两个关键的步骤:
1.定义InsertItemTemplate模板
2.为ListView的InsertItemPosition属性赋值,指定新增模板显示的位置。FistItem-顶端,LastItem-底端,None-不显示
首先、我们来看如何定义InsertItemTemplate
《图24》
为了简单起见,插入模板中的输入界面全都使用文本框做。文本框的Text属性双向绑定至DataSource控件。“Insert”按钮的CommandName="Insert",“Cancel”按钮的CommandName="Cancel"。
当输入内容后点击“Insert”按钮时,会产生以下操作
1.ListView控件触发ItemInserting事件
2.ListView的新增输入界面中的数据传递给DataSource控件的对应参数。
3.ListView调用对应DataSource控件的插入方法,执行插入操作。
4.ListView触发ItemInserted事件
5.ListView重新绑数据源
6.输入界面中文本框重置
然后、设置ListView的InsertItemPosition属性为Last,让添加模板显示在列表的最下面。
运行界面如图:
《图25》
上面虽然能够实现添加功能,但是输入界面不合理,比如:没有非空验证,日期格式没加验证,性别应当使用单选按钮形式,民族应当使用下拉列表形式表现。
下面我们对添加界面做一个修改:
第一步、对代号、姓名和生日加入非空验证。
<asp:RequiredFieldValidator ID="rfvCode" runat="server" ForeColor="Red" ErrorMessage="代号不能为空" Display="Dynamic" ControlToValidate="CodeTextBox"></asp:RequiredFieldValidator>
<asp:RequiredFieldValidator ID="rfvName" runat="server" ForeColor="Red" ErrorMessage="姓名不能为空" Display="Dynamic" ControlToValidate="NameTextBox"></asp:RequiredFieldValidator>
<asp:RequiredFieldValidator ID="rfvBirthday" runat="server" ForeColor="Red" ErrorMessage="生日不能为空" Display="Dynamic" ControlToValidate="BirthdayTextBox"></asp:RequiredFieldValidator>
第二步、对生日加入范围验证。
<asp:RangeValidator ID="rvBirthday" runat="server"
ErrorMessage="生日应在1900-1-1至2999-12-31之间"
Display = "Dynamic"
ControlToValidate="BirthdayTextBox"
Type="Date"
MaximumValue="2999-12-31"
MinimumValue="1900-1-1"
ForeColor="Red"
></asp:RangeValidator>
第三步、对性别输入界面修改为单选按钮形式
<asp:RadioButtonList ID="SexRadioButton" runat="server" RepeatDirection="Horizontal" RepeatLayout="Flow" SelectedValue='<%# Bind("Sex") %>'>
<asp:ListItem Text="男" Value="True" Selected="True"></asp:ListItem>
<asp:ListItem Text="女" Value="False"></asp:ListItem>
</asp:RadioButtonList>
第四步、把民族修改为下拉列表形式。
<asp:DropDownList ID="NationDropDownList" runat="server" AppendDataBoundItems="True" DataSourceID="ObjectDataSource2" DataTextField="Name" DataValueField="Co
<asp:ListItem Text="请选择" Value="-1" Selected="True"></asp:ListItem>
</asp:DropDownList>
<asp:ObjectDataSource ID="ObjectDataSource2" runat="server" TypeName="HRBF" SelectMethod="GetAllNations"></asp:ObjectDataSource>
对于民族下拉列表有点复杂,我们首先使用ObjectDataSource2 控件向下拉列表中加载民族的数据,然后又把民族下拉列表的SelectedValue绑定到ObjectDataSource1的InsertParameter上。如果你使用的.NET Framework3.5的话,会产生错误信息:
System.InvalidOperationException:Databinding methods such as Ev
这是.NET Framework3.5中的一个Bug,并不是我们代码的问题。微软已经在.NET Framework4.0中修改了此错误。本例子我是使用.NET Framework4.0实现的,所以能够正常运行。如果你正在使用的.NET Framework3.5的话,那就不能直接在<asp:DropDownList>中使用SelectedValue='Bind("Nation")'来绑定数据了,需要你在ItemInserting事件中手动为e.Values["Nation"]进行赋值。
修改后的运行效果:
《图26》
(二)修改数据
ListView像GridView一样为我们提供了修改数据的功能,对于修改数据一共涉及到两个模板(ItemTemplate 和 EditItemTemplate)、三个按钮(编辑、更新、取消)。“编辑”按钮应当放在ItemTemplate中,“更新”“取消”按钮就非常好放在EditItemTemplate中。
当点击“编辑”按钮时,ListView会产生以下操作:
1.ListView触发ItemEditing事件。
2.ListView把被点击的“编辑”按钮所在行的索引号赋给ListView.EditIndex
3.ListView重新绑定数据源
4.索引号等于ListView.EditIndex的行,用EditItemTemplate模板替换ItemTemplate呈显出来。出现修改界面
当点击“更新”按钮时,ListView会产生以下操作:
1.ListView触发ItemUpdating事件
2.ListView把编辑界面输入框的内容赋值给DataSource控件中对应的UpdateParameter
3.ListView调用对应DataSource控件的Update方法,执行数据库的更新操作
4.触发ItemUpdated事件
5.将ListView的EditIndex属性设为-1
6.ListView重新绑定数据
7.ListView退出编辑状态,以ItemTemplate显示修改完的数据。
当点击“取消”按钮时,ListView会产生以下操作:
1.ListView触发ItemCanceling事件
2.ListView把ListView.EditIndex设为-1
3.ListView重新绑定数据源
4.所有数据都使用ItemTemplate绑定显示出来,即退出编辑状态
ItemTemplate中的代码:
加入一个<td>,并在其中添加一个CommandName="Edit"的按钮。
<asp:Button ID="btnEdit" runat="server" Text="Edit" CommandName="Edit" />
EditItemTemplate中的代码:
《图27》
运行结果:
《图28》
(三)删除数据
删除操作不需要新模板,只需要在ItemTemplate模板(其它模板也可以)中添加一个“删除”按钮(CommandName="Delete")就可以。
当点击“删除”按钮后,ListView会执行以下操作:
1.ListView触发ItemDeleting事件。
2.ListView将主键值传递给DataSource控件对应的DeleteParameter。
3.ListView调用对应DataSource 控件的删除方法
4.ListView触发ItemDelete事件
在ItemTemplate中加入一个“删除”按钮
<asp:Button ID="btnEdit" runat="server" Text="Delete" CommandName="Delete" On
并把ListView的DataKeyNames设置为表的主键字段
当点击“删除”按钮后就会删除数据了。