Jump to content
davornik

Adding RecNo/RecCount TPanel to DBGrid

Recommended Posts

I am trying to add TPanel below DBGrid to show information about RecNo/RecCount position in DBGrid.

Something like in attached image.

 

I have tried to do this:

 

type
  TDBGrid = class(Vcl.DBGrids.TDBGrid)
  private
    RecPanel: TPanel;
  protected
    procedure UpdateScrollBar; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

...

procedure TDBGrid.UpdateScrollBar;
begin
  //where is proper place to update this?
  RecPanel.Left := Self.Left;
  RecPanel.Width := Self.Width;
  RecPanel.Top := Self.Top + Self.Height;

  //is this best place to be updated?
  if Assigned(Self.DataSource.DataSet) then
    RecPanel.Caption := Self.DataSource.DataSet.RecNo.ToString+'/'+Self.DataSource.DataSet.RecordCount.ToString;

  inherited; // to keep the expected behavior
end;

constructor TDBGrid.Create(AOwner: TComponent);
begin
  inherited;
  RecPanel:=TPanel.Create(TDBGrid(AOwner));
  RecPanel.Parent:=TDBGrid(AOwner);
  RecPanel.Alignment:=taLeftJustify;
  RecPanel.Caption := '0/0';
  RecPanel.Height := 16;
end;

destructor TDBGrid.Destroy;
begin
  RecPanel.Free;
  inherited;
end;

Is there better place to update Panel position and record position data then UpdateScrollBar function?

Does creating Panel like TPanel.Create(TDBGrid(AOwner)) has some benefits than perhaps TPanel.Create(nil)?

 

 

DBGrid+RecNo-RecCount_Panel.png

Share this post


Link to post
TPanel.Create(TDBGrid(AOwner));

The owner of the grid is usually the container (form, frame, etc). Typecasting to the wrong type might cause access violations - simply leave it out. It's unnecessary.

13 hours ago, davornik said:

Does creating Panel like TPanel.Create(TDBGrid(AOwner)) has some benefits than perhaps TPanel.Create(nil)?

The main difference is how the object will be freed. When passing nil as the owner the object has to be freed up manually. Otherwise, it's automatically freed when the owner is freed. I'd suggest using Self (the grid) as the owner of the panel so you can leave out .Free in the destructor.

 

P.s.: When I made a similar component I think I ended up putting the count update logic to the DataChange event but I have to doublecheck...

Edited by aehimself

Share this post


Link to post

Ok, then probably the next step is to make it like this without destructor, perhaps like this?

 

type
  TDBGrid = class(Vcl.DBGrids.TDBGrid)
  private
    RecPanel: TPanel;
    procedure SetRecPanelPos;
  protected
    procedure UpdateScrollBar; override;
    procedure Resize; override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

...

procedure TDBGrid.SetRecPanelPos;
begin
  RecPanel.Left := Self.Left;
  RecPanel.Width := Self.Width;
  RecPanel.Top := Self.Top + Self.Height
end;

procedure TDBGrid.Resize;
begin
  inherited;
  SetRecPanelPos;
end;

procedure TDBGrid.UpdateScrollBar;
begin
  //this must be updated here
  if Assigned(Self.DataSource.DataSet) then
    RecPanel.Caption := Self.DataSource.DataSet.RecNo.ToString+'/'+Self.DataSource.DataSet.RecordCount.ToString;
  inherited; // to keep the expected behavior
end;

constructor TDBGrid.Create(AOwner: TComponent);
begin
  inherited;
  RecPanel:=TPanel.Create(Self);
  RecPanel.Parent:=TDBGrid(AOwner);
  RecPanel.Alignment:=taLeftJustify;
  RecPanel.Caption := '0/0';
  RecPanel.Height := 16;
end;

As far of DataChange event, then I get a message like this: Method 'DataChange' not found in base class...

When doing it like this in designtime I don't have Panel shown below the DBGrid. I always have to reduce height of DBGrid for height of Panel.

I suppose the next step is to create it like a component and install it in Delphi? Then Panel would be shown in designtime?

