本文适用Winform开发,且DataGridView的数据源为DataTable/DataView的情况。

理解前提:熟知DataTable、DataView

求:更好方案

考虑这样一个场景:

某DataTable(下称dt)的B列是计算列(设置了Expression属性),是根据A列的数据计算而来,该dt被绑定到某个DataGridView(下称dgv),A、B两列都要在dgv中显示,其中A列可编辑(ReadOnly=false)。需求是对A列进行编辑时(输入或删除),B列能实时变化。例如下面的例子:

【目标文件名】是根据【款号】和【色号】计算而来(连接字符串),当编辑款号/色号时,目标文件名能实时变化。

熟悉dgv的猿友都知道,如果不做特别处理,是达不到上述效果的。原因是dgv默认是等焦点离开编辑单元格(CurrentCell),才会提交更改到数据源,而且就算焦点离开,但如果焦点仍在同一行(即CurrentCell改变,但CurrentRow没变)的话,该行的源行也仍然处在编辑状态(DataRowView.IsEdit为true),计算列也同样不会更新。非得是焦点离开这一行(去到别的行,或者其它控件),计算列才会更新。——这段话信息量略大,不熟悉dgv提交机制的猿友可能得借助下面进一步的说明才能明白~老鸟请绕道。先认识几个概念:

  • dgv单元格:DataGridViewCell
  • dgv行:DataGridViewRow
  • dgv行的源行:DataRowView。当dgv绑定数据源后,它的每一行就对应了数据源中的一行(或叫一项),这就是我所谓的【源行】。可以通过DataGridViewRow.DataBoundItem属性获得,该属性类型是object,当dgv的数据源为DataTable或DataView(下称dv)时,DataBoundItem的真实类型就是DataRowView,可以理解为DataView的行。而dv又是根据dt来的,所以dv背后又对应一个dt,所以DataRowView背后也对应一个DataRow,可通过DataRowView.Row获得该DataRow。简单表示就是,DataGridViewRow(访问DataBoundItem属性)→DataRowView(访问Row属性)→DataRow
  • dgv有单元格的概念和实体类(DataGridViewCell),但dt和dv没有,后者只到行这一级,虽然可以通过DataRow[x]或DataRowView[x]访问单元格的值,但在类层级上并不存在DataCell这样的表示单元格的实体类,也就是dt和dv的编辑/提交等操作是以【行】为单元

下面是dgv的常规提交流程:

①编辑dgv单元格→②完成编辑(离开焦点)→③提交数据源(源行仍处于编辑状态)→④焦点离开dgv行→⑤源行结束编辑状态→⑥源行更新计算列(其实完整流程还包括别的环节,比如单元格数据验证,但这里只说与提交直接相关的环节)。

可以看到,计算列得到更新的关键有两处:

  1. dgv单元格的数据要提交到数据源相应单元格
  2. 源行结束编辑状态

按常规提交流程,必须使焦点离开单元格所在的行(只离开单元格都不行哦)才能达到目的,而我们的需求是,编辑的过程中就要实时更新,不要说离开行,连单元格都不想离开。

一、解决实时更新计算列的问题

可以通过dgv的CurrentCellDirtyStateChanged事件达到目的:

private void dgv_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
//判断当前单元格是否存在未提交的更改,只有存在才继续。
//此判断有必要,因为下面的dgv.CommitEdit也会触发该事件,但此时IsCurrentCellDirty已为false,
//如果不做判断,将会重复进入,造成无谓消耗
if (dgv.IsCurrentCellDirty)
{
//将单元格值提交给数据源,dgv.EndEdit()也能做到提交,但那样会使单元格结束编辑状态
//而dgv.CommitEdit()则会保持编辑状态
//参数是提供给DataError等事件的原因
dgv.CommitEdit(DataGridViewDataErrorContexts.Commit); //人工结束源行的编辑状态。只有这样,源行的计算列才会更新
(dgv.CurrentRow.DataBoundItem as DataRowView).EndEdit(); //或者执行DataRow的EndEdit()也能达到同样目的
//(dgv.CurrentRow.DataBoundItem as DataRowView).Row.EndEdit();
}
}

