Recent

Author Topic: [SOLVED] DBGrid - Center selected row.  (Read 8775 times)

pcurtis

  • Hero Member
  • *****
  • Posts: 951
[SOLVED] DBGrid - Center selected row.
« on: November 24, 2021, 08:40:08 am »
I have a DBGrid that can show 7 rows. The dataset has 1000 rows.

How can I center the selected row in the center of the grid?
(If RecNo > 3 and RecNo < 997)
« Last Edit: November 24, 2021, 07:43:24 pm by pcurtis »
Windows 10 20H2
Laz 2.2.0
FPC 3.2.2

Zvoni

  • Hero Member
  • *****
  • Posts: 2327
Re: DBGrid - Center selected row.
« Reply #1 on: November 24, 2021, 10:19:32 am »
Maybe with some math using TopRow-Property?
along the lines of
Grid.TopRow:=IndexOfSelectedRow - 3

You would have to catch the Edge-cases of course (SelectedRow Index is 2, 2-3 is -1, setting TopRow to -1 = KABOOM)

Nota Bene: No idea if my proposal works, since i have no clue to how TopRow works, but it was the one Property catching my eye
« Last Edit: November 24, 2021, 10:57:54 am by Zvoni »
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

pcurtis

  • Hero Member
  • *****
  • Posts: 951
Re: DBGrid - Center selected row.
« Reply #2 on: November 24, 2021, 11:27:18 am »
TopRow is not a property of TDBGrid

or after a

Code: Pascal  [Select][+][-]
  1.   iTemp := ZQuery1['fnIDX'];
  2.   MyQuery.ExecSQL; // update recordset
  3.   ZQuery1.Close;
  4.   ZQuery1.Open;
  5.   ZQuery1.Locate('fnIDX', iTemp, []);
  6.  

put the DBGrid back to the same place / position as before the update.
« Last Edit: November 24, 2021, 11:36:15 am by pcurtis »
Windows 10 20H2
Laz 2.2.0
FPC 3.2.2

Zvoni

  • Hero Member
  • *****
  • Posts: 2327
Re: DBGrid - Center selected row.
« Reply #3 on: November 24, 2021, 11:36:54 am »
TopRow is not a property of TDBGrid
Argg... missed that.
It's protected in TCustomGrid.
No idea how to make a protected Property in Ancestor to public in the Grandkid
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

Handoko

  • Hero Member
  • *****
  • Posts: 5150
  • My goal: build my own game engine using Lazarus
