This_Is_A_Bug
This_Is_A_Bug

Reputation: 1

How do I add a unit dynamically to a frame's uses clause?

Suppose I have 1 main form. Main form contains two buttons (Open and Close), clicking on each button creates a frame with the Main form as the parent/owner. The frame contains 3 buttons (Add, Edit and Delete) with the sample code below:

Frame Code:

unit UntFrame;

...

  type
  TFmeMain = class(TFrame)
    procedure AddClick(Sender: TObject);
    procedure EditClick(Sender: TObject);
    procedure DeleteClick(Sender: TObject);
  private ...
  public ...
  end;

implementation

{$R *.dfm}

uses ...

procedure TFmeMain.AddClick(Sender: TObject);
begin
   AddBtnClick;
end;

procedure TFmeMain.EditClick(Sender: TObject);
begin
   EditBtnClick;
end;

procedure TFmeMain.DeleteClick(Sender: TObject);
begin
   DelBtnClick;
end;

end.

The 3 methods (AddBtnClick, EditBtnClick and DelBtnClick) used by the frame exists on two different units namely UntOpen and UntClose but does things differently depending on the created frame. UntOpen should be used by the frame when the Open button is clicked, UntClose is for when the Close button is clicked from the main menu.

UntOpen Code:

unit UntOpen;

...

procedure AddBtnClick;
procedure EditBtnClick;
procedure DelBtnClick;

implementation

procedure AddBtnClick;
begin
    ShowMessage('add open');
end;

procedure EditBtnClick;
begin
   ShowMessage('edit open');
end;

procedure DelBtnClick;
begin
   ShowMessage('delete open');
end;
end.

UntClose Code:

unit UntClose;

...

procedure AddBtnClick;
procedure EditBtnClick;
procedure DelBtnClick;

implementation

procedure AddBtnClick;
begin
    ShowMessage('add close');
end;

procedure EditBtnClick;
begin
   ShowMessage('edit close');
end;

procedure DelBtnClick;
begin
   ShowMessage('delete close');
end;
end.

I want to add a unit dynamically (if possible) to the frames' uses clause each time that it is created so that clicking on the frame's add, edit or delete button fires up the correct method (method from the UntOpen if Open button is clicked or method from UntClose if Close button is clicked).

Or is there more efficient way of doing this?

Thanks.

Upvotes: 0

Views: 576

Answers (3)

Rob Kennedy
Rob Kennedy

Reputation: 163317

You cannot use units dynamically; the uses clause cannot be manipulated at run time. You appear to want to change the unit list so that your frame's code, which already calls AddBtnClick, will magically choose the function from one unit or the other.

Delphi doesn't work that way. If you really want something like that, try Python.

Delphi units are a compile-time construct. They cannot be manipulated at run time. At compile time, the compiler sees your call to AddBtnClick, and it resolves — binds — the name then and there according to what units you've already used. You can't rebind the name later.

Instead, you can pass parameters to your frame class constructor. Put your button functions into data structures, and then pass those objects to the frame. (This is also known as dependency injection.) For example, you could start with this abstract class:

type
  TButtonBehavior = class
    class procedure AddBtnClick; virtual; abstract;
    class procedure EditBtnClick; virtual; abstract;
    class procedure DelBtnClick; virtual; abstract;
  end;

Then, define your frame to receive a descendant of that class, and write your button handlers in terms of that value:

type
  TButtonBehaviorClass = class of TButtonBehavior;

  TMainFrame = class(TFrame)
  private
    FBehavior: TButtonBehaviorClass;
  ...

constructor TMainFrame.Create(AOwner: TComponent; ABehavior: TButtonBehaviorClass);
begin
  inherited Create(AOwner);
  FBehavior := ABehavior;
end;

procedure TMainFrame.AddBtnClick(Sender: TObject);
begin
  FBehavior.AddBtnClick;
end;

Next, define descendants of the abstract class for each of the sets of behavior you want your frame to support. You can define them in separate units if you want, but since they're already in separate classes, further separation isn't required.

type
  TOpenBehavior = class(TButtonBehavior)
    class procedure AddBtnClick; override;
    class procedure EditBtnClick; override;
    class procedure DelBtnClick; override;
  end;

  TCloseBehavior = class(TButtonBehavior)
    class procedure AddBtnClick; override;
    class procedure EditBtnClick; override;
    class procedure DelBtnClick; override;
  end;

