webabcd - 专注于asp.net

ASP.NET
从现在开始 一切都不晚
posts - 151, comments - 4188, trackbacks - 344, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理
原文地址:http://www.codeproject.com/KB/webforms/EditGridviewCells.aspx
[原文源码下载]
[译者改后源码下载]


[翻译]在GridView中针对鼠标单击的某一独立单元格进行编辑


原文发布日期:2007.04.07
作者:Declan Bright
翻译:webabcd


介绍
ASP.NET的GridView控件允许你通过设置它的EditIndex属性来编辑数据行,此时整个数据行都处于编辑模式。 如果你在EditItemTemplate的一些列中使用了DropDownList控件,那么你也许不希望整个数据行都处于编辑模式。 因为,如果每一个DropDownList控件都有很多选项的话,那么一次加载所有DropDownList控件的所有选项就会导致页面执行缓慢。

另外,如果你的数据行的编辑模式需要占用更多的空间的话,那么针对每一个独立的单元格进行编辑要优于针对整个数据行进行编辑。 这里,我将示范如何实现这样的功能,又如何去处理事件验证(event validation)。


背景
本文基于我之前写的一篇文章:GridView和DataList响应单击数据行和双击数据行事件。如果你不知道如何让GridView响应单击数据行事件,那么你可以在阅读本文之前先看看这篇文章。


编辑某一个独立的GridView单元格。


我所演示的这个GridView有一个不可见的asp:ButtonField控件,它处于GridView的第一列,名为“SingleClick”。 它用于给GridView的数据行增加单击事件。
<Columns>                
    
<asp:ButtonField Text="SingleClick" CommandName="SingleClick" Visible="False" />
</Columns>

其它每一列的ItemTemplate中有一个可见的Label控件和一个不可见的TextBox或DropDownList控件。 为了方便,我们称Label为显示控件,TextBox或DropDownList为编辑控件。
    <asp:TemplateField HeaderText="Task">
        
<ItemTemplate>
            
<asp:Label ID="DescriptionLabel" runat="server" Text='<%# Eval("Description") %>'></asp:Label>
            
<asp:TextBox ID="Description" runat="server" Text='<%# Eval("Description") %>' Width="175px" visible="false"></asp:TextBox>
        
</ItemTemplate>
    
</asp:TemplateField>

这里的办法就是用显示控件来显示数据,当单元格所包含的显示控件被单击的时候,则把显示控件的Visible属性设置为false并且把编辑控件的Visible属性设置为true。 这里不用使用EditItemTemplat。

在RowDataBound事件内循环为每一数据行的每一单元格增加单击事件。 使用单元格在数据行中的索引作为事件参数,这样在单元格触发了单击事件后我们就可以知道到底是哪个单元格被单击了。
    protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
    
