After investigating this quite thoroughly, I've come to the conclusion that
the behaviour you've noted is a consequence of the TControlList
's LiveBinding mechanism
"working as designed", so there is unlikely to be a clean way of avoiding it (though there is one obvious work-around, see below).
Please try the test project below by creating a new VCL project, adding a
TClientDataSet
, a TStringGrid
and a TControlList
containing 2 TLabel
s, then
adjust the height of the TStringGrid
to about 10 rows and the TControlList
to
about 5. No manual setting up of LiveBindings
is necessary, since it is all
done in the code below.
Then, set the main form's code as shown below, compile and run.
At run-time, the ClientDataSet creates one hundred numbered records with two
fields and displays them in the StringList and ControlList.
The behaviour I see
is that the ControlList behaves as expected unless its current row is its last one
- if it is then the ControlList scrolls so that the clicked row is shifted up one row.
The only simple way I could find to restore it to being the last row is to click
the upper thumb of the ControlList's vertical scrollbar, which as noted in the
code's comments could be the basis of a (not implemented and, I suspect, brittle)
work-around.
Note that the DoSomething
method, which is executed when the ControList is clicked,
has a $define which determines whether the ClientDataSet does an Edit; Post
or a
Next; Prior
. It doesn't seem to make any difference which of these pairs of operations
is executed, the behaviour is the same. This is what prompted me to take a look at
what happens when the ClientDataSet scrolls.
If you put a disabled breakpoint on the Caption := 'Scrolled
line in the
AfterScroll
handler, run the project and then enable the bp just before clicking on
the ControlList, you'll see that when it trips, the scroll event has been triggered
by MakeValidRecNo
in Data.Bind.Scope
which is ultimately invoked by
TLinkObservers.PositionLinkPosChanged
in System.Classes. It's this fact which makes
me think there is very unlikely to be any clean way of avoiding the problem behaviour
short of persuading EMBA to change TControlList's behaviour.
Btw, @fpiette's comment that even doing an Edit
in the OnClick
handle is sufficient to provoke the behaviour and the reason for that is that it will ultimately cause the ClientDataSet to scroll, triggering the behaviour noted above.
Code:
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ControlList, Data.DB,
Datasnap.DBClient, Vcl.Grids, Data.Bind.Components, Data.Bind.DBScope, Data.Bind.Grid,
Data.Bind.EngExt, Vcl.Bind.DBEngExt, Vcl.Bind.Grid, System.Rtti,
System.Bindings.Outputs, Vcl.Bind.Editors, Vcl.Bind.ControlList, Vcl.ExtCtrls,
Vcl.DBCtrls;
type
TForm1 = class(TForm)
ControlList1: TControlList;
Button1: TButton;
StringGrid1: TStringGrid;
ClientDataSet1: TClientDataSet;
BindingsList1: TBindingsList;
Label1: TLabel;
Label2: TLabel;
DBNavigator1: TDBNavigator;
DataSource1: TDataSource;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure ControlList1Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure ClientDataSet1AfterScroll(DataSet: TDataSet);
private
procedure DoSomething;
public
ItemIndex : Integer;
ARect : TRect;
BindSourceDB1 : TBindSourceDB;
LinkGridToDataSourceBindSourceDB1 : TLinkGridToDataSource;
LinkPropertyToFieldCaption1: TLinkPropertyToField;
LinkGridToDataSourceBindSourceDB2: TLinkGridToDataSource;
LinkPropertyToFieldCaption2: TLinkPropertyToField;
end;
[...]
procedure TForm1.DoSomething;
var
Pt : TPoint;
Rows,
Row : Integer;
begin
//Exit;
Pt := Mouse.CursorPos;
Pt := ControlList1.ScreenToClient(Pt);
Rows := ControlList1.ClientHeight div ControlList1.ItemHeight;
if Pt.Y > Rows * ControlList1.ClientHeight then
Row := Rows + 1
else
Row := Pt.Y div ControlList1.ItemHeight;
Inc(Row); // to make it 1-based
Caption := Format('Row: %d', [Row]);
{.$Define DoEdit}
{$IfDef DoEdit}
ClientDataSet1.Edit;
ClientDataSet1.Post;
{$Else}
ClientDataSet1.Next;
ClientDataSet1.Prior;
{$Endif}
if Row >= Rows then begin
// The observed behaviour is that if the clicked row was the last one in the grid
// the ClientDataSet operations above will have moved the current record's data
// will now be in the row above the bottom row. A work-around would be to simulate
// clicking the upper thumb of ControlList1's vertical scrollbar, as this shifts the
// current ror back down to where it was, in the last row of the grid.
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DoSomething;
end;
procedure TForm1.ClientDataSet1AfterScroll(DataSet: TDataSet);
begin
Caption := 'Scrolled';
end;
procedure TForm1.ControlList1Click(Sender: TObject);
begin
DoSomething;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
AField : TField;
i : Integer;
begin
AField := TIntegerField.Create(Self);
AField.FieldName := 'Field1';
AField.FieldKind := fkData;
AField.DataSet := ClientDataSet1;
AField := TStringField.Create(Self);
AField.FieldName := 'Field2';
AField.Size := 20;
AField.FieldKind := fkData;
AField.DataSet := ClientDataSet1;
ControlList1.ClientHeight := ControlList1.ItemHeight * 5;
BindSourceDB1 := TBindSourceDB.Create(Self);
BindSourceDB1.DataSet := ClientDataSet1;
LinkGridToDataSourceBindSourceDB1 := TLinkGridToDataSource.Create(Self);
LinkGridToDataSourceBindSourceDB1.DataSource := BindSourceDB1;
LinkGridToDataSourceBindSourceDB1.GridControl := StringGrid1;
LinkGridToDataSourceBindSourceDB2 := TLinkGridToDataSource.Create(Self);
LinkGridToDataSourceBindSourceDB2.DataSource := BindSourceDB1;
LinkGridToDataSourceBindSourceDB2.GridControl := ControlList1;
LinkPropertyToFieldCaption1 := TLinkPropertyToField.Create(Self);
LinkPropertyToFieldCaption1.DataSource := BindSourceDB1;
LinkPropertyToFieldCaption1.FieldName := 'Field1';
LinkPropertyToFieldCaption1.Component := Label1;
LinkPropertyToFieldCaption1.ComponentProperty := 'Caption';
LinkPropertyToFieldCaption2 := TLinkPropertyToField.Create(Self);
LinkPropertyToFieldCaption2.DataSource := BindSourceDB1;
LinkPropertyToFieldCaption2.FieldName := 'Field2';
LinkPropertyToFieldCaption2.Component := Label2;
LinkPropertyToFieldCaption2.ComponentProperty := 'Caption';
ClientDataSet1.IndexFieldNames := 'Field1';
ClientDataSet1.CreateDataSet;
for i := 1 to 100 do
ClientDataSet1.InsertRecord([i, 'Row ' + IntToStr(i)]);
ClientDataSet1.First;
end;