都是我最近碰到的一些编程细节,有些内容有主观推断的成分,不一定正确。

BindingSource会自动临时挂起约束?

如果DGV直接以数据表(DataTable)为数据源,编辑行时可能会很麻烦:你刚编辑完一个单元格,还没有离开当前行,只是移到当前行的另一单元格,就会得到一个DataError。使用BindingSource作为DGVDataTable的中间层可以解决这个问题,它能保证只要没有离开当前行,就不启用约束检查。

编辑DGV的行时,如果数据完整性被破坏,如何撤消事务?

DGVDataError事件中只能取消当前单元格的编辑,不能在行级别上取消编辑。所以这里存在一个潜在的问题:连续编辑了一行中的多个单元格后想放弃修改怎么办?我现在知道的一个方案是使用DataTableRejectChanges方法,DataTable通过AcceptChangesRejectChanges方法实现对事务的支持。这个方案要求在DGVRowValidated事件中调用DataTableAcceptChanges方法。

DGV在显示之前不会加载数据?

新创建的DGV在设置好数据源后不会立即加载数据——即它的列集合和行集合仍为空,当它显示(由于被添加到某个Control.Controls 中或被设置了Parent 属性) 时,才可以通过它的Rows 属性访问数据。这个特性似乎是在强调:DGV是个不折不扣的UI,在内存中使用不是它的初衷。

如何向包含自动递增列的数据表中添加数据?

可以使用 DataRow 类的 ItemArray 属性并传入值的数组,来创建新行。对于其 AutoIncrement 设置为 true 的列,这是一个潜在的问题,因为其值是自动生成的。若要使用 ItemArray 属性,请将空引用(在 Visual Basic 中为 Nothing)放入数组中的列的位置。在 ItemArray 中传递空引用(在 Visual Basic 中为 Nothing) 指示未指定任何值。

假设DataDataSet,其中名为TABLE_GROUPS的数据表包含名称分别为COLUMN_GROUP_IDCOLUMN_GROUP_NAME的两列,COLUMN_GROUP_ID列为自动递增列。

下面的代码是正确的:

            Data.Tables[TABLE_GROUPS].Rows.Add(null, DBNull.Value);

下面的代码会导致ArgumentException:不能将列“GroupId”设置为空。请改用DBNull

            Data.Tables[TABLE_GROUPS].Rows.Add(DBNull.Value, DBNull.Value);

下面的代码会失败,语句1导致ArgumentException:不能将列“GroupId”设置为空。请改用DBNull

            DataRow newRow = Data.Tables[TABLE_GROUPS].NewRow();

            newRow[COLUMN_GROUP_ID] = null; // 语句1

            newRow[COLUMN_GROUP_NAME] = "group1";

            Data.Tables[TABLE_GROUPS].Rows.Add(newRow);

下面的代码也会失败,语句2语句1导致ArgumentException:不能将列“GroupId”设置为空。请改用DBNull

            DataRow newRow = Data.Tables[TABLE_GROUPS].NewRow();

            newRow[COLUMN_GROUP_ID] = DBNull.Value; // 语句2

            newRow[COLUMN_GROUP_NAME] = "group1";

            Data.Tables[TABLE_GROUPS].Rows.Add(newRow);

P.S. 从这里可以看出nullDBNull的一点区别。前者表示绝对的,后者则表示空值”——仍然是一个null不能作为“值”赋给字段(语句1),DBNull则可以。数据表中的值是不可能为null的,在DGV中编辑单元格时,空串意味着DBNull

DGV单元格输入空值会怎样?

如果在单元格中输入空内容,则在CellValidating事件中,DataGridViewCellValidatingEventArgs FormattedValue属性的值是空字符串,CellValidated事件中,单元格的值为null。如果DGV已绑定到DataTable,则此单元格向数据表输入的值为DBNull