通过这个事件做了上面要做的两个事,即①将dgv单元格值更新到数据源;②结束源行编辑状态。按说到这里就搞掂了,事实上也的确能使计算列实时反映输入,但却存在另一个体验层面的问题,就是单元格会在每次键入后内容全选,如图:

也就是如果要连续输入,必须在每次输入后用鼠标或方向键取消全选并将光标定位到正确的位置~这不蛋疼吗,必须解决!首先为什么会全选的原因不明,我猜是由于数据源的更新反过来影响dgv所致。尝试过用CellEnter、CellBeginEdit、EditingControlShowing、dgv.EditingControl等东西都不理想,不是根本没用,就是输入焦点不对,总之着实折腾了一番,最后总算另辟蹊径,完美解决。

二、解决键入后自动全选的问题

我是从控件消息这块打的主意,dgv的单元格实际上承载了某种编辑控件(如TextBox,CheckBox),所以甭管它是什么原因全选,最后总该是收到了什么消息它才全选,那么我就用spy++截获消息,果然有发现:

粗略一看,是EM_SETSEL,经过了解,就是EM_SETSEL,所以接下来要做的就是自定义一个文本编辑控件,让它忽略这个消息,完了让这个控件成为dgv单元格中的文本编辑控件。了解一番,有如下套路:

  1. 编写承载控件。需继承基础控件,并实现System.Windows.Forms.IDataGridViewEditingControl接口。由于我只是想屏蔽现有控件的某个消息,并不是要从头编写功能控件,所以直接继承DataGridViewCell承载的文本框控件DataGridViewTextBoxEditingControl即可,因为该控件已经实现上述接口:

    public class DataGridViewTextBoxUnSelectableEditingControl : DataGridViewTextBoxEditingControl
    {
    protected override void WndProc(ref Message m)
    {
    //EM_SETSEL消息的常量是0xb1
    if (m.Msg == 0xb1) { return; } base.WndProc(ref m);
    }
    }
  2. 编写承载上述控件的DataGridViewCell。需继承自DataGridViewCell或其子类。同样,本例我只需继承自DataGridViewTextBoxCell即可:
    public class DataGridViewTextBoxUnSelectableCell : DataGridViewTextBoxCell
    {
    //仅需重写该属性,指明承载的控件类型即可
    public override Type EditType
    {
    get
    {
    return typeof(DataGridViewTextBoxUnSelectableEditingControl);
    }
    }
    }
  3. 设置要使用上述单元格的dgv列(DataGridViewColumn)的CellTemplate属性,为上述单元格的实例,多个列可以设为同一实例。CellTemplate最好尽早设置,比如在窗体构造函数中,紧跟InitializeComponent()方法设置;
    InitializeComponent();
    
    var cell = new DataGridViewTextBoxUnSelectableCell();
    dgv.Columns[].CellTemplate = cell;//将要使用特殊单元格的列的CellTemplate指定为单元格实例
    dgv.Columns[].CellTemplate = cell;//多个列可以共用一个实例
    ...

对于本例而言,做完上述工作即可解决dgv单元格全选的问题。完整的自定义单元格控件的套路请自行参考MSDN

应猿友要求,放上demo:http://pan.baidu.com/s/1qWzKf60

-文毕-

