开发者

How can I access a delphi control from outside the form's unit?

开发者 https://www.devze.com 2023-01-30 20:36 出处:网络
I\'m t开发者_JAVA技巧rying to call the Enabled property of a Timer from a procedure defined like this: procedure Slide(Form: TForm; Show: Boolean); and not with a fixed form name (like: Form2.Timer...

I'm t开发者_JAVA技巧rying to call the Enabled property of a Timer from a procedure defined like this: procedure Slide(Form: TForm; Show: Boolean); and not with a fixed form name (like: Form2.Timer...)

After putting the form's unit in the uses list, this works: Form2.Timer1.Enabled := True; but the following is not working: Form.Timer1.Enabled := True; (where Form is the form passed as parameter to the procedure.

How to get access to the Timer component on my form?

Thanks in advance.


If every form you're going to pass into your function will have a published field named "Timer1," then you can use the FindComponent method to get a reference to it:

procedure Slide(Form: TForm; Show: Boolean);
var
  TimerObj: TComponent;
  Timer: TTimer;
begin
  TimerObj := Form.FindComponent('Timer1');
  Assert(Assigned(TimerObj), 'Form has no Timer1');
  Assert(TimerObj is TTimer, 'Form.Timer1 is not a TTimer');
  Timer := TTimer(TimerObj);
  // Continue using Form and Timer
end;

That's a rather weak interface to program against, though. If you've accidentally neglected to place a timer on your form, or if you've given it the wrong name, or if you've given it a different visibility, you won't discover your mistake until run time (when the assertions fail). And even if the form does meet the required interface, there's no guarantee that it was intentional. There may be lots of forms that have published TTimer fields named Timer1, but they're not all meant to be used with this Slide function. They might already be using their timers for other purposes, so calling Slide on them will break other parts of your program, possibly in difficult-to-debug ways.

It would be a slightly stronger interface if you gave the timer a more descriptive name, such as SlideTimer. Timer1 merely says it was the first TTimer you created on that form, and once you leave the Form Designer, that ceases to be a meaningful designation. You are not required to use the IDE's naming choices.


Another, better, option is to make all your slidable forms have a common base class, and put the timer in that base class. Then, change the parameter type in Slide to take that form class instead of just TForm.

type
  TSlidableForm = class(TForm)
    Timer1: TTimer;
  end;

procedure Slide(Form: TSlidableForm; Show: Boolean);

You seemed concerned that you would have to include a reference to Form2's unit in your Slide unit, and that's generally a good concern to have. You don't want your code to be too tightly coupled since this Slide function should be able to work with more than just one form in one project. But if it's too loose, then you run into the problems I described above. This TSlidableForm class is a compromise; your Slide function isn't bound directly to TForm2 or its unit. Change TForm2 to descend from TSlidableForm.


The_Fox's second suggestion is a variation on my first, but there's still another variation. Instead of passing the name of the timer component, you can pass a reference to the component itself:

procedure Slide(Form: TForm; Timer: TTimer; Show: Boolean);

Now you don't need to use FindComponent to search for the timer to use; you've provided a direct reference to it. The component's name doesn't even matter, so different forms can use different names. You can call it like this:

Slide(Form2, Form2.Timer1, True);
Slide(AnotherForm, AnotherForm.SlideTimer, False);

Once you've come this far, you may go beyond your original question and realize that the timer doesn't even need to belong to the form anymore. You could create it specially for the call to Slide, or the timer could belong to something else entirely (like a data module). But if you're going to create the timer just for the Slide call, then you could use David's suggestion of creating the timer within the routine itself and not have the caller or the form deal with it at all:

procedure Slide(Form: TForm; Show: Boolean);
var
  Timer: TTimer;
begin
  Timer := TTimer.Create(nil);
  try
    Timer.OnTimer := ...;
    Timer.Interval := 500;
    Timer.Enabled := True;

    // Put your normal Slide stuff here
  finally
    Timer.Free;
  end;
end;


You cannot access the Timer from your procedure because your parameter is a TForm, and TForm does not have a Timer1 member. You have to adjust your procedure like this:

uses
  Unit2; //unit with your form

procedure Slide(Form: TForm2; Show: Boolean); //change TForm2 to the classname you use
begin
  Form.Timer1.Enabled := True;
end;

Edit:

If you want to pass any form, you can try this:

procedure Slide(Form: TForm; const aTimerName: string; Show: Boolean);
var
  lComponent: TComponent;