{
        
if (e.Row.RowType == DataControlRowType.DataRow)
        
{
            
// 从第一个单元格内获得LinkButton控件
            LinkButton _singleClickButton = (LinkButton)e.Row.Cells[0].Controls[0];
            
// 返回一个字符串,表示对包含目标控件的 ID 和事件参数的回发函数的 JavaScript 调用
            string _jsSingle = ClientScript.GetPostBackClientHyperlink(_singleClickButton, "");

            
// 给每一个可编辑的单元格增加事件
            for (int columnIndex = _firstEditCellIndex; columnIndex < e.Row.Cells.Count; columnIndex++)
            
{
                
// 增加列索引作为事件参数
                string js = _jsSingle.Insert(_jsSingle.Length - 2, columnIndex.ToString());
                
// 给单元格增加onclick事件
                e.Row.Cells[columnIndex].Attributes["onclick"= js;
                
// 给单元格增加鼠标经过时指针样式
                e.Row.Cells[columnIndex].Attributes["style"+= "cursor:pointer;cursor:hand;"
            }
     
        }

    }

在RowCommand事件内读出命令参数和事件参数。 这会告诉我们被选中的行和列的索引。
    int _rowIndex = int.Parse(e.CommandArgument.ToString());      
    
int _columnIndex = int.Parse(Request.Form["__EVENTARGUMENT"]);

因为知道了被选中的行和列的索引,所以可以通过把显示控件的Visible设置为false,编辑控件的Visible设置为true来把某个独立的单元格设置为编辑模式。 然后通过清除单元格的属性来删除被选中单元格的单击事件。
    // 获得被选中单元格的显示控件并设置其不可见
    Control _displayControl = _gridView.Rows[_rowIndex].Cells[_columnIndex].Controls[1]; 
    _displayControl.Visible 
= false;
    
// 获得被选中单元格的编辑控件并设置其可见
    Control _editControl = _gridView.Rows[_rowIndex].Cells[_columnIndex].Controls[3];
    _editControl.Visible 
= true;
    
// 清除被选中单元格属性以删除click事件
    _gridView.Rows[_rowIndex].Cells[_columnIndex].Attributes.Clear();

下面有一些代码用于回发服务器后设置焦点到编辑控件,如果编辑控件是DropDownList的话,那么它的SelectedValue要设置为显示控件的值,如果编辑控件是TextBox的话,那么为了做好编辑的准备就要使它的文本被选中。
    // 设置焦点到被选中的编辑控件
    ClientScript.RegisterStartupScript(GetType(), "SetFocus"
        
"<script>document.getElementById('" + _editControl.ClientID + "').focus();</script>");
    
// 如果编辑控件是DropDownList的话
    
// SelectedValue设置为显示控件的值
    if (_editControl is DropDownList && _displayControl is Label)
    
{
        ((DropDownList)_editControl).SelectedValue 
= ((Label)_displayControl).Text;
    }
                 
    
// 如果编辑控件是TextBox的话则选中文本框内文本
    if (_editControl is TextBox)
    
{
       ((TextBox)_editControl).Attributes.Add(
"onfocus""this.select()");
    }

在这个Demo中,我把事件被触发的历史记录也写到了页里。

如果GridView处于编辑模式的话,那么要在RowUpdating事件里去查找被选中行的每一个单元格。 如果发现单元格处于编辑模式的话,那么就调用“更新”代码。 在这个Demo中,数据保存在DataTable里,而这个DataTable则储存在session中。
    // 循环每一列以找到处于编辑模式下的单元格
    for (int i = 1; i < _gridView.Columns.Count; i++)
    
{
        
// 获得单元格的编辑控件
        Control _editControl = _gridView.Rows[e.RowIndex].Cells[i].Controls[3];
        
if (_editControl.Visible)
        
{
           . update the data
        }

    }

为了确保RowUpdating事件在编辑单元格后被激发,要在Page_Load中来触发这个事件。 编辑了TextBox后,通过按回车键或者单击另一单元格来使页面做回发处理,下面的这段代码就是用于确保任何数据的改变都会被更新。
    if (this.GridView1.SelectedIndex > -1)
    
{
        
this.GridView1.UpdateRow(this.GridView1.SelectedIndex, false);
    }
   


为了验证而注册回发和回调数据
在RowDataBound中创建的自定义事件必须要在页中注册。 通过重写Render方法来调用ClientScriptManager.RegisterForEventValidation。 通过GridViewRow.UniqueID返回行的唯一ID,按纽的唯一ID通过在行的唯一ID后附加“$ct100”而生成。
    protected override void Render(HtmlTextWriter writer)
    
{
        
foreach (GridViewRow r in GridView1.Rows)
        
{
            
if (r.RowType == DataControlRowType.DataRow)
            
{
                
for (int columnIndex = _firstEditCellIndex; columnIndex < r.Cells.Count; columnIndex++)
                
{
                    Page.ClientScript.RegisterForEventValidation(r.UniqueID 
+ "$ctl00", columnIndex.ToString());
                }

            }

        }

      
        
base.Render(writer);
    }

这将防止任何“回发或回调参数无效”的错误。


这个Demo中的其它示例
使用SQL数据源控件编辑某一独立的GridView单元格
用SqlDataSouce控件实现这个技术需要对GridView的RowUpdating事件做一些修改。 当更新GridView的行的时候,SqlDataSource控件一般要把值(values)从EditItemTemplate转移到NewValues集合里。 因为我们没有使用EditItemTemplate,所以这种情况下值(values)不会自动地转移到NewValues集合里。
    e.NewValues.Add(key, value);

我在App_Data文件夹下使用了一个简单的SQL Server Express数据库。 (要使用你自己的数据库的话,你可以修改web.config里的连接字符串)


使用对象数据源控件编辑某一独立的GridView单元格
本示例使用了App_Code文件夹内的两个类:
    ·Task.cs – 任务对象
    ·TaskDataAccess.cs – 管理任务对象

Aspx页的后置代码与SQL Data Source示例是一样的。 ObjectDataSource通过TaskDataAccess.cs类里的GetTasks和UpdateTask方法来管理数据。


有着电子数据表样式的GridView
这里有一个与电子数据表的样式很像的GridView。 (虽然它看起来像一个电子数据表,但是并不是真的有像电子数据表一样的功能,它仍然是一个GridView。)

这里虽然有一些单击后改变单元格样式的附加代码,但是主要的代码还是与上面所述是相同的。



用SQL数据源控件实现有着电子数据表样式的GridView
本示例与上面的基本相同,但是它修改了GridView的RowUpdating事件以使其允许用SqlDataSource控件来工作。
 

参考
    ·GridView和DataList响应单击数据行和双击数据行事件
    ·ASP.NET 2.0数据教程


结论
如果你想在GridView中一次只针对一个单元格进行编辑,那么这个方法将会对你有所帮助。


译者注:事件验证(EventValidation)。出于安全目的,此功能验证回发或回调事件的参数是否来源于最初呈现这些事件的服务器控件。如果数据有效并且是预期的,则使用ClientScriptManager.RegisterForEventValidation方法来注册回发或回调数据以进行验证。

Feedback

#1楼    回复  引用  查看    

2007-04-23 08:26 by 高海东      
很好的方法 ,如果能有方向键的上下左右移动就更好

#2楼 [楼主]   回复  引用  查看    

2007-04-23 08:55 by webabcd      
@高海东
嗯,什么都是可以做到的

#3楼    回复  引用  查看    

2007-04-23 10:06 by 哈密瓜牌牛奶      
不错哦!!

#4楼 [楼主]   回复  引用  查看    

2007-04-23 10:45 by webabcd      
@哈密瓜牌牛奶
:)
同感,正准备应用到一些项目中

#5楼    回复  引用  查看    

2007-04-23 11:01 by 想吃老虎的猪      
不错 谢谢

#6楼    回复  引用  查看    

2007-04-23 11:02 by haitian      
学习了 谢谢!

#7楼    回复  引用    

2007-04-23 11:02 by ivw [未注册用户]
终于出来了,呵呵。支持

#8楼 [楼主]   回复  引用  查看    

2007-04-23 11:29 by webabcd      
@想吃老虎的猪
@haitian
@ivw
:)