Share this post


Link to post

So, you have an underlying dataset where RecordCount is always accurate when there are more records then in view? Because often TDataset.RecordCount isn't the actual count of records in the final result. 

 

In that case you would need to show something like: 1/4+ or 1/>4

 

(Or do a TDataset.Last + TDataset.First after opening to get the correct RecordCount)

Share this post


Link to post

DataEvent is a method of the link, not the grid itself:

Type
  TMyGridDataLink = Class(TGridDataLink)
  protected
    Procedure DataEvent(Event: TDataEvent; Info: NativeInt); Override;
  End;

  TDBGrid = Class(Vcl.DBGrids.TDBGrid)
  protected
    Function CreateDataLink: TGridDataLink; Override;
  End;

implementation

Procedure TMyGridDataLink.DataEvent(Event: TDataEvent; Info: NativeInt);
Begin
  inherited;

  // Call your public method of (Self.Grid As TDBGrid) to update it's captions
  // Use Event and Info to make conditions if necessary
End;

Function TDBGrid.CreateDataLink: TGridDataLink;
Begin
  Result := TMyGridDataLink.Create(Self);
End;

As for alignment I used a different approach:

  _ownerpanel := TPanel.Create(Self.Parent);
  _ownerpanel.Top := Self.Top;
  _ownerpanel.Left := Self.Left;
  _ownerpanel.Height := Self.Height;
  _ownerpanel.Width := Self.Width;
  _ownerpanel.Anchors := Self.Anchors;
  _ownerpanel.Align := Self.Align;
  _ownerpanel.BevelOuter := bvNone;
  _ownerpanel.Parent := Self.Parent;
  _ownerpanel.Constraints.Assign(Self.Constraints);

  _statusbar := TStatusBar.Create(_ownerpanel);
  _statusbar.Parent := _ownerpanel;
  _statusbar.SimplePanel := True;
  _statusbar.ShowHint := True;

  Self.Constraints.MaxHeight := 0;
  Self.Constraints.MaxWidth := 0;
  Self.Constraints.MinHeight := 0;
  Self.Constraints.MinWidth := 0;
  Self.Align := alClient;
  Self.Parent := _ownerpanel;

 

21 hours ago, davornik said:

I suppose the next step is to create it like a component and install it in Delphi? Then Panel would be shown in designtime?

Correct. You can play around your component in runtime but to reflect your changes in design time you will need a component installed in the Delphi IDE itself.

Share this post


Link to post

This would be component in simplest way possible. On Resize, TPanel moves/resizes with DBGrid.

Only thing I don't know is how to make TPanel move with DBGrid in DesignMode?

 

 

unit EnhDBGrid;

interface

uses
  SysUtils, Classes, DBGrids, ExtCtrls, Messages;

type
  TEnhDBGrid = class(TDBGrid)
  private
    RecPanel: TPanel;
    procedure SetRecPanelPos;
  protected
    procedure UpdateScrollBar; override;
    procedure Resize; override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

procedure Register;

implementation

constructor TEnhDBGrid.Create(AOwner: TComponent);
begin
  inherited;
  RecPanel:=TPanel.Create(Self);
  RecPanel.Parent:=TDBGrid(AOwner);
  RecPanel.Alignment:=taLeftJustify;
  RecPanel.Caption := '0/0';
  RecPanel.Height := 16;
end;

procedure TEnhDBGrid.Resize;
begin
  inherited;
  if Assigned(RecPanel) then SetRecPanelPos;
end;

procedure TEnhDBGrid.UpdateScrollBar;
begin
  inherited; // to keep the expected behavior
  if Assigned(DataSource) and Assigned(DataSource.DataSet) and DataSource.DataSet.Active then
    RecPanel.Caption := DataSource.DataSet.RecNo.ToString+'/'+DataSource.DataSet.RecordCount.ToString;
end;

procedure TEnhDBGrid.SetRecPanelPos;
begin
  RecPanel.Left := Left;
  RecPanel.Width := Width;
  RecPanel.Top := Top + Height;