复杂情况下慎用组合框列(DataGridViewComboBoxColumn

我遇到的情况是:

1.       DGV绑定到数据源。

2.       某列中的每个单元格都可能具有自己的值列表,取决于所属行的其它单元格的值,就是说当它依赖的单元格的值发生变化时,它的值列表需要调整。

开始时我使用组合框列,碰到的最大的麻烦是单元格的值经常不在它的值列表之内,这会导致异常,说“单元格的值无效。”由于DGV绑定到数据源的机制,有些时机下,情形会复杂到难以控制。当数据源与DGV显示的差异比较大,运行时需要一些代码逻辑实现数据格式化时尤其如此。大量的事件,单独考虑都容易使用,但要在好几个事件中执行相关的几个任务或一个任务的不同阶段,事件的协同变得很困难。这种情况下调试,仿佛自己是个疲于应付的农场主,怎么修理栅栏也不能防止一大群狡猾的野兔溜进来一两个。

DataColumn.Expression的取消

设置数据列的表达式时,其只读属性被自动置为真,所以要取消表达式,除了将数据列的表达式属性置为空串,一般还需要将其只读属性置为假。

DataColumn. ExtendedProperties序列化后

类型为PropertyCollection,派生自HashTable。可以储任何类型的值,但以XML的形式反序列化后,所有项目的数据类型均为字符串。MSDN对此的说明是“扩展属性必须是 String 类型。当以 XML 的形式写 DataColumn 时,不保持非 String 类型的属性。”未见对HashTable有类似说明。

区分UI和数据的只读属性

一般来说UI的只读属性限制的是用户而非程序员,即一个控件是只读的,意味着在运行时用户不能编辑,但控件属性(比如Value)仍可通过代码修改。

而对于DataColumn来说,只读属性的意义与C#中的readonly关键字的意义接近,如果DataColumn是只读的,意味着行一旦被添加到表中,列就不能再更改,即使通过代码也不行,但是初始化是可以的。这个特性正是自动递增列和表达式列需要的。

不使用 BindingSource 作为控件和基础数据源的中间层也行,但是 BindingSource 在很多时候非常有价值,这里只展示了一个很小的方面。

1. ListControl.DataSource 属性

获取或设置此 ListControl 的数据源。实现 IList 或 IListSource 接口的对象可以作为数据源,如 DataSet 或 Array。

假设现在有两个对象:List<T> 的 mylist,ListBox 类型的 listBox1。下面这条语句可以使 mylist 的内容显示到 listBox1 中(方法一):

listBox1.DataSource = mylist;

2. BindingSource 类

封装窗体的数据源。BindingSoiurce 实现的接口有:Component, IBindingListView, IBindingList, IList, ICollection, IEnumerable, ITypedList, ICancelAddNew, ISupportInitializeNotification, ISupportInitialize, ICurrencyManagerProvider。

假设现在还有一个 BindingSource 对象 bsrc ,下面的语句也可以使 mylist 的内容显示到 listBox1 中(方法二):

bsrc = new BindingSource(mylist, null);
listBox1.DataSource = bsrc;

3. 为什么需要 BindingSource ?

先看看 BindingSource 除了让控件知道从哪儿读取数据,还能做什么:

它通过提供一个间接寻址层、当前项管理、更改通知和其他服务简化了窗体中控件到数据的绑定。这是通过将 BindingSource 组件附加到数据源然后将窗体中的控件绑定到 BindingSource 组件来实现的。与数据的所有进一步交互,包括定位、排序、筛选和更新,都通过调用 BindingSource 组件实现。
值得注意的最后一句话,我在《对象引用的保护措施》中提出的“包装类”方案就是想让包装类做到这样。

总之,BindingSource 可以监视(直观地说是“监视”,本质上是“代理”)数据源的变化。在上面的方法一中,listBox1 不能跟踪 mylist 的变化,比如由这条语句引起的数据变更:

mylist.RemoveAt(3);
如果要让 listBox1 反应 mylist 的最新情况,需要:

listBox1.DataSource = null;
listBox1.DataSource = mylist;
或其它可以使 listBox1 与 mylist "从头开始"的语句。

tag: .Net 数据绑定(DataBinding) DGV(DataGridView) 新行(NewRow)

我目前所知的方法是直接向数据源添加一个新项目,然后让DGV重新加载数据源。

下面的内容基本上是啰嗦的废话。

这个问题解决起来很简单,之所以把它当个事,是因为我碰到过这种情形:我限定用户只能通过间接方式在新行中输入数据,而DGV又绑定到了数据源,于是,如果不采取一些措施的话,用户会发现他在新行输入了数据后,新的“新行”却没有出现。

顺便指出一下,
1,所谓间接的输入方式是指除键盘、鼠标之外的输入方式,比如通过一个对话框执行对新行的值的编辑;
2,第一条中所谓对新行的编辑指的是对DGV的新行的单元格的值的修改,而非对基础数据源中的新项目的修改,因为在DGV的新行提交之前基础数据源不会添加项目。

存在两个既定事实:
1,通过代码修改DGV新行中单元格的值不会导致DGV自动添加行;
2,DGV绑定到数据源后,试图在代码中通过DataGridViewRowCollection.Add方法添加新行会导致类型为InvalidOperationException的异常。

考虑到DGV绑定到数据源后,就是数据驱动的了,所以简单的解决办法是在编辑完成后通过代码绕过DGV,直接向数据源添加一个新项目,然后让DGV重新加载数据源。例如,如果DGV的数据源是BindingSource,BindingSource的数据源是DataTable,则第一步可以通过DataTable.Rows.Add方法实现,第二步可以通过BindingSource.ResetBindings方法实现。

按MSDN的说法,“BindingSource.Add 方法向基础列表添加新项。”所以第一步貌似也可以通过BindingSource.AddNew方法实现,但是至少当基础数据源为DataTable时,这行不通:新行确实会自动添加,不过刚才的编辑不会被提交。我估计原因是BindingSource.AddNew方法除了向基础列表添加新项,还自动做了一些别的事,而正是这些额外的动作导致刚刚在DGV的新行中输入的数据被丢弃了。

2007-08-24

自言自语

我喜欢五角星,匡威的帅气,东方卫视的时尚,歌特饰物的神秘。

《双枪李向阳》挺好看,尤其是那个渡边队长,一个嗷嗷叫的搞笑猪头。我唯一不安的是,在这个电视剧里,中国人和日本人的关系过于暧昧了。如果不考虑史实,单看电视剧,大佐和渡边一点不可恨,倒是可爱的狠。这个,妥当吗?如果只是想拍一部喜剧,可以写土匪啊。中国人对待抗日题材时,是不是该严肃点呢。

每个人都知道即使他把内裤穿在外面,别人也不会认为他是超人。然而却有些人以为他学了一两样所谓的贵族运动,就会有人当他是贵族。

最近每天早上,都会被不知谁家的孩子吵醒,用钢琴吵的。以我十分外行的耳朵听来,这娃儿没天赋。我觉得这个逻辑很可笑,你家孩子三岁时摸过十八般兵器,十八岁时就是大侠?

喜剧的话,我喜欢搞笑的笨蛋,不喜欢聪明的幽默。

在万晓利的空间里,我学到“嗷”字几个很好玩的用法:嗷!嗷也,嗷买糕的。之前我只会说谁又嗷嗷大叫,这会儿觉得,中文真是妙不可言,呵呵,吼吼,嚯嚯,嗷~~

2007-08-24

大名徐君萍