Recent

Author Topic: Managing frames  (Read 6477 times)

kapibara

  • Hero Member
  • *****
  • Posts: 656
Managing frames
« on: April 09, 2016, 08:53:22 pm »
I'm about to use frames to create something similar to an MDI application.

Frames are shown in panels on the main form.

There could be different frame types, like TMemoFrame or TImageFrame so I have created a TBaseFrame. Some work behind the scene goes on and the baseframe saves the need to duplicate that code in descendant frames.

When you click another frame, it should show that it is selected. Different frames look different so the ancestor and descendant share the responsibility to switch to selected.

To select/switch frame, click it. Then a dispatch method of the frame calls an eventhandler in the main form via an method pointer in the BaseFrame. The event property of each frame is assigned right after the frame is created from the main form unit.

Is this a good approach or would you do it differently?

Simple project attached with only two panels and two frames.

Code: Pascal  [Select][+][-]
  1. unit uMemoFrame;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, ExtCtrls, StdCtrls, uBaseFrame;
  9.  
  10. type
  11.  
  12.   { TfrMemoFrame }
  13.  
  14.   TfrMemoFrame = class(TfrBaseFrame)
  15.     Memo: TMemo;
  16.     procedure FrameClick(Sender: TObject);
  17.   private
  18.     { private declarations }
  19.   public
  20.     constructor Create(APanel: TPanel); override;
  21.     procedure Select; override;
  22.     procedure UnSelect; override;
  23.   end;
  24.  
  25. implementation
  26.  
  27. {$R *.lfm}
  28.  
  29. uses
  30.   Graphics;
  31.  
  32. { TfrMemoFrame }
  33.  
  34. procedure TfrMemoFrame.FrameClick(Sender: TObject);
  35. begin
  36.   {Executes the callback in mainform}
  37.   {using the methodpointer in TfrBaseFrame}
  38.   DoSelect;
  39. end;
  40.  
  41. constructor TfrMemoFrame.Create(APanel: TPanel);
  42. begin
  43.   inherited Create(APanel);
  44.   Memo.Color:=clSilver;
  45. end;
  46.  
  47. procedure TfrMemoFrame.Select;
  48. begin
  49.   {Called from mainform via callback}
  50.   Inherited; //Sets Selected to True
  51.   {Unique settings for a MemoFrame}
  52.   Memo.Color:=clDefault;
  53.   Memo.Font.Size:=15;
  54.   Memo.Lines.Add('Selected');
  55. end;
  56.  
  57. procedure TfrMemoFrame.UnSelect;
  58. begin
  59.   {Called from mainform via callback}
  60.   Inherited; //Sets Selected to False
  61.   {Unique settings for a MemoFrame}
  62.   Memo.Color:=clSilver;
  63.   Memo.Lines.Clear;
  64. end;
  65.  
  66. end.
  67.  
  68.  

Code: Pascal  [Select][+][-]
  1. unit uBaseFrame;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, ExtCtrls;
  9.  
  10. type
  11.  
  12.   TfrBaseFrame = class;
  13.  
  14.   TSelectEvent = procedure(AFrame: TfrBaseFrame) of object;
  15.  
  16.   { TfrBaseFrame }
  17.  
  18.   TfrBaseFrame = class(TFrame)
  19.   private
  20.     FOnSelect: TSelectEvent; //Method Pointer
  21.     FSelected: Boolean;
  22.   protected
  23.     procedure DoSelect; //Dispatch Method
  24.   public
  25.     constructor Create(APanel: TPanel); overload; virtual;
  26.     procedure Select; virtual;
  27.     procedure UnSelect; virtual;
  28.     property Selected: Boolean read FSelected write FSelected;
  29.     property OnSelect: TSelectEvent read FOnSelect write FOnSelect;
  30.   end;
  31.  
  32. implementation
  33.  
  34. {$R *.lfm}
  35.  
  36.  
  37. { TfrBaseFrame }
  38.  
  39. procedure TfrBaseFrame.DoSelect;
  40. begin
  41.   if not Selected then
  42.     if Assigned(FOnSelect) then
  43.       FOnSelect(Self);
  44. end;
  45.  
  46. constructor TfrBaseFrame.Create(APanel: TPanel);
  47. begin
  48.   inherited Create(nil);
  49.   Parent:= APanel;
  50.   Name:='';
  51. end;
  52.  
  53. procedure TfrBaseFrame.Select;
  54. begin
  55.   FSelected:= True;
  56. end;
  57.  
  58. procedure TfrBaseFrame.UnSelect;
  59. begin
  60.   FSelected:= False;
  61. end;
  62.  
  63.  
  64. end.
  65.  

