Delphi - 在运行表单 Create 之前创建控件?

发布于 2024-08-14 04:39:22 字数 996 浏览 5 评论 0原文

好吧,我的问题如下:

我有一个 Delphi 5 应用程序,我基本上将其移植到 Delphi 2010(用最新版本替换旧组件,修复不可避免的 Ansi/Unicode 字符串问题等),并且我遇到了有点麻烦。

创建我们的其中一份表单后,就会发生访问冲突。经过查看后,我得出的结论是,这是因为 Create 中调用的 setter 之一尝试更改尚未创建的表单上的对象的属性。

我已经将其精简了一点,但代码基本上如下所示:

在表单声明中:

property EnGrpSndOption:boolean read fEnGrpSndOption write SetGrpSndOption;

在表单的 Create 中:

EnGrpSndOption := false;

在实现中:

procedure Myform.SetGrpSndOption(const Value: boolean);
begin
  fEnGrpSndOption := Value;
  btGrpSnd.Visible := Value;
end;

通过在 btGrpSnd.Visible 之前添加 ShowMessage(BooltoStr(Assigned(btGrpSend), true)) : =值,我确认问题是btGrpSnd还没有创建。

btGrpSend 是一个 LMDButton,但我很确定它不太相关,因为它甚至还没有被创建。

虽然我意识到我可能应该只在确认分配了控件后才分配一个值,但这只会导致 create 中设置的值未设置为实际控件。

所以我想做的是找到一种方法来确保在运行“创建”之前创建窗体上的所有控件。

任何有关此操作的帮助或有关 Delphi 如何创建表单的信息将不胜感激。 它在 Delphi 5 中可以正常工作,所以我想应该在版本之间的更改列表中的某个位置提及其原因。毕竟,Delphi 2010 比 Delphi 5 新很多。

Well, my problem is as follows:

I have a Delphi 5 application that I'm essentially porting to Delphi 2010 (replacing old components with their latest versions, fixing the inevitable Ansi/Unicode string issues, etc.) and I've run into kind of a hitch.

Upon creation of one of our forms, an access violation happens. After looking it over, I've come to the conclusion that the reason for this is because one of the setters called in Create attempts to change a property of an object on the form that hasn't been created yet.

I've trimmed it down a little, but the code basically looks like this:

In form declaration:

property EnGrpSndOption:boolean read fEnGrpSndOption write SetGrpSndOption;

In form's Create:

EnGrpSndOption := false;

In Implementation:

procedure Myform.SetGrpSndOption(const Value: boolean);
begin
  fEnGrpSndOption := Value;
  btGrpSnd.Visible := Value;
end;

By tossing in a ShowMessage(BooltoStr(Assigned(btGrpSend), true)) right before btGrpSnd.Visible := Value, I confirmed that the problem is that btGrpSnd hasn't been created yet.

btGrpSend is an LMDButton, but I'm pretty sure that isn't quite relevant as it hasn't even been created yet.

While I realize I probably should only assign a value after confirming that the control is assigned, this would just result in the value set in create not being set to the actual control.

So what I want to do is find a way to make certain that all the controls on the form are created BEFORE my Create is run.

Any assistance in doing this, or information regarding how Delphi creates forms would be appreciated.
It worked back in Delphi 5, so I imagine the cause of this should be mentioned somewhere among the lists of changes between versions. Delphi 2010 is quite a bit newer than Delphi 5 after all.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

顾北清歌寒 2024-08-21 04:39:22

就像托比亚斯提到的(但主张反对),您可以更改创建顺序(在更改创建顺序的表单中)。

但您也可以在 setter 方法中检查表单是否正在创建(form.componentstate 中的 csCreating)。如果是这样,您必须自己存储该属性值,并在 AfterConstruction 中处理它。

Like Tobias mentioned (but advocates against) you can change the creation order (right at the form at change the creation order).

But you can also in the setter method check if the form is creating (csCreating in form.componentstate). And if it is you have to store that property value yourself, and handle it in AfterConstruction.

开始看清了 2024-08-21 04:39:22

根据您的评论,您在设计时放置它时得到了 AV,这意味着控件本身存在问题,并且尚未正确向前移植。要在受控环境下在运行时重现它,您需要编写一个像这样的小程序:

使用单一表单创建一个新的 VCL 应用程序。在窗体上放置一个 TButton。在按钮的 OnClick 上,执行如下操作:

var
   newButton: TLMDButton;
begin
   newButton := TLMDButton.Create(self);
   newButton.Parent := self;
   //assign any other properties you'd like here
end;

在构造函数上放置一个断点并跟踪它,直到找到导致访问冲突的原因。

编辑:好的,通过查看评论,我想我们找到了您的问题!

窗体的子控件通过读取 DFM 文件来初始化。当您将控件更改为 TCustomForm 时,您是否提供了新的 DFM 来定义它?如果没有,您需要重写表单的构造函数并创建控件并手动定义它们的属性。没有“魔法”可以为您初始化它。

From your comment that you're getting an AV when placing it at design time, that means there's a problem with the control itself and it hasn't been properly ported forward. To reproduce it at runtime under controlled circumstances, you need to write a little program like this:

Make a new VCL app with a single form. Place a TButton on the form. On the button's OnClick, do something like this:

var
   newButton: TLMDButton;
begin
   newButton := TLMDButton.Create(self);
   newButton.Parent := self;
   //assign any other properties you'd like here
end;

Put a breakpoint on the constructor and trace into it until you can find what's causing the access violation.

EDIT: OK, from looking at the comments, I think we found your problem!

A form's subcontrols are initialized by reading the DFM file. When you changed your control to a TCustomForm, did you provide a new DFM to define it? If not, you need to override the form's constructor and create the controls and define their properties manually. There's no "magic" that will initialize it for you.

