How can I access a delphi control from outside the form's unit?
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)
...
精彩评论