【C#】让DataGridView输入中实时更新数据源中的计算列
本文适用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行→⑤源行结束编辑状态→⑥源行更新计算列(其实完整流程还包括别的环节,比如单元格数据验证,但这里只说与提交直接相关的环节)。
可以看到,计算列得到更新的关键有两处:
- dgv单元格的数据要提交到数据源相应单元格
- 源行结束编辑状态
按常规提交流程,必须使焦点离开单元格所在的行(只离开单元格都不行哦)才能达到目的,而我们的需求是,编辑的过程中就要实时更新,不要说离开行,连单元格都不想离开。
一、解决实时更新计算列的问题
可以通过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单元格中的文本编辑控件。了解一番,有如下套路:
- 编写承载控件。需继承基础控件,并实现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);
}
} - 编写承载上述控件的DataGridViewCell。需继承自DataGridViewCell或其子类。同样,本例我只需继承自DataGridViewTextBoxCell即可:
public class DataGridViewTextBoxUnSelectableCell : DataGridViewTextBoxCell
{
//仅需重写该属性,指明承载的控件类型即可
public override Type EditType
{
get
{
return typeof(DataGridViewTextBoxUnSelectableEditingControl);
}
}
} - 设置要使用上述单元格的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输入中实时更新数据源中的计算列的更多相关文章
- iOS UITextView 输入内容实时更新cell的高度
iOS UITextView 输入内容实时更新cell的高度 2014-12-26 11:37 编辑: suiling 分类:iOS开发 来源:Vito Zhang'blog 11 4741 UIT ...
- MFC For循环中实时更新显示Edit内容
在for(){}循环中如果有处理函数,然后需要显示的时候,简单的UpdateData(false);是不行的: for (int i=0;i<10000;i++) { m_nT1.Format( ...
- 安卓端网页浏览过程中实时更新title的web实现
$(function () { var scrollTop = 0, //缓存上一次触发scroll的时候的scrollTop值 appendIndex = 0, //由于第23行append这个操作 ...
- Dev GridControl数据修改后实时更新数据源
1: /// <summary> 2: /// 嵌入的ComboxEdit控件选择值变化事件 3: /// </summary> 4: /// <param n ...
- Dev GridControl数据修改后实时更新数据源(转)
1: /// <summary> 2: /// 嵌入的ComboxEdit控件选择值变化事件 3: /// </summary> 4: /// <param nam ...
- WebSocket 实时更新mysql数据到页面
使用websocket的初衷是,要实时更新mysql中的报警信息到web页面显示 没怎么碰过web,代码写的是真烂,不过也算是功能实现了,放在这里也是鞭策自己,web也要多下些功夫 准备 引入依赖 & ...
- SqlServer中计算列详解
计算列区别于需要我们手动或者程序给予赋值的列,它的值来源于该表中其它列的计算值.比如,一个表中包含有数量列Number与单价列Price,我们就可以创建计算列金额Amount来表示数量*单价的结果值, ...
- 【SqlServer】SqlServer中的计算列
计算列区别于需要我们手动或者程序给予赋值的列,它的值来源于该表中其它列的计算值.比如,一个表中包含有数量列Number与单价列Price,我们就可以创建计算列金额Amount来表示数量*单价的结果值, ...
- python---django中form组件(2)自定制属性以及表单的各种验证,以及数据源的实时更新,以及和数据库关联使用ModelForm和元类
自定义属性以及各种验证 分析widget: class TestForm(forms.Form): user = fields.CharField( required = True, widget = ...
随机推荐
- windows 8.1 试用感受:蛋疼感大幅降低
众所周知windows 8 的最大使用感受就是蛋疼. 无论是微软MVP,还是我这样的万年不悔微软小白鼠,普通用户,小白用户,或多或少的都对这款操作系统感到蛋疼. 槽点太多,以至于大家都懒得批判了.好在 ...
- [ZigBee] 5、ZigBee基础实验——图文与代码详解定时器1(16位定时器)(长文)
1.定时器1概述 定时器1 是一个支持典型的定时/计数功能的独立16 位定时器,支持输入捕获,输出比较和PWM等功能.定时器有五个独立的捕获/比较通道.每个通道定时器要使用一个I/O 引脚.定时器用于 ...
- 3、CC2541芯片中级教程-OSAL操作系统(ADC光敏电阻和修改串口波特率)
本文根据一周CC2541笔记汇总得来—— 适合概览和知识快速索引—— 全部链接: 中级教程-OSAL操作系统\OSAL操作系统-实验01 OSAL初探 [插入]SourceInsight-工程建立方法 ...
- 爱上MVC3系列~监视Action的运行时间,并提供超时记录机制
回到目录 文章出现的原因 很久没写关于MVC的文章了,原因是将关注点移向了MVVM和DDD这边,而这篇文章完全是因为公司项目的需要,因为公司网站总是不定时的502,而这由可能是程序超时所引起的,为了分 ...
- Java程序员的日常—— POI与JDBC、Mockmvc与单元测试
周日没怎么休息好,周一一天都迷迷糊糊的,不过还算是干了不少的活. 总结一下,大致有以下几点内容: 1 使用poi以及mysql jdbc实现了一个复杂excel的导入 2 基于工程原有的代码,书写sp ...
- iOS-ARC项目使用非ARC文件 MRC项目使用ARC文件
SDK4.0引入了ARC,到现在已经好几年了,开始发现有很多项目会混合使用这两个方案.比如: 1.自己的旧项目没有使用ARC,但是引入的第三方库却是使用了ARC的. 2.自己的新项目使用了ARC,但是 ...
- Oracle 11g系列:数据库
1.创建Oracle数据库 创建Oracle数据库的最常用工具为Database Configuration Assistant(数据库配置助手),依次选择[开始]|[所有程序]|[Oracle-Or ...
- 编写Shader时的一些性能考虑
编写shader时的一些建议:1.只计算需要计算的东西:2.通常,需要渲染的像素比顶点数多,而顶点数又比物体数多很多.所以如果可以,尽量将运算从PS移到VS,或直接通过script来设置某些固定值:3 ...
- python单线程爬虫code
广度优先算法: # -*- coding: utf-8 -*- import urllib import urllib.request from bs4 import BeautifulSoup im ...
- SQLExecption:Operation not allowed after ResultSet closed解决办法
原网址:http://blog.csdn.net/sku0923/article/details/1722370 一个stmt多个rs进行操作引起的ResultSet已经关闭错误 一个stmt多个rs ...