烟织青萝梦 2024-08-21 04:39:22

您的 Create 始终在祖先构造函数之前首先被调用。这就是构造函数的工作原理。在进行其余的初始化之前,您应该能够调用继承的构造函数:

constructor MyForm.Create(Owner: TComponent);
begin
  inherited;
  EnGrpSndOption := False;
end;

但是,有一种更好的方法来指示您想要实现的目标。您的类正在从 DFM 资源加载属性。完成后,它将调用名为 Loaded。它通常用于通知所有孩子一切都准备好了,因此如果他们中的任何一个在表格上保存了对其他孩子的引用,他们知道此时使用这些引用是安全的。您也可以在表单中覆盖它。

procedure MyForm.Loaded;
begin
  inherited;
  EnGrpSndOption := False;
end;

不过,这通常不会对您的情况产生太大影响。在表单完成从 DFM 资源加载自身之后,立即从构造函数调用 Loaded。该资源告诉表单它应该为自己创建的所有控件。如果您的按钮没有被创建,那么它可能没有在 DFM 中正确列出。 DFM 中列出的控件可能在类中没有相应的字段。另一方面,如果某个已发布的字段在 DFM 中没有相应的条目,则 IDE 应向您发出警告,并在您每次在表单设计器中调出该声明时建议删除该声明。以文本形式查看 DFM,并确认确实存在名为 btGrpSnd 的控件条目。

Your Create is always called first, before the ancestor constructor. That's just how constructors work. You should be able to call the inherited constructor before you do the rest of your initialization:

constructor MyForm.Create(Owner: TComponent);
begin
  inherited;
  EnGrpSndOption := False;
end;

However, there's a better way of indicating what it is you're trying to make happen. Your class is loading properties from a DFM resource. When it's finished, it will call a virtual method named Loaded. It's usually used to notify all the children that everything is ready, so if any of them hold references to other children on the form, they know it's safe to use those references at that point. You can override it in the form, too.

procedure MyForm.Loaded;
begin
  inherited;
  EnGrpSndOption := False;
end;

That generally shouldn't make much difference in your case, though. Loaded is called from the constructor right after the form finishes loading itself from the DFM resource. That resource tells the form all the controls it should create for itself. If your button isn't being created, then it's probably not listed correctly in the DFM. It's possible for controls to be listed in the DFM that don't have corresponding fields in the class. On the other hand, if there's a published field that doesn't have a corresponding entry in the DFM, the IDE should warn you about it and offer to remove the declaration each time you bring it up in the Form Designer. View your DFM as text and confirm that there's really an entry for a control named btGrpSnd.

梦在深巷 2024-08-21 04:39:22

这是否足以让您继续下去:

if Assigned(btGrpSnd) and btGrpSnd.HandleAlowned then
btGrpSnd.Visible := ...

Is this good enough to get you going:

if Assigned(btGrpSnd) and btGrpSnd.HandleAllocated then
btGrpSnd.Visible := ...

不交电费瞎发啥光 2024-08-21 04:39:22

我看到两种可能性:在将 Value 分配给 Visible 属性之前检查 btGrpSnd 是否为零。如果它为零,您可以:

  • 不设置属性
  • create btGrpSnd

我不会弄乱创建顺序。它更复杂,并且可能会因进一步的更改而中断。


根据您的评论:您可以检查您是处于设计模式还是运行模式。在设置可见性之前检查您是否处于设计时间。

if not (csDesigning in Componentstate) then
begin
  btGrpSnd:=Value;
end;

回答您的评论:

这样做:

procedure Myform.SetGrpSndOption(const Value: boolean); 
begin 
  fEnGrpSndOption := Value; 
  if btGrpSnd<>nil then btGrpSnd.Visible := Value; 
end; 

以及一个附加属性设置 btGrpSnd。如果它被设置为一个值<> nil,也设置 fEnGrpSndOption 中安全的可见性。

如果不需要在 Myform 外部设置 btGrpSnd,请创建一个创建所有内容的 init 过程。例如:

constructor Myform.Create(...)
begin
  init;
end;

procedure init
begin
  btGrpSnd:=TButton.Create;
  ...
end;

procedure Myform.SetGrpSndOption(const Value: boolean); 
begin 
 fEnGrpSndOption := Value; 
 if btGrpSnd<>nil then init;
 btGrpSnd.Visible := Value; 
end; 

这仍然比依赖于将来可能会破坏的一些更改的初始化代码黑客更好。

I see 2 possibilities: check if btGrpSnd is nil before assigning Value to the Visible property. If it's nil, you could either:

  • not set the property
  • create btGrpSnd

I would not mess around with the creation order. It's more complicated and may break with further changes.


from your comment: you can check wether you are in design or in runtime mode. Check wether your are in designtime before setting the visibility.

if not (csDesigning in Componentstate) then
begin
  btGrpSnd:=Value;
end;

Answer to your comment:

go for this:

procedure Myform.SetGrpSndOption(const Value: boolean); 
begin 
  fEnGrpSndOption := Value; 
  if btGrpSnd<>nil then btGrpSnd.Visible := Value; 
end; 

and one additional property setting btGrpSnd. If it is set to a value <> nil, set the visibility safed in fEnGrpSndOption as well.

If there's no need to set btGrpSnd outside Myform, create a init-procedure that creates everything. E.g.:

constructor Myform.Create(...)
begin
  init;
end;

procedure init
begin
  btGrpSnd:=TButton.Create;
  ...
end;

procedure Myform.SetGrpSndOption(const Value: boolean); 
begin 
 fEnGrpSndOption := Value; 
 if btGrpSnd<>nil then init;
 btGrpSnd.Visible := Value; 
end; 

This is still better then depending on some changed init-code-hack that may break in the future.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文