Re: DBGrid - Center selected row.
« Reply #4 on: November 24, 2021, 02:48:35 pm »
If you meant you want the DBGrid to scroll to certain position showing certain record number, then this maybe is what you want:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, dbf, Forms, Controls, Dialogs, DBGrids, StdCtrls, Spin, DB;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     Dbf1: TDbf;
  17.     DBGrid1: TDBGrid;
  18.     Label1: TLabel;
  19.     speGoto: TSpinEdit;
  20.     speTop: TSpinEdit;
  21.     procedure Button1Click(Sender: TObject);
  22.     procedure FormCreate(Sender: TObject);
  23.   private
  24.     function  RandomChars(Count: Integer): string;
  25.     procedure GenerateDataFile;
  26.     procedure GotoRow(RowNo, TopPadding: Integer);
  27.   end;
  28.  
  29. var
  30.   Form1: TForm1;
  31.  
  32. implementation
  33.  
  34. Const
  35.   DataFileName = 'Data.dbf';
  36.  
  37. {$R *.lfm}
  38.  
  39. { TForm1 }
  40.  
  41. procedure TForm1.FormCreate(Sender: TObject);
  42. var
  43.   aDataSource: TDataSource;
  44. begin
  45.  
  46.   // Connection and data
  47.   aDataSource         := TDataSource.Create(Self);
  48.   aDataSource.DataSet := Dbf1;
  49.   Dbf1.FilePathFull   := ExtractFileDir(ParamStr(0));
  50.   Dbf1.TableName      := DataFileName;
  51.   if not(FileExists(DataFileName)) then
  52.     GenerateDataFile;
  53.   Dbf1.Active        := True;
  54.   DBGrid1.DataSource := aDataSource;
  55.  
  56.   // Appearance
  57.   Constraints.MinHeight := 240;
  58.   Constraints.MinWidth  := 320;
  59.   DBGrid1.Anchors       := [akLeft, akRight, akTop, akBottom];
  60.   Button1.Anchors       := [akLeft, akBottom];
  61.   speGoto.MaxValue      := Dbf1.ExactRecordCount;
  62.   speGoto.Anchors       := [akLeft, akBottom];
  63.   speTop.MaxValue       := Dbf1.ExactRecordCount;
  64.   speTop.Anchors        := [akLeft, akBottom];
  65.  
  66. end;
  67.  
  68. procedure TForm1.Button1Click(Sender: TObject);
  69. begin
  70.   GotoRow(speGoto.Value, speTop.Value);
  71. end;
  72.  
  73. function TForm1.RandomChars(Count: Integer): string;
  74. var
  75.   S: string;
  76.   i: Integer;
  77. begin
  78.   if Count < 1  then Count := 1;
  79.   if Count > 20 then Count := 20;
  80.   S := '';
  81.   for i := 1 to Count do
  82.     S := S + chr(Ord('a')+Random(26));
  83.   Result := S;
  84. end;
  85.  
  86. procedure TForm1.GenerateDataFile;
  87. const
  88.   TotalItems = 1000;
  89. var
  90.   i: Integer;
  91. begin
  92.   with Dbf1 do
  93.   begin
  94.     FieldDefs.Add('ID', ftInteger, 0, True);
  95.     FieldDefs.Add('DATA', ftString, 8, True);
  96.     CreateTable;
  97.     Open;
  98.       for i := 1 to TotalItems do
  99.       begin
  100.         Append;
  101.         FieldByName('ID').AsInteger  := i;
  102.         FieldByName('DATA').AsString := RandomChars(8);
  103.         Post;
  104.       end;
  105.     Close;
  106.   end;
  107. end;
  108.  
  109. procedure TForm1.GotoRow(RowNo, TopPadding: Integer);
  110. var
  111.   Total:       Integer;
  112.   VisibleRows: Integer;
  113.   Key:         Word;
  114.   Adjust:      Integer;
  115.   i:           Integer;
  116. begin
  117.  
  118.   Total := Dbf1.ExactRecordCount;
  119.   if Total <= 0 then Exit;
  120.  
  121.   // Get visible row count
  122.   VisibleRows := 0;
  123.   for i := 1 to Total do
  124.     if DBGrid1.IsCellVisible(1, i) then
  125.       Inc(VisibleRows);
  126.   if VisibleRows <= 0 then Exit;
  127.  
  128.   // Send Ctrl+Home keyboard signal to the grid
  129.   Key := 36;
  130.   DBGrid1.EditorKeyDown(Self, Key, [ssCtrl]);
  131.  
  132.   // Send Arrow_Down keyboard signal to the grid
  133.   for i := 1 to RowNo-1 do
  134.   begin
  135.     Key := 40;
  136.     DBGrid1.EditorKeyDown(Self, Key, []);
  137.   end;
  138.  
  139.   // Send arrow up and down signals to get position for the top padding
  140.   if TopPadding >= VisibleRows + 1 then Exit;
  141.   Adjust := VisibleRows - TopPadding - 1;
  142.   if RowNo + Adjust > Total then
  143.     Adjust := Total - RowNo;
  144.   for i := 1 to Adjust do
  145.   begin
  146.     Key := 40;
  147.     DBGrid1.EditorKeyDown(Self, Key, []);
  148.   end;
  149.   for i := 1 to Adjust do
  150.   begin
  151.     Key := 38;
  152.     DBGrid1.EditorKeyDown(Self, Key, []);
  153.   end;
  154.  
  155. end;
  156.  
  157. end.

  • First time running the program, a dbf with 1000 records will be created, see line #86
  • The grid will try to scroll to position the RecNo to have the top padding as the value provided but it may fail on some cases
  • TopPadding = 0 means to show the wanted RecNo on the first row in the grid
  • The trick to make the grid to scroll automatically is in the GotoRow procedure, see line #109
Warning:
Tested on Lazarus 2.0.10 GTK2 Linux, it may or may not work on future versions of Lazarus and it is may not work on other OSes.
« Last Edit: November 25, 2021, 02:39:23 am by Handoko »

Handoko

  • Hero Member
  • *****
  • Posts: 5150
  • My goal: build my own game engine using Lazarus
Re: DBGrid - Center selected row.
« Reply #5 on: November 24, 2021, 03:09:51 pm »
TopRow is not a property of TDBGrid
It's protected in TCustomGrid.

Not a problem, we can force a protected property to be used on its descendants.

