奇怪的是“名为TFrm1的组件已存在”错误

时间:2011-02-01 11:14:29

标签: delphi


我想让用户创建同一表单的多个实例(让我们称之为Form1,这是一个MDI子表单)。所以我有两个这样的程序,我在那里创建表单。

procedure MyProcedure1;           // procedure 2 is similar. it also has a var called MyFrm
var MyFrm: TFrm1;
begin
  ... 
  MyFrm:= TFrm1.create(MainForm);
  MyFrm.BringToFront;
  MyFrm.LoadFromFile(someFile);
end;

正如您所见,MyFrm是本地变量。这对我来说没问题,因为我创建它之后不需要以编程方式访问表单。没有其他名为Frm1的全局变量。在MyFrm的OnClose事件中,我有Action:= caFree;

什么可能导致上述错误? 用户发送了该错误。它只发生一次,我无法重现它。


修改:

  1. 错误出现在“MyFrm:= TFrm1.create”行中。

  2. 有些人建议我需要以编程方式为动态创建的表单提供唯一名称。我也想知道自己在创建表单时采用了什么名称,所以我在调用MyProcedure1程序时进入了代码。
    德尔福自动给出像
    这样的独特名称 MyFrm.name = MyFrm,然后是 MyFrm.name = MyFrm_1,
    MyFrm.name = MyFrm_2,
    MyFrm.name = MyFrm_3,依此类推。

  3. LoadFromFile中未更改MyFrm.Name。我在程序MyProcedure1的末尾检查了(断点)'MyFrm.Name'的值;在LoadFromFile之后。这个名字很独特。

  4. 正如有些人建议的那样,我重写了SetName过程并检查了TMyFrm的名称。事实上,每个表格都有一个独特的名称。

    程序TMyFrm.SetName(const值:TComponentName);
    开始
       ShowMessage(值);
       继承;
    结束;

  5. 我在这个应用程序中有很多表单,但只有MainForm是自动创建的。

  6. 我不使用线程。无论如何,这是不相关的,因为表单是由用户创建的(因此多线程是无关紧要的,除非用户可以同时创建2个表单)。

4 个答案:

答案 0 :(得分:8)

MainForm 作为 TFrm1.Create 中的所有者,将新创建的表单包含在MainForm的组件列表中。组件确保此列表不包含具有相同非空名称的任何两个组件(否则FindComponent将不起作用)。当组件更改其名称时,此机制也有效。

只要您没有在TFrm1.Create中指定名称,它很可能是由LoadFromFile方法设置的,这意味着除非您更改文件的内容,否则对名称的影响不大。

一个有效的解决方法是使用nil作为所有者创建表单,从文件加载表单,将名称更改为唯一值或更改为空字符串,最后调用MainForm.InsertComponent。

procedure MyProcedure1;           
var MyFrm: TFrm1;
begin
  ... 
  MyFrm:= TFrm1.create(nil);
  MyFrm.BringToFront;
  MyFrm.LoadFromFile(someFile);
  MyFrm.Name := ''; // or some unique name
  MainForm.InsertComponent(MyFrm);
end;

答案 1 :(得分:6)

导致消息,因为每个表单必须唯一命名。

当您创建表单两次时,您需要确保每个实例都有唯一的名称或将Name设置为空字符串。后者也是使用数据模块的多个实例时的技巧,因此数据感知控件的自动链接不会总是使用第一个实例。

添加

<击>

<击>
MyFrm.Name := MyFrm.Name + <something unique>;

<击>

MyFrm.Name := '';

在Create调用之后你应该没问题

答案 2 :(得分:3)

MyFrm.Name对于两个实例都是相同的......

确保MyFrm.Name是唯一的......

答案 3 :(得分:0)

就我在这条线上的探索而言,是的,“已经存在”的问题源于编辑器的具有相同值的Name属性。因为另一种解决方法是不能直观地创建编辑器。为您希望用户能够创建多个实例的编辑器创建基于TForm / TFrame / TPanel的新组件。你将不得不手工编写创作和编码。删除任何子控件,在代码中设置它们的属性并赋值 - 从V_Btn = new TBitBtn(this),V_Btn-&gt; Color = clTeal到V_Btn-&gt; OnClick = Close_The_Window。但是,永远不要为新类中任何组​​件的Name属性赋值,并且在创建编辑器实例后不要设置编辑器的Name属性。将编辑器的Name属性视为不存在。创建类并将其添加到项目后,以下内容有效:

TMyeditor* Editor_01 = new TMyeditor(Main_Form);
TMyeditor* Editor_02 = new TMyeditor(Main_Form);
  Editor_01->Parent = Tab_Sheet_Addresses;
  Editor_02->Parent = Tab_Sheet_Billing;

编辑器的设计理念越复杂,您就会越努力地编写课程代码。但是,这种方法将解决“已存在”错误。

            End of answer.