【C#】让DataGridView输入中实时更新数据源中的计算列的更多相关文章

  1. iOS UITextView 输入内容实时更新cell的高度

    iOS UITextView 输入内容实时更新cell的高度 2014-12-26 11:37 编辑: suiling 分类:iOS开发 来源:Vito Zhang'blog  11 4741 UIT ...

  2. MFC For循环中实时更新显示Edit内容

    在for(){}循环中如果有处理函数,然后需要显示的时候,简单的UpdateData(false);是不行的: for (int i=0;i<10000;i++) { m_nT1.Format( ...

  3. 安卓端网页浏览过程中实时更新title的web实现

    $(function () { var scrollTop = 0, //缓存上一次触发scroll的时候的scrollTop值 appendIndex = 0, //由于第23行append这个操作 ...

  4. Dev GridControl数据修改后实时更新数据源

      1:  /// <summary> 2:  /// 嵌入的ComboxEdit控件选择值变化事件 3:  /// </summary> 4: /// <param n ...

  5. Dev GridControl数据修改后实时更新数据源(转)

    1:  /// <summary> 2:  /// 嵌入的ComboxEdit控件选择值变化事件 3:  /// </summary> 4: /// <param nam ...

  6. WebSocket 实时更新mysql数据到页面

    使用websocket的初衷是,要实时更新mysql中的报警信息到web页面显示 没怎么碰过web,代码写的是真烂,不过也算是功能实现了,放在这里也是鞭策自己,web也要多下些功夫 准备 引入依赖 & ...

  7. SqlServer中计算列详解

    计算列区别于需要我们手动或者程序给予赋值的列,它的值来源于该表中其它列的计算值.比如,一个表中包含有数量列Number与单价列Price,我们就可以创建计算列金额Amount来表示数量*单价的结果值, ...

  8. 【SqlServer】SqlServer中的计算列

    计算列区别于需要我们手动或者程序给予赋值的列,它的值来源于该表中其它列的计算值.比如,一个表中包含有数量列Number与单价列Price,我们就可以创建计算列金额Amount来表示数量*单价的结果值, ...

  9. python---django中form组件(2)自定制属性以及表单的各种验证,以及数据源的实时更新,以及和数据库关联使用ModelForm和元类

    自定义属性以及各种验证 分析widget: class TestForm(forms.Form): user = fields.CharField( required = True, widget = ...

随机推荐

  1. 【译】Activitys, Threads和 内存泄露

    Android编程中一个共同的困难就是协调Activity的生命周期和长时间运行的任务(task),并且要避免可能的内存泄露.思考下面Activity的代码,在它启动的时候开启一个线程并循环执行任务. ...

  2. C#Light Unity逻辑热更新解决方案0.20 发布

    之前一直是Beta,这次已经实际运用到项目中间了,去掉beta状态 在项目中使用面对一些新的问题,还有以前没注意的bug. 更新列表 一.增加类中类的支持 二.增加对foreach的支持,同C#语法 ...

  3. 使用ViwePager显示图片时如何防止内存泄露。

    内存泄露的检测. 1. 在Android Studio中运行你的应用,然后切换到输出窗口的Android tab. 2. 尽情的玩耍你的应用,最好各个功能都用到,如果是Viewpager,则多滑动一些 ...

  4. 如何成为一个Xamarin专家

    近期,我们发布了 Xamarin studio 6,这个版本充满了美妙的新特性,能够更有效的帮助我们的开发工作.由于其深层次的 IDE 比较复杂,同时我们也很难去发现并记得那些对我们最有帮助的特性,所 ...

  5. 在同一台服务器上配置多个Tomcat

    如果要在一台服务器上配置多个Tomcat,主要就是要避免Tomcat服务器的端口冲突的问题.只需要修改CATALINA_HOME\conf\server.xml中的启动端口和连接端口就OK了! 下面我 ...

  6. tomcat4 请求的处理——初步分析

    以tomcat4为例, 每当HttpConnector的ServerSocket得到客户端的连接时,会创建一个Socket. 接下来就处理这个Socket发来的数据. 怎么处理呢? 考虑到客户端同时发 ...

  7. 每天一个linux命令(56):netstat命令

    netstat命令​用于显示与IP.TCP.UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况.netstat是在内核中访问网络及相关信息的程序,它能提供TCP连接,TCP和UD ...

  8. 知方可补不足~SQL中的count命令的一些优化措施(百万以上数据明显)

    回到目录 SQL中对于求表记录总数的有count这个聚合命令,这个命令给我们感觉就是快,比一般的查询要快,但是,当你的数据表记录比较多时,如百万条,千万条时,对于count来说,就不是那么快了,我们需 ...

  9. Fiddler实战深入研究(二)

    Fiddler实战深入研究(二) 阅读目录 Fiddler不能捕获chrome的session的设置 理解数据包统计 请求重定向(AutoResponder) Composer选项卡 Filters选 ...

  10. Atitit attilax在自然语言处理领域的成果

    Atitit attilax在自然语言处理领域的成果 1.1. 完整的自然语言架构方案(词汇,语法,文字的选型与搭配)1 1.2. 中文分词1 1.3. 全文检索1 1.4. 中文 阿拉伯文 英文的简 ...