Code: Pascal  [Select][+][-]
  1. type
  2.   TMyGrid = class(TCustomGrid)
  3.     public
  4.       property TopRow;
  5.   end;
  6.  
  7. procedure TForm1.Button1Click(Sender: TObject);
  8. begin
  9.   TMyGrid(DBGrid1).TopRow := 5;
  10. end;

Only warning but compile-able. Unfortunately, due to some unknown reason it does not work.

Zvoni

  • Hero Member
  • *****
  • Posts: 2327
Re: DBGrid - Center selected row.
« Reply #6 on: November 24, 2021, 03:13:24 pm »
Not a problem, we can force a protected property to be used on its descendants.

Code: Pascal  [Select][+][-]
  1. type
  2.   TMyGrid = class(TCustomGrid)
  3.     public
  4.       property TopRow;
  5.   end;
  6.  
  7. procedure TForm1.Button1Click(Sender: TObject);
  8. begin
  9.   TMyGrid(DBGrid1).TopRow := 5;
  10. end;

Only warning but compile-able. Unfortunately, due to some unknown reason it does not work.
In Singlestep-Mode, when assigning (your Line 9), can you step into the (protected) setter of the Property?
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

Handoko

  • Hero Member
  • *****
  • Posts: 5150
  • My goal: build my own game engine using Lazarus
Re: DBGrid - Center selected row.
« Reply #7 on: November 24, 2021, 03:16:34 pm »
Just tried it, yes I can step into it. Unfortunately the code is too advanced, I am not able to digest it.

Code: Pascal  [Select][+][-]
  1. procedure TCustomGrid.TryScrollTo(aCol, aRow: Integer; ClearColOff,
  2.   ClearRowOff: Boolean);
  3. var
  4.   TryTL: TPoint;
  5.   NewCol,NewRow: Integer;
  6.   TLChange: Boolean;
  7. begin
  8.   TryTL:=ScrollGrid(False,aCol, aRow);
  9.   TLChange := not PointIgual(TryTL, FTopLeft);
  10.   if TLChange
  11.   or (not PointIgual(TryTL, Point(aCol, aRow)) and (goSmoothScroll in Options))
  12.   or (ClearColOff and (FGCache.TLColOff<>0))
  13.   or (ClearRowOff and (FGCache.TLRowOff<>0)) then
  14.   begin
  15.     NewCol := TryTL.X - FTopLeft.X + Col;
  16.     NewRow := TryTL.Y - FTopLeft.Y + Row;
  17.     FTopLeft:=TryTL;
  18.     if ClearColOff then
  19.       FGCache.TLColOff := 0;
  20.     if ClearRowOff then
  21.       FGCache.TLRowOff := 0;
  22.     if (aCol>TryTL.X) and (goSmoothScroll in Options) then
  23.       FGCache.TLColOff := FGCache.MaxTLOffset.X;
  24.     if (aRow>TryTL.Y) and (goSmoothScroll in Options) then
  25.       FGCache.TLRowOff := FGCache.MaxTLOffset.Y;
  26.     {$ifdef dbgscroll}
  27.     DebugLn('TryScrollTo: TopLeft=%s NewCol=%d NewRow=%d',
  28.       [dbgs(FTopLeft), NewCol, NewRow]);
  29.     {$endif}
  30.     // To-Do: move rect with ScrollBy_WS and invalidate only new (not scrolled) rects
  31.     if TLChange then
  32.       doTopleftChange(False)
  33.     else
  34.       VisualChange;
  35.     if goScrollKeepVisible in Options then
  36.       MoveNextSelectable(False, NewCol, NewRow);
  37.   end;
  38. end;

pcurtis

  • Hero Member
  • *****
  • Posts: 951
Re: [SOLVED] DBGrid - Center selected row.
« Reply #8 on: November 24, 2021, 07:45:20 pm »
@Handoko PERFECT
Windows 10 20H2
Laz 2.2.0
FPC 3.2.2

pcurtis

  • Hero Member
  • *****
  • Posts: 951
Re: [SOLVED] DBGrid - Center selected row.
« Reply #9 on: November 25, 2021, 01:05:04 pm »
I made this custom DBGrid based on your supplied code