以下内容与原始问题相符,因为它是可能想要进一步处理您的代码的扩展。如果是这样的话,我会写它来帮助你。以下内容允许您有效地存储/检索编辑器及其发布的属性,例如用户屏幕上的位置,主题等。如果您已经走上述路线,请添加以下内容:

void RegisterClassesWithStreamingSystem(void)
{   
  // Make sure that as part of the startup   
  // code TMyEditor is registered   
  // with the streaming system.   
  #pragma startup RegisterClassesWithStreamingSystem  
  Classes::RegisterClass(__classid(TMyEditor)); 
}

你现在可以使用ComponentToString&lt; ---&gt; StringToComponent [* 1]编辑器。 您现在可以创建一个简单的数据库,每个编辑器保存它[* 2]并在运行时重新创建编辑器。拯救和重新创建几乎完全由TReader / TWriter对象完成。 {值得一读的有关Delphi帮助文件中包含的TReader / TWriter}

[假设您有一个想要保存的TMyEditor实例,名为Editor_01&amp; Editor_02和 您已创建数据集并将其分配给名为“CDS”的TClientDataSet

//How to write the Editors
String_Version_Of_Editor = ComponentToString(Editor_01);
CDS->Insert();
CDS->FieldByName("Data")->AsString = String_Version_Of_Editor;
CDS->Post();
String_Version_Of_Editor = ComponentToString(Editor_02);
CDS->Insert();
CDS->FieldByName("Data")->AsString = String_Version_Of_Editor;
CDS->Post();

//How to read, create an instance of, set the Owner of 
//(allowing for automatic destruction/deletion
// if desired, Vis-à-vis Let the compiler/runtime package handle that), 
//& setting the form's Parent
AnsiString   String_Version_Of_Editor; 
TWinControl* New_Editor;
String_Version_Of_Editor = CDS->FieldByName("Data")->AsString;
//The next line creates/constructs the new editor
New_Editor = StringToComponent(String_Version_Of_Editor);
//The next line sets the new editor's Owner to Main_Form 
//It also assigns Main_Form the responsibility of object cleanup
Main_Form->Insert(New_Editor);
//The next line sets the Editor's Parent causing it to be part of the 
//displayed user interface (it has been invisble since creation)
New_Editor->Parent = Tab_Sheet_Addresses;
//Move on to the next editor;
CDS->Next();
String_Version_Of_Editor = CDS->FieldByName("Data")->AsString;
New_Editor = StringToComponent(String_Version_Of_Editor);
Main_Form->Insert(New_Editor);
New_Editor->Parent = Tab_Sheet_Billing;

阅读上述精明内容的人会注意到,在上面的代码中,New_Editor的类型为TWincontrol 而不是 TMyEditor - 虽然它可能应该是。但是我这样做是为了引起人们注意这样一个事实,即Delphi中的TReader对象实际上正在进行将字符串转换为组件对象实例的工作,这会创建/构造任何对象,该对象已经注册了通过RegisterClass传输流。以这种方式,通过明​​确地命名它的类型来避免显式创建编辑器。如果考虑到TMyEditor及其后代的设计,则代码所需的唯一更改是将TWinControl *更改为TMyEditor * - 即使超出TWinControl *之外的已发布属性不在TMyEditor范围之外访问也不需要 - 例如TMyEditor可以访问正在编辑其值的变量,并且不需要将此信息提供给编辑器。(如果使用DataModule,则将数据模块的头部包含在TMyEditor中)。

旁注: 您可以使用实用程序来了解从数据库中读取的类,以便您可以将实例放在它所属的位置。要在代码中#include <typeinfo>执行此操作。 示例:如果您将TMyEditor,TMyEditor_Generation_01,TMyEditor_Generation_02等实例写入数据库,则以下内容将允许您检查在运行时读取的实例以放置到用户界面中:

 if (typeid(New).name() == "TMyEditor *")
   New_Editor->Parent = Tab_Sheet_Addresses;
  else
   if (typeid(New).name() == "TMyEditor_Generation_01 *")
     New_Editor->Parent = Tab_Sheet_Billing;
   else
    if (typeid(New).name() == "TMyEditor_Generation_02 *")
      New_Editor->Parent = Tab_Sheet_Other_Editor;

typeid( _ _ )。name()将返回一个字符串,该字符串是该类的名称,在这种情况下也将包含“*”。

以上允许将 ANY 对象存储在数据库中并重新创建。数据库中的条目不需要相关。隐藏在Delphi代码中的TReader对象将在运行时决定它们是什么,并使用正确的构造函数。

[* 1]注意:ComponentToString和StringToComponent是delpi / c ++帮助文件中的示例。

[* 2]注意:保存的是已发布的属性,因此在您的编辑器类中您希望存储和检索的任何值尚未继承,发布应该在新类的__published部分声明。这些项也可以是自定义对象,对于那些您可能在定义_property时为读/写访问说明符编写自定义特定方法/函数的对象。我建议将任何复杂对象转换为字符串值,以便在开发过程中检查代码。