#9楼    回复  引用    

2007-04-23 11:37 by ivw [未注册用户]
不知道有没有想过把这个功能结合你之前扩展的那个gridview控件那里呢?

#10楼 [楼主]   回复  引用  查看    

2007-04-23 11:53 by webabcd      
@ivw
可以,没问题的

#11楼    回复  引用    

2007-04-23 13:02 by WinKen [未注册用户]
效果不错.
不过一般的项目赶时间不敢用...

学习了.....

谢谢,,

#12楼 [楼主]   回复  引用  查看    

2007-04-23 13:38 by webabcd      
@WinKen
:)
不谢

#13楼    回复  引用    

2007-04-23 14:54 by ivw [未注册用户]
让我试试(呵呵),他这个编辑的原理好像就是用一个button模拟出编辑更新按钮,是吗?如果加上判断,数据没有更新时不回发,那可能性能上会有很大的改进。

#14楼    回复  引用    

2007-04-23 15:14 by ivw [未注册用户]
能不能做到点击后整行处于编辑状态啊?这样要改一行数据的时候就方便多了

#15楼    回复  引用    

2007-04-23 15:24 by ivw [未注册用户]
楼主试试在SQL里跟踪一下,一点击他做了9次的select操作,如果数据量大的话这可不简单啊。你那里是这样吗?

#16楼 [楼主]   回复  引用  查看    

2007-04-23 17:06 by webabcd      
@ivw
是,更新就是用GridView.UpdateRow方法
没有更新时也要会回发的,比如要把TextBox变成Label

点击后整行处于编辑状态其实就是响应一下行的单击事件,让鼠标单击行后相当于单击了行的CommandName="Edit"的按钮就ok

跟踪了一下,一次select操作

#17楼    回复  引用    

2007-04-23 17:31 by ivw [未注册用户]
改成这个我这里试不成功。CommandName="Edit";

我改用了SQL2000,跟踪的确发生了9次select操作,你那就没有,真的奇怪了,代码我没有改过。



把这功能放到你那扩展的gridview控件里基本成功,不过如果还有一些问题没解决。

#18楼    回复  引用    

2007-04-23 18:16 by ivw [未注册用户]
想请问你,得到了行号,怎样可以得到这行的列值啊?

#19楼 [楼主]   回复  引用  查看    

2007-04-23 21:29 by webabcd      
@ivw
没问题的,当然要有EditItemTemplate

列索引是事件的参数,读取的时候这么读Request.Form["__EVENTARGUMENT"]
列索引知道了,列的值就能读出来了

