Updating field in cxGrid acting strange

2.7k views Asked by At

I have a function to update a cxGrid made with help from answers to Loop through records on a cxgrid and update a field/column

But it is sometimes acting a bit strange. If I open the form with the cxGrid and click the columnheader without doing anything else, the records are updateted OK. But if the 'selectorbar' is moved away from the top, the record marked is not updated. I am sure it is a property that needs to be changed, but which one.

The variable fSelected is set to False at FormShow and is ther so that the user can unselect records as well.

procedure TfrmContactsSelect.colContactSelectedHeaderClick(Sender: TObject);
var
  i: Integer;
  Index: Integer;
  BookMark : TBookMark;
  Contact: variant;
begin
  if fMulti = True then
    begin
     Screen.Cursor := crHourGlass;
     fSelected := not fSelected;
      BookMark := qryContacts.GetBookmark;
      qryContacts.DisableControls;
      try
        for i := 0 to grdContactsView1.DataController.FilteredRecordCount - 1 do
          begin
            Index := grdContactsView1.DataController.FilteredRecordIndex[i];
            Contact := grdContactsView1.DataController.Values[Index, 4];
            if grdContactsView1.DataController.LocateByKey(Contact) then
              begin
                qryContacts.Edit;
                qryContacts.FieldByName('fldcontact_selected').AsBoolean := fSelected;
                qryContacts.Post;
              end;
          end;
      finally
        qryContacts.EnableControls;
        qryContacts.GotoBookmark(BookMark);
        qryContacts.FreeBookmark(BookMark);
      end;
      Screen.Cursor := crDefault;
    end;
end;

Delphi XE7, DevExpress 14.2.2, UniDAC 5.5.12 for DB access

Comment: I have ended up with the following solution based on the answer and input from MartynA

procedure TfrmContactsSelect.colContactSelectedHeaderClick(Sender: TObject);
var
  i: Integer;
  Index: Integer;
  MarkedRecord: variant;
  CurrentRecord: variant;
begin
  if fMulti = True then
    begin
      Screen.Cursor := crHourGlass;
      fSelected := not fSelected;

      Index := grdContactsView1.DataController.FocusedRecordIndex;
      MarkedRecord := grdContactsView1.DataController.Values[Index, colContactGuid.ID];

      try
        for i := 0 to grdContactsView1.DataController.FilteredRecordCount - 1 do
          begin
            Index := grdContactsView1.DataController.FilteredRecordIndex[i];
            CurrentRecord := grdContactsView1.DataController.Values[Index, colContactGuid.ID];
            if grdContactsView1.DataController.LocateByKey(CurrentRecord) then
              begin
                grdContactsView1.DataController.Edit;
                grdContactsView1.DataController.SetEditValue(colContactSelected.ID, fSelected, evsText);
                grdContactsView1.DataController.Post;
              end;
          end;
      finally
        grdContactsView1.DataController.LocateByKey(MarkedRecord);
      end;

      Screen.Cursor := crDefault;
    end;
end;
1

There are 1 answers

3
MartynA On BEST ANSWER

I can reproduce your problem using the sample project I posted in my answer to your other q.

Try this: Add a TMemo to your form, and inside the 'if grdContactsView1.DataController.LocateByKey(Contact) then' block, write the value of a row-unique datafield and the Selected datafield value to the memo.

Then, what I get when some row other than the top row is selected is that one row is listed twice in the memo, with Selected both false and true, and one of the rows in the filter isn't listed at all, which I think accounts for the behaviour you're seeing. If I then comment out the .Edit .. .Post lines, it correctly lists all the rows in the filter.

So evidently doing the Selected field changes inside a block which iterated the FilteredRecordIndex property of the DBTableView is what's causing the problem.

Personally, I find that it goes a bit against the grain to modify dataset rows in code via a DB-aware control (because you usually end up fighting the DB-awareness of the control), but in this case, it's straightforward to do the processing via the DBTableView of the cxGrid.

procedure TForm1.ProcessFilteredRecords;
var
  PrevV,
  V : Variant;
  i,
  Index: Integer;
  S : String;
begin

  //  First, pick up a reference to the current record
  //  so that we can return to it afterwards
  Index := cxGrid1DBTableView1.DataController.FocusedRecordIndex;
  PrevV := cxGrid1DBTableView1.DataController.Values[Index, 0];

  try
    for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
      Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
      V := cxGrid1DBTableView1.DataController.Values[Index, 0];
      if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
        cxGrid1DBTableView1.DataController.Edit;
        // 2 is the index of my Selected column in the grid
        if cxGrid1DBTableView1.DataController.SetEditValue(2, True, evsText) then
          Caption := 'OK'
        else
          Caption := 'Failed';
        cxGrid1DBTableView1.DataController.Post;
      end;
    end;
  finally
    if cxGrid1DBTableView1.DataController.LocateByKey(PrevV) then
      Caption := 'OK'
    else
      Caption := 'Failed';
  end;
end;

Another way to avoid the problem is to change the Selected states in two steps:

  • Iterate the FilteredRecordIndex to build a list of rows to change - in your case this would be a list of guids

  • Then, iterate the list of rows and update their Selected states.

Code:

procedure TForm1.ProcessFilteredRecords;
var
  V : Variant;
  i,
  Index: Integer;
  BM : TBookMark;
  S : String;
  TL : TStringList;
begin
  Memo1.Lines.Clear;
  TL := TStringList.Create;
  try
    for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
      Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
      V := cxGrid1DBTableView1.DataController.Values[Index, 0];

      if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
        if CDS1.FieldByName('Selected').AsBoolean then
          S := 'True'
        else
          S := 'False';
        S := CDS1.FieldByName('Name').AsString + ' ' + S;
        Memo1.Lines.Add(S);
        TL.Add(CDS1.FieldByName('Guid').AsString);
      end;
    end;

    try
      BM := CDS1.GetBookMark;
      CDS1.DisableControls;
      for i := 0 to TL.Count - 1 do begin
        if CDS1.Locate('guid', TL[i], []) then begin
          CDS1.Edit;
          CDS1.FieldByName('Selected').AsBoolean := True;
          CDS1.Post;
        end
      end;
    finally
      CDS1.EnableControls;
      CDS1.GotoBookmark(BM);
      CDS1.FreeBookmark(BM);
    end;
  finally
    TL.Free;
  end;
end;

Like you, I was expecting that changing a property or two of the cxGrid might avoid the problem without any code, but I haven't been able to find anything which does.