【问题标题】:ShowModal for an associated form关联表单的 ShowModal
【发布时间】:2025-12-16 20:30:02
【问题描述】:

我有一个具有以下属性的组件:

property Form: TForm read FForm write SetForm
property BtnOK: TButton read FBtnOK write SetBtnOK

后面的过程是这样的:

procedure Execute_FormShowModal;

我想在执行Execute_FormShowModal 时打开关联的表单(例如FormUser)。

我想提一下,关联的表单已经定义并存在,但没有创建。

有没有可能做到这一点?

procedure TMyComp.Execute_FormShowModal;
var
  frm: TForm;
begin
  frm:= TForm(FForm.ClassName).Create(FParentForm); //Access Violation...
  //... here I would like to play also this the elements from this form
  //like: BtnOK.Enabled:= False;
  frm.ShowModal;
  frm.Free;
end;

【问题讨论】:

  • 当您调用该方法时,FForm 是一个有效的引用吗?
  • 是的,当然。我已将 Form 的值设置为现有的有效形式,例如 FormUser
  • 啊,现在我看到了一个问题。您正在尝试将 ClassName 类型转换为一个类。它应该是ClassType
  • 让我们举个简单的例子:procedure MainForm.BtnTestClick(Sender: TObject); var frm: TForm; begin frm:= TForm(FormUser.ClassType).Create(Self); frm.ShowModal; frm.Free; end; Access Violation... :(
  • 如果你想实例化 FForm,你不需要'frm'。 FForm := TForm.Create(Self)

标签: forms delphi


【解决方案1】:

您在 cmets 中添加了您将 FForm 设置为等于有效的现有表单。如果是这样,您可能不需要创建任何东西:

procedure TMyComp.Execute_FormShowModal;
var
  frm: TFormUser;

begin
   frm:= TFormUser(FForm);

   frm.BtnOK.Enabled:=False;

   frm.ShowModal;
   //frm.Free;
end;

这假定您所引用的这个有效实例已声明

type
   TFormUser = class(TForm)
      BtnOK : TButton;

      // etc...
   end;

如果您尝试制作表单的副本,您可以使用:

procedure TMyComp.Execute_FormShowModal;

var
   frm: TFormUser;

begin
   frm:= TFormUser(TFormClass(FForm.ClassType).Create(FParentForm)); 

   // which is no different than:

   frm:= TFormUser.Create(FParentForm)); 

   frm.BtnOK.Enabled:=False;
   frm.ShowModal;
   frm.Free;
end;

如果要操作表单上的控件(即 BtnOK),那么您需要知道表单的类类型(本例中为 TFormUser)。因此,既需要知道表单的确切类类型,又想从设计时建立的类型中实例化表单,这是矛盾的。


由于您可能试图在不“知道”其绝对类型的情况下实例化表单,因此您的 FForm 属性应该是表单的

假设您没有在组件中发布“Form”属性,我将对您的组件进行以下更改:

TMyComp = class(TComponent)
   FFormClass : TFormClass;

   procedure SetFormClass(Value : TFormClass);
   property  FormClass: TFormClass read FFormClass write SetFormClass;

   procedure Execute_FormShowModal;
end;

您提到的初始化代码可能如下所示:

begin
  // .....

  //MyComp.Form := FormUser1;

  MyComp.FormClass := TFormUser;

  // .....

end;

然后“Execute_FormShowModal”就变成了:

procedure TMyComp.Execute_FormShowModal;

var
   frm: TForm;

begin
   // Check that FFormClass is not nil and perform some alternate
   //  action.
   // if FFormClass = nil then ......
   //
   frm:= FFormClass.Create(FParentForm); 
   frm.ShowModal;
   frm.Free;
end;

当然,您可能还想添加一些代码来检查 FFormClass 是否为 nil,如果是,则执行一些替代行为,例如引发异常或显示一些消息,甚至实例化默认表单。


如果您要发布 Form 属性,那么它将无法处理您的 FForm 字段值为 nil 的情况,因为您不知道或没有特定的类类型来实例化 Form。那就是:

frm:= TFormClass(FForm.ClassType).Create(FParentForm); 

只会显示一个空白的空表单。


如果你想发布这个属性,你可以尝试把它变成一个字符串类型,它带有你想要实例化的表单类的name,然后使用 RTTI 来查找类:

uses RTTI;

TMyComp = class(TComponent)
   FFormClassName : string;

   procedure SetFormClassName(const Value : string);
   property FormClassName: string read FFormClassName write SetFormClassName;

   procedure Execute_FormShowModal;