Code: Pascal  [Select][+][-]
  1. unit uMain;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls,
  9.   Menus, ExtCtrls, uBaseFrame, uMemoFrame;
  10.  
  11. type
  12.  
  13.   { TfrmMain }
  14.  
  15.   TfrmMain = class(TForm)
  16.     ImageList1: TImageList;
  17.     MenuItem1: TMenuItem;
  18.     pnlLeft: TPanel;
  19.     pmFrames: TPopupMenu;
  20.     pnlRight: TPanel;
  21.     Splitter1: TSplitter;
  22.     StatusBar: TStatusBar;
  23.     ToolBar1: TToolBar;
  24.     tbAddMemoFrame: TToolButton;
  25.     tbFrames: TToolButton;
  26.     procedure FormCreate(Sender: TObject);
  27.     procedure FormResize(Sender: TObject);
  28.   private
  29.     { private declarations }
  30.   public
  31.     CurrentFrame: TfrBaseFrame;
  32.     procedure SetActiveFrame(AFrame: TfrBaseFrame);
  33.   end;
  34.  
  35. var
  36.   frmMain: TfrmMain;
  37.  
  38. implementation
  39.  
  40. {$R *.lfm}
  41.  
  42. { TfrmMain }
  43.  
  44. procedure TfrmMain.FormCreate(Sender: TObject);
  45. var
  46.   aFrame: TfrBaseFrame;
  47. begin
  48.   {Create first MemoFrame, assign eventhandler}
  49.   with TfrMemoFrame.Create(pnlLeft) do
  50.     OnSelect:=@SetActiveFrame;
  51.  
  52.   {Create second MemoFrame, assign eventhandler, and select frame}
  53.   aFrame:= TfrMemoFrame.Create(pnlRight);
  54.   aFrame.OnSelect:=@SetActiveFrame;
  55.   SetActiveFrame(aFrame);
  56. end;
  57.  
  58. procedure TfrmMain.FormResize(Sender: TObject);
  59. var
  60.   panelwidth: integer;
  61. begin
  62.   {Resize panels when form is resized}
  63.   panelwidth:= round(Width / 2);
  64.   pnlLeft.Width:=panelwidth;
  65. end;
  66.  
  67. procedure TfrmMain.SetActiveFrame(AFrame: TfrBaseFrame);
  68. begin
  69.   {If assigned, unselect old frame, select last clicked frame}
  70.   if Assigned(CurrentFrame) then
  71.   begin
  72.     if CurrentFrame <> AFrame then
  73.     begin
  74.       CurrentFrame.UnSelect; //Deselect old frame
  75.       CurrentFrame:= AFrame;
  76.       CurrentFrame.Select;  //Then select chosen frame
  77.       StatusBar.SimpleText:= TPanel(CurrentFrame.Parent).Name;
  78.     end;
  79.   end
  80.   else
  81.   begin
  82.     {CurrentFrame not assigned so just assign and select}
  83.     CurrentFrame:= AFrame;
  84.     CurrentFrame.Select;
  85.     StatusBar.SimpleText:= TPanel(CurrentFrame.Parent).Name;
  86.   end;
  87. end;
  88.  
  89. end.
  90.  
Lazarus trunk / fpc 3.2.2 / Kubuntu 24.04 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 13511

Mike.Cornflake

  • Hero Member
  • *****
  • Posts: 1271
Re: Managing frames
« Reply #2 on: April 09, 2016, 09:11:21 pm »
I have used a Frame hierarchy quite successfully.  Been quite happy with it.  All my frames are created dynamically, which reduced a few headaches in the IDE back when I started.

My architecture differs slightly from yours.  I also have "docks", which are simply forms placed in a pagecontrol (at runtime).  Those docks have an Enter and Leave, similar to your Select/UnSelect, and I can say that's been working great as well :-)

In short, looks good to me.

Mind you, I'd still stick a Memo on the form somewhere, and in your TBaseFrame Select/UnSelect add some log entries to this memo (or some other logging mechanism).  Just to make sure the selection status of your Frames is initialised correctly.  I had some minor UI issues that came down to my initialisation...
Lazarus Trunk/FPC latest fixes on Windows 11
  I'm getting old and stale.  Slowly getting used to git, I'll get there...

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Managing frames
« Reply #3 on: April 09, 2016, 09:25:37 pm »
I would get rid of the panels. They are redundant containers, just making your framework heavier to no purpose, since the frames are all you need in the way of containers.
See the modified project attached.

Mike.Cornflake

  • Hero Member
  • *****
  • Posts: 1271
Re: Managing frames
« Reply #4 on: April 09, 2016, 09:51:03 pm »
While I agree that each TPanel is an additional handle that isn't needed, IMO that's not much of an additional cost.

I use them everywhere I use one of my frames, and I set the Panel Caption to the Class Type of the frame I'm going to place there.  Not for any reason other than a reminder when I'm looking at the form and deciding any UI changes.
Lazarus Trunk/FPC latest fixes on Windows 11
  I'm getting old and stale.  Slowly getting used to git, I'll get there...

eny

  • Hero Member
  • *****
  • Posts: 1665
Re: Managing frames
« Reply #5 on: April 10, 2016, 12:53:32 pm »
My architecture differs slightly from yours.  I also have "docks", which are simply forms placed in a pagecontrol (at runtime).  Those docks have an Enter and Leave, similar to your Select/UnSelect, and I can say that's been working great as well :-)
Same architecture I used in my last project ;)
All posts based on: Win11; stable Lazarus 4_4  (x64) 2026-02-12 (unless specified otherwise...)

kapibara

  • Hero Member
  • *****
  • Posts: 656
Re: Managing frames
« Reply #6 on: April 10, 2016, 10:30:01 pm »
@Mike, Eny I'm glad to hear my approach is sensible.

@WP TMultiFrame looks useful, will look closer at it. My original idea was to show the frames in Pokorny's TMultiPanel and create/destroy/switch between them myself.

@Howard In my case, you are right about the TPanels. Thanks for the improved code.
« Last Edit: April 10, 2016, 10:34:27 pm by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 24.04 - 64 bit

 

TinyPortal © 2005-2018