三、命令列的使用
命令列主要提供了
GridView中常用的一些修改、更新、删除的操作,在GridView与DataSource控件结合使用的情况下,我们并不需要为些编写太多的代码就可以实现简单的维护操作,如果在复杂的情况下(如一对多链接查询时),我们仍需要编写代码来实现更新与删除。
(车延禄)
使用命令列有两种方式:
1.在“可选列”中添加相应的CommandField列的子项
2.在“可选列”中添加CommandField列,然后设置CommandField列的属性ShowEditButton、ShowDeleteButton等
一旦添加了命令按钮,我们就可以对GridView实现简单的修改和更新的操作了。但是如果要想实现删除操作的话还不行,还需要为GridView设置一个DataKeyNames属性。如果不设置此属性的话编译是没问题的,但运行时会产生“未提供参数”的异常,这一点切记。
DataKeyNames属性用来设置GridView对应的数据源的主键列,只有设置这个属性,在删除的时候才会把要删除的主键值传递给DataSource控件执行删除功能。
现在编辑界面如图:
我们一起来看一下运行结果所存在的问题:
1.主键列Code不应当被编辑
2.GridView自动产生的文本框太宽,把我们的GridView都给挤变形了。
3.民族列在浏览时一直都显示民族代号,应当显示民族名称
4.民族列在修改的时候最好使用下拉列表让用户选择相应的民族。
5.文本输入框没有任何验证,对输错的信息在提交的时候会产生异常
6.删除时没有确认提示
7.删除时产生外键引用的异常
这里的问题实在是太多了,有的问题很简单就可以解决,但有的问题我们借助后面的模板列会更简单一些。
1、防止某列被编辑,只需要在“编辑列”对应框中将该列的ReadOnly属性设为true就可以了。
2、代码控制编辑状态下文本框的宽度(这种方式有点复杂,可以用模板列实现)
我们上面已经分析过GridView每一行的建造过程了,它包含“创建行”和“绑定行”两步操作,“创建行”的过程中会把一些静态的东西创建出来,当然也包含文本框的创建。因此我们只需要在RowCreated事件中把编辑列中的文本框的长度改变一下就可以了。
protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
//这里可能是VS2005的一个Bug,所以我只好把整型强制转为DataControlRowState枚举类型再与e.Row.RowState属性进行比较
//4-Normal行的编辑状态,5-Alternating行处于编辑状态
if (e.Row.RowState == (DataControlRowState)4||e.Row.RowState==(DataControlRowState)5)
{
//Width属性是Unit类型,不能直接赋整型值
//Unit.Pixel():按像素设置绝对宽度;Unit.Percent():按百分比设置宽度
((TextBox)e.Row.Cells[1].Controls[0]).Width = Unit.Pixel(60);
((TextBox)e.Row.Cells[3].Controls[0]).Width = Unit.Pixel(60);
((TextBox)e.Row.Cells[4].Controls[0]).Width = Unit.Pixel(100);
}
}
}
3.浏览状态时显示民族名称,只需要将实体中添加一个只读属性NationName,然后用它替换Nation字段,绑定到GridView就可以了。
4.对主表中的数据删除时级联删除相关子表数据
这种问题用很多种实现方式:
a.使用数据库中外键的级联删除功能
b.使用存储过程或触发器。
c.在程序代码中删除
前两种方式实现方式主要是数据库方面的知识,这里不多说了,下面我们主要看看如何使用程序代码删除数据。
当我们点击删除按钮的时候,GridView会自动把要删除的行对象传递给DataSource控件,然后DataSource调用相关的业务类的方法执行删除,再然后就发生“外键引用异常”......
整个这个过程是系统自动完成的,那我们要想自己写代码进行级联删除,并且禁止系统自动调用默认的删除功能,那如何实现呢?
下面显示了GridView中的一部分事件
大家会注意到其中的事件大部分都有两类:***ed和***ing两类。如:Deleted和Deleting事件
***ed事件:系统执先完操作后触发,如Deleted事件是系统调用默认删除功能,删除成功后自动触发
***ing事件:系统执行操作之前触发,如Deleting事件是系统调用默认删除功能之前触发。
现在大家应当清楚在点击删除按钮后如何添加自己的删除处理程序了,答案当然是应当在Deleting中编写代码进行级联删除。
主要代码如下:
protected void GridView1_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
//取得要删除行的主键的值
string str = GridView1.DataKeys[e.RowIndex].Value.ToString();
//使用事务
using (TransactionScope ts = new TransactionScope())
{
//删除该主键值在删除简历表中对应的数据
new WorkDA().deleteInfo(str);
//删除该主键值在删除家庭关系表中对应的数据
new FamilyDA().deleteInfo(str);
//删除该主键值在基本信息表中对应的数据
new InfoDA().delete(str);
//提交事务
ts.Complete();
}
//取销事件进一步响影,即该删除操作不再往上提交至DataSource控件。
e.Cancel = true;
}
问题还没全部解决,剩下的问题我们留给模板列来解决。(虽然不用模板列也可以解决上面所有的问题,但是那可需要大量的Coding,所以建议大家使用模板列)
四、模板列
关于模板列我们可以用四个字来描述--“无所不能”。很多棘手的问题,在遇到模板列的时候会迎刃而解,因为模板列的定制功能很强大,允许我们建立不同状态下的模板,所以我们用代码控制起它来也就更加灵活自如了。(车延禄)
创建模板列一般有两种方式:
在“编辑列”对话框中向“选中列”中添加TemplateField字段
在“编辑列”对话框中点击“选中列”中相应的列,再点击“确定”按钮上方的“将此字段转换为TemplateField”文字,这就把现有的列转换为模板列了。这种转换方式大家要小心使用,因为一旦转换为模板列,就没办法再转换回去了。
当一个绑定列被转换成模板列后会发生如下变化:
变化前的代码:
变化后的代码:
变化后的设计界面
通过上面的图我们可以看到,当一个绑定列转换为模板列后,该列的数据显示时,会把数据源的数据绑定到模板列的Label控件中去。而不是像绑定列那样直接把数据绑定到单元格的Text属性上。
所以取模板列的单元格中的值与取绑定列的单元格中的值是有区别的:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
if (e.Row.RowState == DataControlRowState.Normal || e.Row.RowState == DataControlRowState.Alternate)
{
//在RowDataBound事件中操作绑定列的单元格中的显示值
e.Row.Cells[4].Text += "...";
//在RowDataBound事件中操作模板列的单元格中的显示值
((Label)e.Row.Cells[3].Controls[1]).Text ="...";
}
}
}
在绑定列中,数据直接绑定在单元格的Text属性上,所以可以直接用e.Row.Cells[4].Text进行操作;但在模板列中,数据并没有绑定在单元格的Text上,而是绑定在Label控件的文本上,所以应当通过e.Row.Cells[3].Controls[1]来取得Label控件(要记得强制转换)。细心的朋友会发现这里我是取“当前行第三个单元格中第一个控件”该控件就是显示数据的Label控件,那为什么要取第一个控件而不是第“零”个控件呢?答案是,当GridView运行的时候会在模板列中绑定显示数据的Label控件的前后分别加入一个Literal控件。
下面我们继续来解决未解决的问题:(车延禄)
1.如何实现删除时出现确认提示的功能:
思路:把删除按钮所在的列转换模板列,此时我们就会在“源页面”中找到删除按钮了,然后在删除按钮里加入OnClientClick事件,执行return window.confirm('...')脚本函数。
代码界面:
运行界面:
2.民族列在编辑状态下显示下拉列表
第一步:把民族列变为模板列
第二步:修改民族列的编辑模板,把原有的文本框删除,加入下拉列表ddlNation和DataSource控件
第三步:配置DataSource控件,访问民族字典表。
第四步:将ddlNation下拉列表绑定到DataSource控件上
第五步:打开“源页面”在ddlNation控件中加入属性:SelectedValue=<%#Bind('Nation')%>,把下拉列表框的选中值设为当前行的民族值。
运行效果:
大家思考:如何把性别列变为单先按钮列表形式?
3.在编辑状态下加入验证控件:
把在编辑状态下需要加入验证控件的列全都转换为模板列,然后在其编辑状态模板上加入验证控件,并设置其验证相应的文本框即可。
4.用模板列实现GridView的嵌套
在GridView模板列的单元格中再加入GridView。这种形式将来有可能用到。
运行效果:
《图23》
这个例子使用了三个GridView进行嵌套,显示出制造商、系列和汽车型号之间的关系。
源代码如下:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
CellPadding="0" DataKeyNames="Prod_code" DataSourceID="ObjectDataSource1"
ForeColor="#333333" GridLines="Horizontal" onrowdatabound="GridView1_RowDataBound"
Width="100%">
<FooterStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" />
<RowStyle BackColor="#E3EAEB" HorizontalAlign="Center" />
<Columns>
<asp:BoundField DataField="Prod_name" HeaderText="生产厂商"
SortExpression="Prod_name">
<HeaderStyle Width="100px" />
<ItemStyle BackColor="#99CCFF" />
</asp:BoundField>
<asp:TemplateField HeaderText="汽车信息">
<ItemTemplate>
<asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False"
CellPadding="0" ForeColor="#333333"
GridLines="Horizontal" ShowHeader="False" Width="100%"
DataKeyNames="Brand_Code" onrowdatabound="GridView2_RowDataBound">
<FooterStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" />
<RowStyle BackColor="#E3EAEB" BorderStyle="Dotted" HorizontalAlign="Left" />
<Columns>
<asp:BoundField DataField="Brand_name" HeaderText="Brand_name"
SortExpression="Brand_name" >
<HeaderStyle Width="100px" />
<ItemStyle BackColor="#FFCC66" Width="100px" />
</asp:BoundField>
<asp:TemplateField>
<ItemTemplate>(车延禄)
<asp:GridView ID="GridView3" runat="server" AutoGenerateColumns="False"
CellPadding="0" ForeColor="#333333" GridLines="None" ShowHeader="False"
Width="100%">
<FooterStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" />
<RowStyle BackColor="#E3EAEB" />
<Columns>
<asp:BoundField DataField="Name">
<ItemStyle BackColor="#CC99FF" HorizontalAlign="Left" Width="100%" />
</asp:BoundField>
</Columns>
</asp:GridView>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</ItemTemplate>
</asp:TemplateField>
</Columns>
...... </asp:GridView>
后台代码如下:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
if (e.Row.RowState == DataControlRowState.Normal || e.Row.RowState == DataControlRowState.Alternate)
{
string prodCode = GridView1.DataKeys[e.Row.RowIndex].Value.ToString();
ObjectDataSource sdsBrand = new ObjectDataSource(); ;
sdsBrand.ID = "ObjectDataSource2";
sdsBrand.TypeName = "BrandDA";
sdsBrand.SelectMethod = "selectProd_code";
sdsBrand.SelectParameters.Add("prod_code", prodCode);
e.Row.Cells[1].Controls.Add(sdsBrand);
GridView gvBrand = (GridView)e.Row.Cells[1].FindControl("GridView2");
gvBrand.DataSourceID = sdsBrand.ID;
}
}
}
protected void GridView2_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
if (e.Row.RowState == DataControlRowState.Normal || e.Row.RowState == DataControlRowState.Alternate)
{
GridView gvBrand = e.Row.Parent.Parent as GridView;
string brandCode = gvBrand.DataKeys[e.Row.RowIndex].Value.ToString();
ObjectDataSource sdsCar = new ObjectDataSource(); ;
sdsCar.ID = "ObjectDataSource3";
sdsCar.TypeName = "CarDA";
sdsCar.SelectMethod = "selectBrand";
sdsCar.SelectParameters.Add("brand", brandCode);
e.Row.Cells[1].Controls.Add(sdsCar);
GridView gvCar = (GridView)e.Row.Cells[1].FindControl("GridView3");
gvCar.DataSourceID = sdsCar.ID;
}
}
}
五、按钮列
除了执行“命令列”功能之外的其它功能按钮,这些按钮在点击的时候都会触发RowCommand事件。如果在GridView中有多个按钮列,那这多个按钮列的按钮就都过来执行RowCommand事件中的代码,如何区分是哪个按钮列的哪个按钮被点击是一个关键性的问题。(车延禄)
区分不同列的按钮:
为区分不同类型的按钮(即不同列的按钮),我们在创建按钮列的时候可以为按钮列设置CommandName,这样不同类型的按钮就可以通过CommandName来区分了。
区分不同行的按钮:
不同行的按钮不用我们手动区分,系统会自动为我们区分,在RowCommand的事件参数GridViewCommandEventArgs e中有一个属性e.CommandArgument,它的值就是行的索引号。因此我们可以通过e.CommandArgument判断是哪一行的按钮被点击了。
我们既可以通过按钮列的CommandName属性来区分不同列的按钮,又可以通过RowCommand事件的GridViewCommandEventArgs参数取得不同行的按钮,那二者结合起来使用岂不可以随意控制页面中任何地方的按钮了吗?
如:
源代码:
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1"
onrowcommand="GridView1_RowCommand" DataKeyNames="Ids">
<FooterStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" />
<RowStyle BackColor="#E3EAEB" />
<Columns>
......
<asp:ButtonField CommandName="cmdUP" Text="向上" />
<asp:ButtonField CommandName="cmdDN" Text="向下" />
</Columns>
......
</asp:GridView>
C#代码:(车延禄)
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "cmdUP")
{
string pk = GridView1.DataKeys[Convert.ToInt32(e.CommandArgument)].Value.ToString();
Response.Write("主键为" + pk + "行的向上按钮被点击了");
}
else if (e.CommandName == "cmdDN")
{
string pk = GridView1.DataKeys[Convert.ToInt32(e.CommandArgument)].Value.ToString();
Response.Write("主键为" + pk + "行的向下按钮被点击了");
}
}