end;


procedure TMyComp.Execute_FormShowModal;

var
   frmCls : TFormClass;
   frm: TForm;

   RTTI : TRTTIContext;
   RTTIType : TRTTIType;

begin
   frmCls := nil;
   for RTTIType in RTTI.GetTypes do
      begin
         if (RTTIType.Name = FFormClassName) and (RTTIType.TypeKind = tkClass) then
            begin
               if RTTIType.Handle.TypeData.ClassType.InheritsFrom(TForm) then
                  begin
                     frmClass := TFormClass(RTTIType.Handle.TypeData.ClassType);
                     break;
                  end;
            end;
      end;

   // Check that frmCls is not nil and perform some alternate
   //  action.
   // if frmCls = nil then ......
   //
   frm:= frmCls.Create(FParentForm); 
   frm.ShowModal;
   frm.Free;
end;

【讨论】:

  • 您对property FormClass 的回答的第一部分部分有效。不幸的是,在表单打开之前和关闭之后,我收到了这个错误:Invalid pointer operation。关于 RTTI,我测试很快,它不起作用Access Violation。我会坚持的。
  • 抱歉 - 刚刚意识到 RTTI 代码示例中存在严重的格式错误。请重试代码,它应该可以工作。我使用的是 Delphi 10.2,但这应该可以在任何版本的 Delphi 中使用,直到引入新的 RTTI 时。
  • “无效的指针操作”您能否以某种方式显示生成消息的确切代码。某些东西可能以某种方式被释放了两次。
  • “我会坚持”——对不起,我不知道这是什么意思。
  • 我现在不在电脑上,但它在对象的空闲内存中。我会带着明确的答案回来。我会坚持,这意味着我会尝试更多地了解和测试您的 RTTI 解决方案。第一次测试没有成功。
【解决方案2】:

有没有可能做到这一点? 是的

试试这样的

uses uFForm; // Add the unit name that defined the associated form to your (TMyComp) unit uses clause

procedure TMyComp.Execute_FormShowModal;
begin
   with TFForm.Create(Self) do  //TFForm is the child form
   begin
    //... here I would like to play also this the elements from this form
    BtnOK.Enabled:= False;
    Show;
   end;
end;

【讨论】:

    【解决方案3】:

    第一个问题是您尝试将 ClassName 类型转换为您尝试实例化的类类型。相反,您希望使用可以通过 ClassType 方法从对象实例获得的元类。下一个问题是,对于这样的元类,您需要将类型转换为元类,而不是类,因此不要将类型转换为 TForm 类转换为其元类 TFormClass.

    对于您问题的下一部分,如果您可以一般访问声明为公共类祖先的对象的特定类成员,不,这是不可能的。作为一种解决方法,您必须确定对象类类型并通过对该类进行类型转换来访问它,或者使用更困难的 RTTI。

    试试这样的:

    procedure TMyComp.Execute_FormShowModal;
    var
      frm: TForm;
    begin
      frm := TFormClass(FForm.ClassType).Create(FParentForm);
    
      { to acess the class specific members you will have to typecast to a
        specific class (or use RTTI, which is even more difficult) }
      if frm is TMyForm then
        TMyForm(frm).BtnOK.Enabled := False;
    
      frm.ShowModal;
      frm.Free;
    end;
    

    【讨论】:

    • 我认为 OP 已经定义了一个表单 FormUser: TFormUser (类只是我的猜测,但并不重要)。此表单不是自动创建的。然后他用 FormUser 分配了组件的 Form 属性(当然是 nil)。因此FForm.ClassName 会触发 AV,FForm.ClassType 也会触发。只是一个理论,他没有回答我的最后一个问题。
    • @Tom,那么问题的下一条评论不正确。有效引用是指对有效对象的引用(也许我应该在那里更具体)。不过,您不能将 ClassName 类型转换为 TForm 类。相反,您需要使用元类。虽然我还没有测试我的答案中的代码(现在没有 Delphi)。
    • 嗯,他说他得到了一个 AV,也在另一条评论中。我看不出他的代码会如何触发 AV。您可能希望将这种可能性视为您的答案中的一种可能性。哦,如果他已经分配了一个现有的表单,他可以直接使用 FormUser。
    • @Tom,如果通过尚未实例化的对象声明分配字段值不足以确定类类型,那么您是对的(正如我所说,我现在无法验证这一点)。随意使用这篇文章中的任何内容来形成你自己的(我可以删除这个)。