我想让用户创建同一表单的多个实例(让我们称之为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;
什么可能导致上述错误? 用户发送了该错误。它只发生一次,我无法重现它。
修改:
错误出现在“MyFrm:= TFrm1.create”行中。
有些人建议我需要以编程方式为动态创建的表单提供唯一名称。我也想知道自己在创建表单时采用了什么名称,所以我在调用MyProcedure1程序时进入了代码。
德尔福自动给出像
这样的独特名称
MyFrm.name = MyFrm,然后是
MyFrm.name = MyFrm_1,
MyFrm.name = MyFrm_2,
MyFrm.name = MyFrm_3,依此类推。
LoadFromFile中未更改MyFrm.Name。我在程序MyProcedure1的末尾检查了(断点)'MyFrm.Name'的值;在LoadFromFile之后。这个名字很独特。
正如有些人建议的那样,我重写了SetName过程并检查了TMyFrm的名称。事实上,每个表格都有一个独特的名称。
程序TMyFrm.SetName(const值:TComponentName);
开始
ShowMessage(值);
继承;
结束;
我在这个应用程序中有很多表单,但只有MainForm是自动创建的。
我不使用线程。无论如何,这是不相关的,因为表单是由用户创建的(因此多线程是无关紧要的,除非用户可以同时创建2个表单)。
答案 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时为读/写访问说明符编写自定义特定方法/函数的对象。我建议将任何复杂对象转换为字符串值,以便在开发过程中检查代码。