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:
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.
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;