【问题标题】:How can I access a delphi control from outside the form's unit?如何从表单单元外部访问 delphi 控件?
【发布时间】:2010-12-13 12:13:22
【问题描述】:

我正在尝试从如下定义的过程中调用 Timer 的 Enabled 属性:procedure Slide(Form: TForm; Show: Boolean);,而不是使用固定的表单名称(例如:Form2.Timer...

将表单的单元放入使用列表后,可以:Form2.Timer1.Enabled := True; 但以下不起作用:Form.Timer1.Enabled := True;(其中 Form 是作为参数传递给过程的表单。

如何访问我的表单上的 Timer 组件?

提前致谢。

【问题讨论】:

  • 这听起来和你之前的问题一样:stackoverflow.com/questions/4404206/…
  • 大卫+1;和一个“关闭”请求。
  • 不,正如我在下面发布的:问题是我试图从这样定义的过程中调用 Enabled 属性:procedure Slide(Form: TForm; Show: Boolean);而不是固定的形式名称。将表单的单位放入使用列表后,这有效: Form2.Timer1.Enabled := True;但以下不起作用: Form.Timer1.Enabled := True; (其中 Form 是作为参数传递给过程的表单。
  • 为什么不在幻灯片功能中创建计时器?这使您不必在需要滑动的每种形式中声明一个 TTimer,这听起来像是一个糟糕的设计选择。
  • @emurdad:详细说明我之前的评论,因为我怀疑您可能不太理解我的意思,在您的名为 Slide 的函数中声明 TTimer 类型的局部变量,使用 TTimer.Create 和创建它使用此计时器来控制您的幻灯片。或者,在主窗体中添加一个计时器,并使 Slide 函数成为主窗体的方法。

标签: delphi pascal


【解决方案1】:

如果您要传递给函数的每个表单都有一个名为“Timer1”的已发布字段,那么您可以使用FindComponent 方法获取对它的引用:

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;

不过,这是一个相当弱的编程接口。如果你不小心忽略了在表单上放置一个计时器,或者你给了它错误的名字,或者你给了它一个不同的可见性,你直到运行时才发现你的错误(当断言失败)。即使表单确实符合要求的界面,也不能保证它是故意的。可能有很多表单已经发布了名为Timer1TTimer 字段,但它们并不都适用于这个Slide 函数。他们可能已经将他们的计时器用于其他目的,因此对他们调用 Slide 会破坏程序的其他部分,可能会以难以调试的方式。

如果您给计时器起一个更具描述性的名称,例如SlideTimer,这将是一个稍微强大的界面。 Timer1 只是说这是您在该表单上创建的第一个 TTimer,一旦您离开表单设计器,这将不再是一个有意义的名称。您不需要使用 IDE 的命名选项。


另一个更好的选择是让所有可滑动表单都有一个公共基类,并将计时器放在该基类中。然后,更改Slide 中的参数类型以采用该表单类,而不仅仅是TForm

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

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

您似乎担心必须在 Slide 单元中包含对 Form2 单元的引用,这通常是一个很好的考虑。你不希望你的代码耦合得太紧,因为这个Slide 函数应该能够在一个项目中使用不止一种形式。但是如果它太松,那么你就会遇到我上面描述的问题。这个TSlidableForm 类是一种妥协;您的 Slide 函数未直接绑定到 TForm2 或其单元。将TForm2 更改为TSlidableForm 的后代。


The_Fox's second suggestion 是我第一个的变体,但还有另一个变体。您可以传递对组件本身的引用,而不是传递计时器组件的名称

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

现在您不需要使用FindComponent 来搜索要使用的计时器;您提供了对它的直接引用。组件的名称甚至无关紧要,因此不同的表单可以使用不同的名称。你可以这样称呼它:

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

一旦您走到这一步,您可能会超越最初的问题,并意识到计时器甚至不再需要属于表单。您可以专门为调用Slide 创建它,或者计时器可以完全属于其他东西(如数据模块)。但是,如果您要为 Slide 调用创建计时器,那么您可以使用 David 的建议,即在例程本身中创建计时器,而根本不需要调用者或表单处理它:

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;

【讨论】:

  • -1 放弃类型安全不是好的做法,也没有迫切的需要。
  • 这种被遗弃的类型安全性以什么方式存在,@David?两个版本都检查他们收到的参数是否满足所需的接口。这些技术允许解耦,因此Slide 函数将适用于任何形式,而不仅仅是Form2。我更喜欢我的第二个建议,为所有形式提供一个通用基类,但这取决于 Emurad。
  • @Rob 你说得对,我不是说类型安全,我在写之前应该三思而后行!我的意思是静态类型而不是类型安全。我仍然认为这是一种非常令人反感的方式。按要求回答问题并不能真正帮助@emurad,他似乎对编程很陌生。他需要帮助和鼓励才能退后一步,以不同的方式看待他的问题。
  • @Sertac 在我看来,就其价值而言,专业工程师应该始终质疑正在解决的问题是否真的是正确的问题。
  • @Rob 谢谢你,我对 SO 比较陌生,所以这对我很有帮助。我会说您的 CreateMessageDialog 示例有点虚伪,因为您无法控制该例程的实现,而 OP 确实可以控制所有相关表单。这就是我将其归类为离开静态类型系统的令人信服的理由。
【解决方案2】:

您不能从您的过程中访问 Timer,因为您的参数是 TForm,而 TForm 没有 Timer1 成员。你必须像这样调整你的程序:

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;

编辑:

如果你想通过任何形式,你可以试试这个:

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;

从表单上的按钮这样调用:

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

或者你让你的表单继承一个带有方法的接口来打开定时器。不过有点复杂。

【讨论】:

  • 这是硬编码的。我不能这样做 - 如果我不能传递另一个表单作为参数传递有什么好处!?
  • 如果传递TForm,则只能访问TForm的属性和方法,而不能访问TForm1的属性和方法,TForm1继承自TForm并具有额外成员。
【解决方案3】:

将该单位添加到使用列表中。

...
implementation

uses Unit2;

{$R *.dfm}
...

【讨论】:

    【解决方案4】:

    在当前单元的使用列表中使用Form2的单元名称。

    供您编辑:

    通过使用TForm,您无法访问TTimer,因为TForm 具有no fields or properties as TTimer.

    所以你需要使用TForm1 as the parameter

    如果您有一个表单,比如 Form1,您要为其创建多个实例并向用户显示,然后将您的过程语法更改为 procedure Slide(Form: TForm1; Show: Boolean);

    但是,如果您有多个具有不同组件的表单,那将变得很困难。您需要使用不同的参数重载过程。下面的代码显示了该方法。

    Form1 单元

    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    uses Unit3;
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
    Slide(Form1, True)
    end;
    

    Form2 单元

    var
      Form2: TForm2;
    
    implementation
    
    {$R *.dfm}
    
    uses Unit3;
    
    procedure TForm2.Button1Click(Sender: TObject);
    begin
    Slide(Form2, True)
    end;
    

    您的程序所在的单元

    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.
    

    【讨论】:

      【解决方案5】:

      只是为了添加更多信息。

      在 Delphi 项目中,代码被组织成单元。每个单元都是一个带有名称和 .pas 扩展名的文件。

      单位有以下形式:

      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.
      

      一个单元可以使用该单元中定义的任何东西,只要它在被使用之前被定义。

      如果名称相同,有时这会导致混乱:

      unit1;
      interface
      type
        TTest = Integer;
      // ...
      
      unit2;
      interface
      type
        TTest = Boolean;
      // ...
      
      unit3;
      interface
      uses
        unit1, unit2;
      var
        a: TTest;  // Which TTest?
      // ...
      

      您可以通过了解评估顺序或使用单位前缀来解决此问题:

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

      在您的情况下,您需要使用定义 Form2 的单位。

      如果两个表单都需要计时器,您还可以使用数据模块(就像表单一样,您可以将不可见的组件拖到其中但它们不可见)。然后两种形式都可以使用数据模块。

      编辑

      你尝试使用:

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

      而 TForm 没有 Timer1。

      您可以执行以下操作:

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

      如果 TForm2 是包含 Timer 的表单。

      【讨论】:

      • 问题是我试图从这样定义的过程中调用 Enabled 属性:procedure Slide(Form: TForm; Show: Boolean);而不是固定的形式名称。将表单的单位放入 uses 列表后,它可以工作: Form2.Timer1.Enabled := True;但以下不起作用: Form.Timer1.Enabled := True; (其中 Form 是作为参数传递给过程的表单。
      【解决方案6】:

      最简单的方法是在表单上找到它:

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

      为了安抚大卫 ;-),这里有一个更“类型安全”的替代方案:

      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;♦♠
      

      【讨论】:

      • -1 放弃类型安全不是好的做法,也没有迫切的需要。
      • 它并没有放弃类型安全,因为大概 OP 足够聪明,只能传递一个上面有计时器的表单。但是,为了防止您发布另一个关于没有类型安全 的错误评论,我进行了编辑以提供更安全的替代方案。
      【解决方案7】:

      实现这一点的一种简单(和 OOP)方法是使用接口。

      声明一个接口 ISlideable,它定义了一个 SlideTimer 属性(带有 getter 和 setter 方法),然后像这样编写 Slide 方法

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

      每个应该传递的表单都说它是可滑动的

      MyFormN = class(TForm, ISlideable)
      ...
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-11-16
        • 2013-04-26
        • 1970-01-01
        • 1970-01-01
        • 2011-01-06
        • 2019-09-23
        • 1970-01-01
        相关资源
        最近更新 更多