end;

procedure Register;
begin
   RegisterComponents('Data Controls', [TEnhDBGrid]);
end;

end.

 

Share this post


Link to post
17 minutes ago, davornik said:

This would be component in simplest way possible. On Resize, TPanel moves/resizes with DBGrid.

Only thing I don't know is how to make TPanel move with DBGrid in DesignMode?

Do you want that panel over the TDBGrid? That would mean interference when using scrollbar and last record etc.

It might be better to do a complete TPanel and create the lower TPanel (alBottom aligned) with recordcount and a TDBGrid with alClient aligned.

You could even use a sort of TStatusbar.

With correct alignment (or correct anchors) all your sub-components should resize automatically with the container.

 

I used my own statusbar for showing recordcount. I also implemented a OnScroll event to check if the TDataset has already reached the EOF. If it didn't, then you can't be certain of the recordcount and you can indicate that with a + after the count. Once TDataset.EOF has been reached, the TDataset.Recordcount is accurate (and the + can be dropped). At least... that's how I do it.

 

 

Share this post


Link to post
5 minutes ago, rvk said:

Do you want that panel over the TDBGrid? That would mean interference when using scrollbar and last record etc.

No, Panel is below DBGrid because of that. Only thing left is to move it when DBGrid moves in Designmode.

Everything else works fine, did not notice any other issue (yet :classic_biggrin:).

Share this post


Link to post

O, now I see what you are doing. But if the TPanel is below the TDBGrid then this line is wrong:

RecPanel.Parent:=TDBGrid(AOwner);

Because the parent isn't TDBGrid but the owner of TDBGrid (which is TForm or TPanel or something). So the AOwner parameter in the create call isn't the TDBGrid itself.

You probably get away with it because casting TForm to TDBGrid will point to the same TWinControl object which is stored in TPanel.Parent. But the cast like this is not correct.

 

You probably want to do something like:

RecPanel.Parent:=TWinControl(AOwner);

But then you need to create TEnhDBGrid with the Form as Owner !! (which is default but not always guaranteed).

 

You might want to do

RecPanel.Parent:=Self.Parent; // this would be the same parent as the TDBGrid

But that goes wrong because if you create the component dynamically (like I tried) you don't have a parent yet (because that's assigned after TEnhDBGrid.Create(Formx) call).

 

Anyway... you also can't do TEnhDBGrid.Align := alClient now because the dangling panel below it would be out of view :classic_unsure:

 

That's why it might be more stable if a complete container is created with a TDBGrid and TPanel inside it. Just a thought :classic_happy:

 

BTW. You could also do it like TJvDBGridFooter does. (just a inherited TStatusbar where you can define columns which get summed up).

But there the TJvDBGridFooter and TDBGrid are really separated from each other (component wise) and it's up to the programmer to place the TJvDBGridFooter.

 

Edited by rvk

Share this post


Link to post
On 11/7/2024 at 5:51 PM, rvk said:

BTW. You could also do it like TJvDBGridFooter does. (just a inherited TStatusbar where you can define columns which get summed up).

But there the TJvDBGridFooter and TDBGrid are really separated from each other (component wise) and it's up to the programmer to place the TJvDBGridFooter. 

Yes, that is ok, but a solution with an "attached" TPanel (TStatusbar) is more practical.

I have found in some article on SO from Remy Lebeau (thanks Remy for help) that in Designmode you must override WM_NCHitTest message to move TPanel with DBGrid!

 

...
protected
    procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHitTest;
...

procedure TEnhDBGrid.WMNCHitTest(var Message: TWMNCHitTest);
begin
  inherited;
  if (csDesigning in ComponentState) then SetRecPanelPos;
end;

 

On 11/7/2024 at 5:51 PM, rvk said:

You might want to do


RecPanel.Parent:=Self.Parent; // this would be the same parent as the TDBGrid

That's what I tried first, but it does not work.

Since DBGrid is usually placed on TForm, RecPanel.Parent:=TWinControl(AOwner); will be better alternative, thanks.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×