Use of EditValueChanged (of TcxGrid) for catching and handling user performed edits?

240 views Asked by At

I am migrating application from Delphi 6 / TdxDBGrid's to the Delphi 2009 / TcxGrid's and I have the following problem:

I had TdxDBGrid with grid level OnEdited event that assigned "IS_MODIFIED_BY_USER" attribute to the edited record:

procedure TSalesOrderForm.LineGridEdited(Sender: TObject;
  Node: TdxTreeListNode);
begin
  inherited;
  if LineGrid.FocusedField<>LineGridIS_MODIFIED_BY_USER.Field then
    (Sender as TdxDBGrid).DataSource.
      DataSet.FieldByName('IS_MODIFIED_BY_USER').AsInteger:=1;
end;

Such implementation was very smooth. It handled updates coming from grid (user) only and IS_MODIFIED_BY_USER stayed intact if the record was modified by the DataModule (i.e. by the processes that were not direct editing of the user). It worked for edits of any field in the grid.

Now I am trying to move this functionality to the TcxGrid/TcxDBTableView and I am looking on grid level OnEditValueChanged and on the Column-Editor-Properties.OnEditValueChanged.

Such event is fired indeed, but any attempt to change another field (e.g. assign 1 to the IS_MODIFIED_BY_USER when the AMOUNT field is changed) fails spectacularly: the value is assigned (to the IS_MODIFIED_BY_USER), but the edited value (of AMOUNT) is ignored by the grid and upon pressing the Enter (while keeping cursor in the AMOUNT field) the value of AMOUNT reverts to the initial values.

I have tried different assignment strategiess - via DataSet, via Grid Editors, but nothing works.

Other alternatives are not goods as well. E.g. TDataSource.OnUpdateDate is not solution, because it works on the Post only, but I need to assign 1 to the IS_MODIFIED_BY_USER immediately after completion of edit and there may be many other edits in the same record before the whole record is posted.

Another approach would be the use of TClientDataSetAMOUNT.OnChange event handler, but it is not the solution, because this event is fired upon any changes of the dataset record. But I need to catch only changes coming from the user edits in the grid. Another improvement would be the use of flat, that prevents update and set this flag for any DataModule initiated changes:

procedure TMainForm.CDSAMOUNTChange(Sender: TField); begin if (FSystemUpdates) then Exit; if CDSIS_MODIFIED_BY_USER.AsInteger<>1 then CDSIS_MODIFIED_BY_USER.AsInteger:=1; end;

This can work indeed, but this is very involved approach.

I still hope that I can tame the TcxDBTableView to respond to the user-made edits.

Here is my test project that demonstrates the problem:

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.

And 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
1

There are 1 answers

0
TomR On

I narrowed down my problem to the requirement "Change another TcxDBTableView column value while changing one TcxDBTableView column" and it had solution from DevExpress https://supportcenter.devexpress.com/ticket/details/a343/how-to-set-a-value-of-another-grid-column-during-editing-of-a-cell

In the particular case of the code provided by OP there are 2 variants:

If EditValueChanged is handled for the column, then the code is:

procedure TMainForm.DataGridDBTableViewAMOUNTPropertiesEditValueChanged(
  Sender: TObject);
begin
  (Sender as TcxCustomEdit).PostEditValue;
  DataGridDBTableView.DataController.SetEditValue(
    DataGridDBTableViewAMOUNT_2.Index,
    1, evsValue);
end;

But if the EditValueChanged is handled for the DBTableView, then the code is:

procedure TMainForm.DataGridDBTableViewEditValueChanged(
  Sender: TcxCustomGridTableView; AItem: TcxCustomGridTableItem);
begin
  if AItem.Index = DataGridDBTableViewAMOUNT_2.Index then Exit;

  if DataGridDBTableView.Controller.EditingController.IsEditing then begin
    if DataGridDBTableView.Controller.EditingController.EditingItem=AItem then begin
      DataGridDBTableView.Controller.EditingController.Edit.PostEditValue;
    end;
  end;

  DataGridDBTableView.DataController.SetEditValue(
    DataGridDBTableViewAMOUNT_2.Index,
    1, evsValue);
end;

cxDataUtils should be added to the uses.

There are 2 crucial points: 1) the value of the current column should be posted by finding editor and calling PostEditValue; 2) Direct assignment to DataSet or DataController.Values[...] does not work, one shoule set value via SetEditValue(...).