Code: Pascal  [Select][+][-]
  1. unit MyDBGrid;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, LResources, Forms, Windows, Controls, Graphics, StdCtrls, ExtCtrls,
  9.   Dialogs, DBGrids, Grids;
  10.  
  11. type
  12.   TMyHackedGrid = class(TDBGrid);
  13.   TMyDBGrid = class(TDBGrid)
  14.   private
  15.  
  16.   protected
  17.  
  18.   public
  19.     function GetSelectedRow : Integer;
  20.     function GetVisibleRowCount : Integer;
  21.     procedure ScrollTo(aIndexField : String; aValue : Integer; aOffset : Integer);
  22.     constructor Create(AOwner : TComponent); override;
  23.     destructor Destroy; override;
  24.   published
  25.  
  26.   end;
  27.  
  28. procedure Register;
  29.  
  30. implementation
  31.  
  32. {$R mydbgrid.res}
  33.  
  34. constructor TMyDBGrid.Create(AOwner : TComponent);
  35. begin
  36.   inherited Create(AOwner);
  37.   AlternateColor := clMoneyGreen;
  38.   Color := clCream;
  39.   Options := Options + [dgRowSelect];
  40.   Options := Options - [dgEditing];
  41.   AutoFillColumns := true;
  42. end;
  43.  
  44. destructor TMyDBGrid.Destroy;
  45. begin
  46.   inherited Destroy;
  47. end;
  48.  
  49. function TMyDBGrid.GetSelectedRow : Integer;
  50. begin
  51.   Result := TMyHackedGrid(Self).Row;
  52. end;
  53.  
  54. function TMyDBGrid.GetVisibleRowCount : Integer;
  55. begin
  56.   Result := TMyHackedGrid(Self).VisibleRowCount;
  57. end;
  58.  
  59. procedure TMyDBGrid.ScrollTo(aIndexField : String; aValue : Integer; aOffset : Integer);
  60. var
  61.   iTemp : Integer;
  62. begin
  63.   if (not Assigned(DataSource)) or
  64.      (DataSource.DataSet.Active = False) or
  65.      (DataSource.DataSet.RecordCount = 0) then
  66.        exit;
  67.  
  68.   DataSource.DataSet.First;
  69.   DataSource.DataSet.Locate(aIndexField, aValue, []);
  70.  
  71.   for iTemp := 1 to aOffset do
  72.   begin
  73.     PostMessage(Self.Handle, WM_KEYDOWN, VK_UP, 0);
  74.   end;
  75.  
  76.   for iTemp := 1 to aOffset do
  77.   begin
  78.     PostMessage(Self.Handle, WM_KEYDOWN, VK_DOWN, 0);
  79.   end;
  80. end;
  81.  
  82. procedure Register;
  83. begin
  84.   RegisterComponents('Misc',[TMyDBGrid]);
  85. end;
  86.  
  87. end.
  88.  
« Last Edit: November 25, 2021, 08:38:12 pm by pcurtis »
Windows 10 20H2
Laz 2.2.0
FPC 3.2.2

Handoko

  • Hero Member
  • *****
  • Posts: 5150
  • My goal: build my own game engine using Lazarus
Re: [SOLVED] DBGrid - Center selected row.
« Reply #10 on: November 25, 2021, 02:24:20 pm »
That looks good. Unfortunately, I cannot test it because it depends on Windows unit.

lainz

  • Hero Member
  • *****
  • Posts: 4468
    • https://lainz.github.io/
Re: [SOLVED] DBGrid - Center selected row.
« Reply #11 on: November 25, 2021, 03:12:53 pm »
Windows unit can not be replaced with LCLType, at some extent?

wp

  • Hero Member
  • *****
  • Posts: 11912
Re: [SOLVED] DBGrid - Center selected row.
« Reply #12 on: November 25, 2021, 03:22:47 pm »
That looks good. Unfortunately, I cannot test it because it depends on Windows unit.
Replace unit Windows by LCLType and LCLIntf. And replace WM_KEYDOWN/WM_KEYUP by LM_KEYDOWN/LM_KEYUP.

Sieben

  • Sr. Member
  • ****
  • Posts: 310
Re: [SOLVED] DBGrid - Center selected row.
« Reply #13 on: November 27, 2021, 09:47:40 pm »
Based on this here idea by 'Soner' I ended up with that little sketch included below that seems to do quite a good job in keeping the selected row of a DBGrid centered. Of course that has to be supended when the user clicks a certain record, but snaps in again asap. And I could not get the grid to agree that the 'natural' center position with an even number of data rows ist the last line of the upper half instead of the first one of the lower. Solution is to make sure that your grid is odd...
Lazarus 2.2.0, FPC 3.2.2, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

 

TinyPortal © 2005-2018