#20楼    回复  引用    

2007-04-24 12:01 by ivw [未注册用户]
为什么去掉Render这个过程他也没问题啊?

#21楼    回复  引用    

2007-04-24 12:15 by ivw [未注册用户]
请问EditItemTemplate这里是添加一个编辑、更新、取消那个按钮吗?

#22楼 [楼主]   回复  引用  查看    

2007-04-24 12:36 by webabcd      
@ivw
Render那段是为了实现事件验证(EventValidation),是出于安全目的的,所以去掉也没问题

可以用CommandField
或者自己写按钮然后设置CommandName

#23楼    回复  引用    

2007-04-24 15:31 by ivw [未注册用户]
我用了edit后他只能编辑第1列,其它不能编辑啊。

#24楼    回复  引用    

2007-04-24 15:49 by ivw [未注册用户]
如果把 private const int _firstEditCellIndex = 1;设成1下面这两句就会提示超出范围。你有试过吗/

Control _displayControl = _gridView.Rows[e.RowIndex].Cells[i].Controls[1];
Control _editControl = _gridView.Rows[e.RowIndex].Cells[i].Controls[3];

#25楼    回复  引用    

2007-04-24 16:18 by ivw [未注册用户]
好像看到他一定要保留一列不给编辑,否则就会在查找控件的时候出错,超出范围。

#26楼 [楼主]   回复  引用  查看    

2007-04-24 18:04 by webabcd      
@ivw
CommandName="Edit",就是现实编辑模板,看看你的编辑模板里怎么写的,就知道了

那是因为你的索引为1的这个单元格没有文中所谓的显示控件或编辑控件
本文的示例并不通用,需要具体需求做具体修改

文中示例的第一列其实就是按钮

#27楼    回复  引用  查看    

2007-04-24 18:39 by 随心所欲      
不错。
如果能做到批量提交就更好了。每次可以编辑很多行,然后一次性提交
页面的数据量会稍微大一些,但是我想没关系。只要做好分页,也不会达到哪里去。

#28楼    回复  引用    

2007-04-24 20:27 by ivw [未注册用户]
对,这种方法只合适单个单元格编辑使用,而且如果数据量大对速度影响很大。如果能做到像Winform的grid那样就完美了,呵呵

#29楼 [楼主]   回复  引用  查看    

2007-04-24 22:19 by webabcd      
@随心所欲
@ivw
肯定是可以,曾经做过一个有相似需求的项目

我的实现方法是这样
在客户端对table编辑还算是简单,更新相关td的innerHTML就行了,插入行删除行用js也都能实现
把所有对table的更新放到js的dom中,然后与一个hiddenfield同步,然后提交后批量更新数据库

#30楼    回复  引用    

2007-04-24 22:49 by ivw [未注册用户]
能做个例子放上来让大家学习吗?

#31楼 [楼主]   回复  引用  查看    

2007-04-25 08:19 by webabcd      
@ivw
公司项目,不能放上来啊

原理就如我上面所说的
兄弟想要实现的话
google一下js操作table以及js的dom,把这两个搞通了,实现那个功能就小case了

#32楼    回复  引用    

2007-04-25 15:21 by ivw [未注册用户]
呵呵,了解

#33楼    回复  引用    

2007-04-25 15:57 by ivw [未注册用户]
如果像模板列那样放进下拉等控件时操作也一样吗

#34楼 [楼主]   回复  引用  查看    

2007-04-25 18:21 by webabcd      
@ivw
:)
文中所述的方法就是用模板列啊

#35楼    回复  引用    

2007-04-25 21:52 by ivw [未注册用户]
我的意思是说如果列中用的是checkbox或下拉框,那用js怎样编辑table啊?

#36楼 [楼主]   回复  引用  查看    

2007-04-25 22:52 by webabcd      
@ivw
原来我做的都是文本的,哈
如果给td增加checkbox或下拉框,我觉得一样是设置td的innerHTML

#37楼    回复  引用    

2007-04-26 10:12 by ivw [未注册用户]
如果是下拉的话就要读库中的资料那怎么办啊,编辑TD的时候读库很麻烦的啊。
还有啊,兄弟,一直都想问问你,自己写的控件库怎么做才能不给别人随便调用啊?

#38楼 [楼主]   回复  引用  查看    

2007-04-26 11:55 by webabcd      
@ivw
既然想全客户端的就尽量不读数据库,不用ajax
有下拉的话就要在起初加载的时候把数据放到页面上