Finally, pass one of those classes to the frame constructor when you create a new instance of the frame.

Frame := TMainFrame.Create(Self, TOpenBehavior);

Upvotes: 5

mg30rg
mg30rg

Reputation: 1349

If I were you I would try this solution:

Main frame:

unit UntFrame;

...

uses
  UntOpen, UntClose, ...;

type
  TSelectedPage = (spOpen, spClose);

  TFmeMain = class(TFrame)
    procedure AddClick(Sender: TObject);
    procedure EditClick(Sender: TObject);
    procedure DeleteClick(Sender: TObject);
  private 
    FSelectedPage: TSelectedPage;
    ...
  public ...
  end;

implementation

{$R *.dfm}

uses ...

procedure TFmeMain.AddClick(Sender: TObject);
begin
  case FSelectedPage of:
    spOpen: TUntOpen.AddBtnClick;
    spClose: TUntClose.AddBtnClick;
  end;
end;

procedure TFmeMain.EditClick(Sender: TObject);
begin
  case FSelectedPage of:
    spOpen: TUntOpen.EditBtnClick;
    spClose: TUntClose.EditBtnClick;
  end;
end;

procedure TFmeMain.DeleteClick(Sender: TObject);
begin
  case FSelectedPage of:
    spOpen: TUntOpen.DelBtnClick;
    spClose: TUntClose.DelBtnClick;
  end;
end;

end.

Open unit:

unit UntOpen;

...
type
  TUntOpen = class 
  public
    class procedure AddBtnClick;
    class procedure EditBtnClick;
    class procedure DelBtnClick;
  end;

implementation

class procedure TUntOpen.AddBtnClick;
begin
    ShowMessage('add open');
end;

class procedure TUntOpen.EditBtnClick;
begin
   ShowMessage('edit open');
end;

class procedure TUntOpen.DelBtnClick;
begin
   ShowMessage('delete open');
end;
end.

Close unit:

unit UntClose;

...
type
  TUntClose = class 
  public
    class procedure AddBtnClick;
    class procedure EditBtnClick;
    class procedure DelBtnClick;
  end;

implementation

class procedure TUntClose.AddBtnClick;
begin
    ShowMessage('add close');
end;

class procedure TUntClose.EditBtnClick;
begin
   ShowMessage('edit close');
end;

class procedure TUntClose.DelBtnClick;
begin
   ShowMessage('delete close');
end;
end.

This is the OOP way to do what you are trying to achieve, and it is possible in Delphi.

Upvotes: 0

Remy Lebeau
Remy Lebeau

Reputation: 597051

Try something more like this instead:

unit UntFrame;

...

type
  TFmeMain = class(TFrame)
    procedure AddClick(Sender: TObject);
    procedure EditClick(Sender: TObject);
    procedure DeleteClick(Sender: TObject);
  public
    OnAdd: procedure;
    OnEdit: procedure;
    OnDelete: procedure;
  end;

implementation

{$R *.dfm}

procedure TFmeMain.AddClick(Sender: TObject);
begin
  if Assigned(OnAdd) then OnAdd;
end;

procedure TFmeMain.EditClick(Sender: TObject);
begin
  if Assigned(OnEdit) then OnEdit;
end;

procedure TFmeMain.DeleteClick(Sender: TObject);
begin
  if Assigned(OnDelete) then OnDelete;
end;

end.

unit MainForm;

...

uses
  ..., UntFrame, UntOpen, UntClose;

...

procedure TFormMain.OpenClick(Sender: TObject);
begin
  with TFmeMain.Create(Self) do
  begin
    Parent := ...;
    OnAdd := Addr(UntOpen.AddBtnClick);
    OnEdit := Addr(UntOpen.EditBtnClick);
    OnDelete := Addr(UntOpen.DelBtnClick);
  end;
end;

procedure TFormMain.CloseClick(Sender: TObject);
begin
  with TFmeMain.Create(Self) do
  begin
    Parent := ...;
    OnAdd := Addr(UntClose.AddBtnClick);
    OnEdit := Addr(UntClose.EditBtnClick);
    OnDelete := Addr(UntClose.DelBtnClick);
  end;
end;

end.

Upvotes: 0

Related Questions