davornik 4 Posted October 30 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)? Share this post Link to post
aehimself 396 Posted October 31 (edited) 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 October 31 by aehimself Share this post Link to post
davornik 4 Posted November 2 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
rvk 44 Posted November 2 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
aehimself 396 Posted November 3 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
davornik 4 Posted November 7 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
rvk 44 Posted November 7 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
davornik 4 Posted November 7 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 ). Share this post Link to post
rvk 44 Posted November 7 (edited) 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 That's why it might be more stable if a complete container is created with a TDBGrid and TPanel inside it. Just a thought 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 November 7 by rvk Share this post Link to post
davornik 4 Posted November 11 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