本文适用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. 在.net中使用aquiles访问Cassandra(二)

    上文中我们已经建立了项目的基本结构,今天实现数据的修改.在NoSQL中,通常添加和修改都认为是对数据的一种Mutation.   1.建立描述修改Row的实体. public class RowMut ...

  2. 用python实现的百度新歌榜、热歌榜下载器

    首先声明,本工具仅仅为学习之用,不涉及版权问题,因为百度音乐里面的歌曲本身是可以下载的,而且现在百度也提供了”百度音乐播放器”,可以通过这个工具进行批量下载. 我当时做这个工具的时候,百度还没有提供” ...

  3. 第二章 深入 C# 数据类型

    第二章 深入 C# 数据类型 1.封装又称信息隐藏,是指利用抽象数据类型将数据和数据的操作结合在一起,使其构成一个不可分割的独立实体,尽可能的隐藏内部的细节,只保留一些对外接口,使之于外部发生联系. ...

  4. 我心中的核心组件(可插拔的AOP)~第五回 消息组件

    回到目录 之所以把发消息拿出来,完全是因为微软的orchard项目,在这个项目里,将公用的与领域无关的功能模块进行抽象,形成了一个个的组件,这些组件通过引用和注入的方式进行工作,感觉对于应用程序的扩展 ...

  5. instanceof, typeof, & Object.prototype.toString

    /** * * @authors Your Name (you@example.org) * @date 2016-11-18 09:31:23 * @version $Id$ */instanceo ...

  6. javascript_basic_02之数据类型、分支结构

    1.弱类型:声明无需指定数据类型,由值决定,查看变量数据类型:typeof(变量): 2.隐式转换:任何数据类型与string类型相加,结果为string类型: 3.显式(强制)转换: ①toStri ...

  7. 23.实现一个名为Person的类和它的子类Employee,Employee有两个子类Faculty 和Staff。 具体要求如下: (1)Person类中的属性有:姓名name(String类型),地址address(String类型), 电话号码telphone(String类型)和电子邮件地址email(String类型); (2)Employee类中的属性有:办公室office(Stri

    package banking; public class Person { private String name; public String address; public String tel ...

  8. iOS越狱开发(一)

    做越狱开发也有一些时间了,有很多东西想总结一下,希望给他人一些借鉴,也是自己对过去开发经历的一些总结.个人不推荐使用盗版,这里主要以技术介绍为主. 这个系列里面主要介绍怎样进行越狱开发,涉及到以下几个 ...

  9. CSS布局中的问题

    1. 高度自适应的问题 1. 一个元素是否可以使用百分比显示,取决于它的父级元素,所以如果需要给此元素设置100%,那么需要先给父元素设置高100%. 2. 一级元素的父元素是body,所以如果我们想 ...

  10. WPF自定义控件与样式(9)-树控件TreeView与菜单Menu-ContextMenu

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 菜单M ...