views:

689

answers:

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.

A: 

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.

Tobias Langner
We need these things to be done during designtime though.For example, the property mentioned in my question should be possible to set during designtime.
Michael Stahre
+3  A: 

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.

BennyBechDk
I'll look into AfterConstruction.But if there is such a thing, isn't it almost always safer to use AfterConstruction instead of Create when you're writing a component that has controls on it?
Michael Stahre
+2  A: 

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.

Mason Wheeler
I did this just now and it lead me to the same line of code I'd found by looking at the access violation message given to me during designtime.It's probably the same thing I describe in my original question, as that line of code is the one where the access violation happens.So this means it happens at both designtime and runtime the same way.Any idea why the controls on the component I'm placing aren't created before the component's create is called when it apparently was back in Delphi 5?
Michael Stahre
Again, forgot to say:Either way, this is a mighty handy trick that I'll definitely commit to memory. I'm surprised I hadn't thought of it already.Thanks :)
Michael Stahre
It should be created. If you check, all the other objects on the form will already be created by then. Maybe something's going wrong while deserializing the component? Hard to tell without more information. BTW is this a button directly on your form, or is it a sub-component of a larger component that you've placed on your form?
Mason Wheeler
It goes like this:TRadioPanel is a VCL Form, on which there are various components (this LMDButton is one of them.)In my main application, we place multiple TRadioPanel components on the main form and make them visible if the user press their respective buttons.So that would make it a sub-component I think.
Michael Stahre
TRadioPanel is a VCL Form, which is placed on other forms? Do you mean it's a frame?
Mason Wheeler
Well, it was originally a TVisualComponent, derived off the CDK toolkit.Since that isn't available anymore (and isn't necessary anyway), I've changed it to a TCustomForm.I've tried TForm, TWinControl, TFrame and such too.TVisualComponent was based on TWinControl, so you'd think that oughta work... But alas, no difference.
Michael Stahre
It probably has nothing to do with form creation, and everything to do with control initialization inside your custom control. Not form. Control. Control with subcontrol. Probably need to step through your custom control line by line. And it would be cool if you could post a working sample demonstrating this problem.
Warren P
Is the distinction more than just semantics though?I guess it's correct to call it a control though.Either way, aren't subcontrols on a control initialized automatically the same way controls on a form are?I guess this might be the part where things were changed since Delphi 5.I'll see about replicating it.
Michael Stahre
Ah.I hadn't found any information in the DFM pertaining to things like this, so I'd just assumed Delphi looks at the objects written in the DFM, the ones defined in the .pas file and figured out creation details from there....So that just leaves the question - how do I make a new DFM that contains this data?The only way I've ever ('ever' might be a bit of an exaggerated word, as I only have three or so months of Delphi experience) found to create DFM files is File->New->Form(or VCL Application)
Michael Stahre
Worth noting - there IS a DFM that when viewed as text in RAD Studio contains entries for the buttons in question.Am I right to understand that the DFM contains the definitions you're referring to in your Edit in data that isn't shown when you use view as form?Opening the file in Notepad2 reveals lots of data that isn't plaintext. Is some of that hidden completely from the user in RAD Studio?
Michael Stahre
A: 

Is this good enough to get you going:

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

Warren P
Well, the problem is that this will leave the values unchanged, removing the point of setting them in Create in the first place.Someone already suggested using AfterConstruction to assign the values that couldn't be assigned during Create, but I haven't spent time doing that yet because I don't really want to circle around the issue.Plus I'm quite curious how the automatic creation aspect of delphi handles controls on forms. Haven't been able to find anything specific about it.So I could work around it in a manner not unlike what you suggest, but I'd much rather not.Thanks nontheless.
Michael Stahre
+1  A: 

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.

Rob Kennedy
Well, there unfortunately are entries like these there.Unless the DFM contains data completely hidden from the coder that also has to be correct, this doesn't seem to be the issue.Here's to hoping a fresh DFM might solve it...Thank you for your description of Loaded. I've definitely wanted to know if a procedure like this existed.
Michael Stahre