0

I am using XE2. I want to do when a user edits the Unit Price in a grid, auto re-calculate total price which is the next column.

Which event I should use?

I tried TCXGridTableView OnEditValueChanged, but I cannot get the modified value? the AItem.EditValue is the before modifying value.

Then I tried TcxGridColumn OnEditValueChanged. I can get the modified value like this:

  cxCE := Sender as TcxCurrencyEdit;
  cxCE.Value; // this is the modified value

However, here I got an issue if user modified a value, then NOT pressing Enter, but press the TAB key to leave, a funny problem occurred:

  1. The TcxGridColumn OnEditValueChanged event does executed.
  2. I can still get the cxCE.Value (modified value) and updated the next column's value successful.
  3. Just after modifed the next column's value, cxCE.Value changed back to before modify value!
  4. as a result, user input is rolled back, but the next column does updated.

an example what happened:

Qty | Unit Price | Total Price

2...... 5................ 10

when user modifed unit price from 5 to 7, and press tab after OnEditValueChanged, Unit Price rolled back but my logic updated Total Price:

2...... 5 (rollback) 14(updated)

Appreciated if anyone could help me, thanks a lot.

Eric Cheng
  • 477
  • 1
  • 9
  • 24
  • 1
    Use the DevEx knowledge base rather. (https://www.devexpress.com/Support/Center/Question/Details/T564409/calculate-unbound-column) – Freddie Bell Oct 09 '18 at 09:22
  • is your view connected to a dataset or not? if it is then just modify the dataset – Ago Oct 12 '18 at 09:04

1 Answers1

1

Which event I should use?

None of the above. I think you are going about this the wrong way. What you seem to want is to automatically update the TotalPrice field when the Qty or UnitPrice field changes. The most productive way to think of that is as a data-manipulation operation, rather that a GUI operation,and that's the way you should code it.

The cxGrid is a db-aware component, and these are coded to automatically reflect changes to the data, so the way to go about updating the TotalPrice field is to do it in code which operates on the dataset, NOT in code which operates on the cxGrid. If you try to do it in code for the cxGrid, you'll find yourself continually "fighting" with the grid, because it knows how to be db-aware and you're trying, in effect, to subvert that.

Try the example project below. Set up a new VCL project, add a TClientDataSet, TDataSource and TDBNavigator and "wire them up" in the usual way.

Set up an OnCalcFields event handler for the CDS and an FormCreate event for the form and then add the code shown below.

When the project runs, it dynamically creates a cxGRid to display the data (I did it this way because there are so many settings and sub-components in a cxGrid that it's easiest to create one in code rather than specify its settings in an answer like this).

Play about with changing the values in the Qty and UnitPrice fields and notice that the TotalPrice automatically updates without requing any code which operates on the cxGrid.

type
  TForm1 = class(TForm)
    CDS1: TClientDataSet;
    DS1: TDataSource;
    DBNavigator1: TDBNavigator;
    procedure FormCreate(Sender: TObject);
    procedure CDS1CalcFields(DataSet: TDataSet);
  private
  public
    cxGrid : TcxGrid;
    cxLevel : TcxGridLevel;
    cxView : TcxGridDBTableView;
  end;

[...]

// This is a utility function to create TFields in code
function CreateField(AFieldClass : TFieldClass; AOwner : TComponent; ADataSet : TDataSet;
AFieldName, AName : String; ASize : Integer; AFieldKind : TFieldKind) : TField;
begin
  Result := AFieldClass.Create(AOwner);
  Result.FieldKind := AFieldKind;
  Result.FieldName := AFieldName;
  Result.Name := AName;
  Result.Size := ASize;
  Result.DataSet := ADataSet;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i : Integer;
  Field : TField;
  Col : TcxGridDBColumn;
begin

  //  First, create the Fields of the ClientDataSet
  Field := CreateField(TIntegerField, Self, CDS1, 'ID', 'CDS1ID', 0, fkData);
  Field := CreateField(TIntegerField, Self, CDS1, 'Qty', 'CDS1Qty', 0, fkData);
  Field := CreateField(TCurrencyField, Self, CDS1, 'UnitPrice', 'CDS1UnitPrice', 0, fkData);
  Field := CreateField(TCurrencyField, Self, CDS1, 'TotalPrice', 'CDS1TotalPrice', 0, fkInternalCalc);
//  Field.ReadOnly := True;

  CDS1.CreateDataSet;

  CDS1.IndexFieldNames := 'ID';

  //  Next, populate the CDS with a few records
  //  Note : If we are using calculated fields, we do to need to specify
  //  a value for the TotalPriced field
  CDS1.InsertRecord([1, 1, 1]);
  CDS1.InsertRecord([2, 2, 5]);
  CDS1.InsertRecord([3, 3, 6]);

  CDS1.First;

  //  Now, create a cxGrid to display the CDS data
  cxGrid := TcxGrid.Create(Self);
  cxGrid.Parent := Self;
  cxGrid.Width := 400;

  cxLevel := cxGrid.Levels.Add;
  cxLevel.Name := 'Firstlevel';

  cxView := cxGrid.CreateView(TcxGridDBTableView) as TcxGridDBTableView;
  cxView.Name := 'ATableView';
  cxView.DataController.KeyFieldNames := 'ID';
  cxView.DataController.Options := cxView.DataController.Options + [dcoImmediatePost];

  cxLevel.GridView := cxView;
  cxView.DataController.DataSource := DS1;
  cxView.DataController.CreateAllItems;

  //  Since the TotalPrice column is a calculated field, we need to
  //  prevent the user from attempting to edit it
  Col := cxView.GetColumnByFieldName('TotalPrice');
  Col.Options.Editing := False;

  ActiveControl := cxGrid;

end;

//  Procedure to calculate the TotalPrice field
procedure CalculateTotalPrice(DataSet : TDataSet);
var
  Qty : Integer;
  UnitPrice,
  TotalPrice : Currency;
begin
  Qty := DataSet.FieldByName('Qty').AsInteger;
  UnitPrice := DataSet.FieldByName('UnitPrice').AsCurrency;
  TotalPrice := Qty * UnitPrice;
  DataSet.FieldByName('TotalPrice').AsCurrency := TotalPrice;
end;

procedure TForm1.CDS1CalcFields(DataSet: TDataSet);
begin
  CalculateTotalPrice(DataSet);
end;
MartynA
  • 30,454
  • 4
  • 32
  • 73