begin
  lComponent := Form.FindComponent(aTimerName);
  if Assigned(lComponent) and (lComponent is TTimer) then
    TTimer(lComponent).Enabled := True;
end;

Call like this from a button on your form:

procedure TForm2.Button1Click(Sender: TObject);
begin
  Slide(Self, 'Timer1', False);
end;

Or you let your Form inherit an interface with methods to turn on the timer. It's a little bit more complicated though.


Add the unit to the uses list.

...
implementation

uses Unit2;

{$R *.dfm}
...


Use Form2's unit name in uses list of current unit.

For your Edit:

By using TForm you cant access TTimer because TForm has no fields or properties as TTimer.

So you need to use TForm1 as the parameter.

If you are having a form say Form1, for which you are creating multiple instances and showing to the user, then change your procedure syntax to procedure Slide(Form: TForm1; Show: Boolean);

But if you are having multiple forms with different components, it will become difficult. You need to overload procedures with different parameters. Below code shows the approach.

Form1 Unit

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses Unit3;

procedure TForm1.Button1Click(Sender: TObject);
begin
Slide(Form1, True)
end;

Form2 Unit

var
  Form2: TForm2;

implementation

{$R *.dfm}

uses Unit3;

procedure TForm2.Button1Click(Sender: TObject);
begin
Slide(Form2, True)
end;

Unit in which your procedures lies

unit Unit3;

interface

uses Forms, ExtCtrls, Unit1, Unit2;

procedure Slide(Form: TForm1; Show: Boolean) overload;
procedure Slide(Form: TForm2; Show: Boolean) overload;

implementation


procedure Slide(Form: TForm2; Show: Boolean);
begin
  Form.Timer1.Enabled := True;
end;


procedure Slide(Form: TForm1; Show: Boolean);
begin
  Form.Timer1.Enabled := True;
end;

end.


Just to add some more information.

In a Delphi project, code is organised into units. Each unit is a file with a name and a .pas extention.

The unit has the following form:

unit Name;

interface
uses
  // The definition of these units can be used both in the 
  // interface as in the implementation section.
  unit1, unit2;  

// Public interface, visible to other units that use this unit.

implementation
uses
  // The definition of these units can be used only in the 
  // implementation section.
  unit3, unit4;

// Private implementation, not visible outside.

initialization
  // code to initialize the unit, run only once
finalization
  // code to cleanup the unit, run only once
end.

A unit can use anything defined in the unit as long as it is defined before it is beïng used.

Sometimes this leads to confusing situations if names are identical:

unit1;
interface
type
  TTest = Integer;
// ...

unit2;
interface
type
  TTest = Boolean;
// ...

unit3;
interface
uses
  unit1, unit2;
var
  a: TTest;  // Which TTest?
// ...

You can solve this by either knowing the evaluation order, or use a unit prefix:

unit3;
interface
uses
  unit1, unit2;
var
  a: unit1.TTest;  // TTest from unit1
  b: unit2.TTest;  // TTest from unit2
// ...

In your case, you need to use the unit in which Form2 is defined.

If the timer is needed by both forms, you can also use a datamodule (just like a form, you can drag nonvisible components to it but they won't be visible). Both forms then can use the datamodule.

Edit

You try to use:

procedure Slide(Form: TForm; Show: Boolean); 

And TForm has no Timer1.

You can do the following:

procedure Slide(Form: TForm2; Show: Boolean); 

If TForm2 is the form containing the Timer.


The easiest way is to find it on the form:

procedure Slide(Form: TForm; Show: Boolean);
var
  Timer: TTimer;
begin
  Timer := Form.FindComponent('Timer1');
  if Assigned(Timer) then
    Timer.Enabled := True;
end;

To appease David ;-), here's a more "type-safe" alternative:

procedure Slide(Form: TForm; Show: Boolean);
var
  i:  Integer;
begin
  // Could use a TComponent and for..in instead. This works in
  // all Delphi versions, though.
  for i := 0 to Form.ComponentCount - 1 do
    if Form.Components[i] is TTimer then
      TTimer(Form.Components[i]).Enabled := True;
end;♦♠


A simple (and OOP) way to implement this is to use interfaces.

Declare an interface ISlideable which defines a SlideTimer property (with getter and setter methods) and then write the Slide method like

Slide(const Target: ISlideable; Show: Boolean);

And every form which should be passed says that it is Slideable

MyFormN = class(TForm, ISlideable)
...
0

精彩评论

暂无评论...
验证码 换一张
取 消