写的库不让别人调用?那就让你库里的所有类都继承自一个基类,然后在这个基类里按mac之类的做判断
要想不让别人篡改就用snk

#39楼    回复  引用    

2007-04-26 14:12 by ivw [未注册用户]
这样做是不是别人不能从工具栏那里添加自己写的DLL来调用控件啊?

#40楼 [楼主]   回复  引用  查看    

2007-04-26 19:50 by webabcd      
@ivw
控件的话就在构造函数里判断mac之类的,以决定让谁用

#41楼    回复  引用    

2007-04-26 23:43 by ivw [未注册用户]
判断网卡MAC地址啊?继承自一个基类,他半身要继承例如Controls这样的类,还可以再继承其它类吗?

#42楼 [楼主]   回复  引用  查看    

2007-04-27 08:25 by webabcd      
@ivw
单纯类库的话最好继承自一个基类
控件的话写在构造函数里就ok了

判断mac还是cpu序列号之类的要看你的需求了

#43楼    回复  引用    

2007-04-27 09:12 by ivw [未注册用户]
有个问题就是,如果使用WMI取硬件的信息很多时候会出现问题。你有API取mac或cpu序列号的资料吗?

#44楼 [楼主]   回复  引用  查看    

2007-04-27 13:02 by webabcd      
@ivw
:(
没有啊

#45楼    回复  引用    

2007-04-27 17:08 by ronglj [未注册用户]
我的没跑起来??
数据库的问题?

#46楼 [楼主]   回复  引用  查看    

2007-04-27 17:43 by webabcd      
@ronglj
测试过,没问题的
看看sqlserver2005 express的服务启没启

#47楼    回复  引用    

2007-04-27 18:57 by ivw [未注册用户]
程序没问题的,我试过了,他本来用的是SQL2005,如果你机上的是SQL2000就要把连接串改,还 有表名也要改的。
兄弟,有个菜问题问问你。自定义控件的构造函数在那啊?

#48楼 [楼主]   回复  引用  查看    

2007-04-27 21:55 by webabcd      
@ivw
以我写的扩展GridView为例
public class SmartGridView : GridView
{
public SmartGridView()
{
// 构造函数
}
}

#49楼    回复  引用    

2007-04-27 22:36 by ivw [未注册用户]
如果是这样,那不是要每个控件里都做判断吗?如果判断硬件信息的话那如果把程序放到其它机上就用不了啦?

#50楼 [楼主]   回复  引用  查看    

2007-04-28 08:29 by webabcd      
@ivw
是这样的
你不是不想让别人用吗,那要根据一些信息判断啊

#51楼    回复  引用    

2007-04-28 20:55 by ivw [未注册用户]
请问为什么我在gridview控件里做了行的单击事件,用ajax调用后台进行select,如果查找带return的话他会做两次的select操作啊?gridview控件放在UpdatePanel控件里。

#52楼 [楼主]   回复  引用  查看    

2007-04-28 21:29 by webabcd      
@ivw
是用数据源控件吗?数据源控件会自动select的,所以你自己再select的话就是两遍

#53楼    回复  引用    

2007-04-28 22:23 by ivw [未注册用户]
是用数据源,但如果我没有返回值他就不会select两次了。有办法解决吗

#54楼    回复  引用    

2007-04-28 22:26 by ivw [未注册用户]
但他出现两次的不是数据源那条sql语句,而是我调用时使用的sql语句。

#55楼    回复  引用    

2007-04-28 22:38 by ivw [未注册用户]
刚才我跟踪多次发现问题可能出现在Fill()跟ExecuteNonQuery()这里的问题,他们各自都要select一次,把数据填充到dataset里除了Fill()还有什么更好的办法啊?

#56楼 [楼主]   回复  引用  查看    

2007-04-29 09:00 by webabcd      
@ivw
兄弟确实奇怪的问题
填充到dataset,用fill是最简单的了

不过现在很少写数据层的代码了,因为用.net 2.0里的强类型dataset和datatableadapter实在是太简单了

#57楼    回复  引用    

2007-04-29 19:09 by ivw [未注册用户]
呵呵 ,兄弟有没有NetAdvantage 2006 的破解啊?我想要里面的那个grid控件啊,web的grid控件实在太基本了。或者有没有其它更好的grid控件啊?

#58楼 [楼主]   回复  引用  查看    

2007-04-29 21:48 by webabcd      
@ivw

没有啊

#59楼    回复  引用    

2007-04-29 22:57 by ivw [未注册用户]
有没有其它更好的grid控件啊?最好是像winform那个grid差不多的。