You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 01:14:51