Delphi迁移:TcxGrid的EditValueChanged事件处理用户编辑异常问题
迁移Delphi 6/TdxDBGrid至Delphi 2009/TcxGrid的字段编辑问题解决
问题背景
原Delphi 6项目中使用TdxDBGrid,通过网格级OnEdited事件为编辑后的记录设置IS_MODIFIED_BY_USER属性,该实现仅响应用户在网格中的编辑操作,数据模块修改记录时该属性保持不变,且对网格中任意字段的编辑均有效。
迁移至Delphi 2009/TcxGrid后,尝试使用网格级OnEditValueChanged或列编辑器的Properties.OnEditValueChanged实现相同功能时出现问题:修改当前字段时同步修改其他字段(如修改AMOUNT字段时为AMOUNT_2赋值),按下Enter键后当前编辑的AMOUNT值会回滚至初始值,无论通过DataSet还是网格编辑器赋值均无效。
其他替代方案存在不足:
TDataSource.OnUpdateData仅在Post时触发,无法满足编辑完成后立即设置属性的需求;TClientDataSet字段的OnChange事件会响应所有数据变更,需通过标记区分系统与用户修改,实现复杂。
解决方案
核心问题是在编辑器的OnEditValueChanged事件中直接修改DataSet会打乱TcxGrid的编辑状态,导致当前字段值未被正确提交。以下两种方法可解决该问题:
方法1:使用TcxGridDBTableView的OnValidate事件
该事件在用户完成单元格编辑(如按下Enter、切换单元格)时触发,此时当前字段值已提交至DataSet,修改其他字段不会导致回滚。
修改代码示例:
// 先在窗体类中添加事件声明 type TMainForm = class(TForm) // ... 已有组件声明 ... procedure DataGridDBTableViewValidate(Sender: TcxCustomGridTableView; ARecord: TcxCustomGridRecord; var ValidateRecord: Boolean); private { Private declarations } public { Public declarations } end; // 实现事件 procedure TMainForm.DataGridDBTableViewValidate(Sender: TcxCustomGridTableView; ARecord: TcxCustomGridRecord; var ValidateRecord: Boolean); begin // 为当前编辑的记录设置标记字段 CDS.FieldByName('IS_MODIFIED_BY_USER').AsInteger := 1; // 若需针对特定列的编辑操作做处理 if ARecord.FocusedColumn = DataGridDBTableViewAMOUNT then CDSAMOUNT_2.AsFloat := 500; end;
在DFM中为DataGridDBTableView绑定OnValidate事件:
object DataGridDBTableView: TcxGridDBTableView // ... 已有属性 ... OnValidate = DataGridDBTableViewValidate // ... 列定义 ... end;
方法2:在OnEditValueChanged中先提交当前编辑值
若需在用户输入过程中实时修改其他字段,需先调用网格的DataController.PostEdit方法提交当前编辑值,再修改目标字段。
修改事件实现代码:
procedure TMainForm.DataGridDBTableViewAMOUNTPropertiesEditValueChanged( Sender: TObject); var LGridSite: TcxGridSite; LTableView: TcxGridDBTableView; begin LGridSite := TcxGridSite(TcxCustomEdit(Sender).Owner); LTableView := TcxGridDBTableView(LGridSite.GridView); // 提交当前编辑的字段值到DataSet LTableView.DataController.PostEdit; // 修改其他字段,此时不会导致当前字段回滚 CDSAMOUNT_2.AsFloat := 500; end;
复现问题的测试代码
PAS代码
unit MainFormU; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, DB, DBClient, StdCtrls, cxStyles, dxSkinsCore, dxSkinBlack, dxSkinBlue, dxSkinCaramel, dxSkinCoffee, dxSkinDarkSide, dxSkinGlassOceans, dxSkiniMaginary, dxSkinLilian, dxSkinLiquidSky, dxSkinLondonLiquidSky, dxSkinMcSkin, dxSkinMoneyTwins, dxSkinOffice2007Black, dxSkinOffice2007Blue, dxSkinOffice2007Green, dxSkinOffice2007Pink, dxSkinOffice2007Silver, dxSkinPumpkin, dxSkinSilver, dxSkinSpringTime, dxSkinStardust, dxSkinSummer2008, dxSkinsDefaultPainters, dxSkinValentine, dxSkinXmas2008Blue, dxSkinscxPCPainter, cxCustomData, cxGraphics, cxFilter, cxData, cxDataStorage, cxEdit, cxDBData, cxGridCustomTableView, cxGridTableView, cxGridDBTableView, cxGridLevel, cxClasses, cxControls, cxGridCustomView, cxGrid, cxMaskEdit; type TMainForm = class(TForm) SetupBtn: TButton; CDS: TClientDataSet; DS: TDataSource; CDSID: TIntegerField; CDSAMOUNT: TFloatField; CDSAMOUNT_2: TFloatField; DataGridDBTableView: TcxGridDBTableView; DataGridLevel: TcxGridLevel; DataGrid: TcxGrid; DataGridDBTableViewID: TcxGridDBColumn; DataGridDBTableViewAMOUNT: TcxGridDBColumn; DataGridDBTableViewAMOUNT_2: TcxGridDBColumn; procedure SetupBtnClick(Sender: TObject); procedure DataGridDBTableViewAMOUNTPropertiesEditValueChanged( Sender: TObject); private { Private declarations } public { Public declarations } end; var MainForm: TMainForm; implementation {$R *.dfm} procedure TMainForm.DataGridDBTableViewAMOUNTPropertiesEditValueChanged( Sender: TObject); begin CDSAMOUNT_2.AsFloat:=500; //DataGridDBTableViewAMOUNT_2.EditValue:=2.0*DataGridDBTableViewAMOUNT.EditValue; end; procedure TMainForm.SetupBtnClick(Sender: TObject); var i: Integer; begin if CDS.Active then CDS.EmptyDataSet else CDS.CreateDataSet; DataGridDBTableView.BeginUpdate; try for i := 0 to 100 do begin CDS.Append; CDSID.AsInteger:=i; CDSAMOUNT.AsFloat:=i*100.11; CDSAMOUNT_2.AsFloat:=i*1000.11; CDS.Post; end; finally DataGridDBTableView.EndUpdate; end; end; end.
DFM代码
object MainForm: TMainForm Left = 0 Top = 0 Caption = 'MainForm' ClientHeight = 400 ClientWidth = 784 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False PixelsPerInch = 96 TextHeight = 13 object SetupBtn: TButton Left = 288 Top = 351 Width = 75 Height = 25 Caption = 'SetupBtn' TabOrder = 0 OnClick = SetupBtnClick end object DataGrid: TcxGrid Left = 8 Top = 8 Width = 761 Height = 266 TabOrder = 1 object DataGridDBTableView: TcxGridDBTableView NavigatorButtons.ConfirmDelete = False DataController.DataSource = DS DataController.Summary.DefaultGroupSummaryItems = <> DataController.Summary.FooterSummaryItems = <> DataController.Summary.SummaryGroups = <> object DataGridDBTableViewID: TcxGridDBColumn DataBinding.FieldName = 'ID' PropertiesClassName = 'TcxMaskEditProperties' Width = 154 end object DataGridDBTableViewAMOUNT: TcxGridDBColumn DataBinding.FieldName = 'AMOUNT' PropertiesClassName = 'TcxMaskEditProperties' Properties.OnEditValueChanged = DataGridDBTableViewAMOUNTPropertiesEditValueChanged Width = 220 end object DataGridDBTableViewAMOUNT_2: TcxGridDBColumn DataBinding.FieldName = 'AMOUNT_2' PropertiesClassName = 'TcxMaskEditProperties' Width = 156 end end object DataGridLevel: TcxGridLevel GridView = DataGridDBTableView end end object CDS: TClientDataSet Aggregates = <> Params = <> Left = 512 Top = 336 object CDSID: TIntegerField FieldName = 'ID' end object CDSAMOUNT: TFloatField FieldName = 'AMOUNT' end object CDSAMOUNT_2: TFloatField FieldName = 'AMOUNT_2' end end object DS: TDataSource DataSet = CDS Left = 424 Top = 344 end end
内容的提问来源于stack exchange,提问作者TomR
相关产品推荐
相关产品推荐

