Where Developers Matter
Integrated Development Environments for Windows, Java, and Web Developers
| | Log On

Using visual form inheritance with IntraWeb 5.1 in Delphi 7

Abstract: This article explains a way to be able to use visual form inheritance with IntraWeb (5.1). Something that normally is not working.
Introduction

When I started with my first real application in IntraWeb, I wanted to make a consistent layout through out my application. Since with IntraWeb you can make a web application just like a normal Delphi application: my first thought was: (visual) form inheritance (VFI). So I started happily programming making a baseform as a parent for all my other forms, putting on it a title bar, and started inheriting from it. And it worked: I had the title bar on all my forms!

But after I started programming really functional forms, I soon also wanted to change something about my baseform, make the layout a bit nicer and better fitting with the forms I was making. And then it happened: I had added something to my baseform, compiled the application and it just gave an exception: "Control 'IWButton1' has no parent window". I hadn't changed anything really, except enhancing my baseform: and there wasn't even a IWButton1 on my baseform, it was on my mainform! Well after a little debugging, I looked at the IntraWeb newsgroups and it's website and soon found the answer: IntraWeb doesn't support visual form inheritance. You can use inherited code, but you can't inherit the visual layout... 

Finding the problem...

Since I am a stubborn person (I wanted to use visual form inheritance!), and in my opinion problems are there to be solved, I started digging deeper in the problem. With the help of Delphi's VCL code (one of the things I love most about Delphi: you can always look at the source!) I soon found out in which part I had to search for the problem: the SetZOrderPosition method of TControl. When you add controls to a form you have inherited from (the baseform) the ordering of the controls on your descendant forms (in my case the Mainform) changes. You can see this ordering when you look at the dfm file of a descendant form: you see numbers in brackets [] after the controls. These numbers tell Delphi in which order the controls appear. In this SetZOrderPosition method there is a call to ValidParentForm (found in Forms.pas) that returns the parent form of the current control if that parent forms is a descendant of TCustomForm. And that was the problem: a TIWAppForm is not a descendant of TCustomForm!

...and trying to fix it.

Well only finding the problem doesn't solve it, I needed to find a way to fix it. My first thought was: I wanted that Borland had made ValidParentForm a virtual protected method of TControl, so anyone could override it, and the guys from IntraWeb could have made a special version that worked with IntraWeb forms. But since that wasn't the case, I had to go one level up to the SetZOrderPosition method. And found out that that one is private to TControl, so no chance of overriding that. (B.T.W. If that one could have be overridden it would have also meant that it had to be done in TIWBaseControl: and I would not be able to do that.) So I needed to go up one level: the caller of SetZOrderPosition which was in this case the SetChildOrder method of TWinControl, where this TWinControl was actually my Mainform. And the nice news was that SetChildOrder of TWinControl could be overridden! However if I did that there could be other problems because SetZOrderPosition was also called by other methods in TControl and TWinControl. But if that happened I would find out sooner or later.

Seeing that I needed to override SetChildOrder of my Mainform, and since that one inherited from my baseform: I put my implementation of SetChildOrder in my baseform. My implementation looked as follows:

procedure TIWBaseVFIForm.SetChildOrder(Child: TComponent; Order: Integer);
begin
  if Child is TIWBaseControl then
    TIWBaseControlHelper(Child).SetZOrderPosition(Order)
  else
   inherited;
end;

So if the Child is a TIWBaseControl I call a special implementation of SetZOrderPosition else I just call the inherited version. That special implementation of SetZOrderPosition I put in a class called TIWBaseControlHelper:

type
  TIWBaseControlHelper = class(TIWBaseControl)
  public
    procedure  SetZOrderPosition(Position: Integer);
  end;
To hack or not to hack?

Until so far it was simple. But now the real challenges appeared: I had to copy the implementation of SetZOrderPosition, but that implementation used a private field called FControls (see the VCL implementation of TControl.SetZOrderPosition in the Controls.pas unit). So I looked at what does the SetZOrderPosition has to do: it has to put itself at a certain position in it's parent list of controls. How can it do that, well look at the code I produced:

procedure TIWBaseControlHelper.SetZOrderPosition(Position: Integer);
var
  I, J, Count: Integer;
  ParentForm: TIWBaseForm;
  TmpParent: TWinControl;
  TmpControls: TList;
begin
  if Parent = nil then
    Exit;

  // First find our current position (in I)
  Count := Parent.ControlCount;
  I := 0;
  while I < Count do
  begin
    if Parent.Controls[I] = Self then
      Break;
    Inc(I);
  end;

  if I = Count then
    Exit; // strange we are not in our parent list?

  if Position < 0 then Position := 0;
  if Position >= Count then Position := Count - 1;
  if Position <> I then
  begin
    // re-position, but since we can't directly manipulate Parent.Controls, 
    // we need to hack and make a copy in TmpControls
    TmpControls := TList.Create;
    try
      for J := 0 to Count-1 do
        TmpControls.Add(Parent.Controls[J]);
      // delete ourselves from the list:
      TmpControls.Delete(I);
      // and insert at new position
      TmpControls.Insert(Position, Self);
      // save Parent (we'll be setting it to nil in the next lines)
      TmpParent := Parent;
      // remove all controls from the Parent's Controls list
      // we do that by setting the Parent property of the controls to nil
      while TmpParent.ControlCount > 0 do
        TmpParent.Controls[0].Parent := nil;
      // And put them back in in our TmpControls order
      for J := 0 to Count-1 do
        TControl(TmpControls[J]).Parent := TmpParent;
    finally
      TmpControls.Free;
    end;
    // Invalidate; Not necessary in IntraWeb.
    ParentForm := ValidParentIWForm(Self);
    if csPalette in ParentForm.ControlState then
      THackIWBaseForm(ParentForm).PaletteChanged(True);
  end;
end;

This code was of course inspired by the original TControl implementation: but the important part I had to rewrite. What it does is it first checks if it has a parent, then it searches for it's current position in the parent's control list and sees if it's new position is not the same. When it finds out it has to reposition, the hacking begins. We can't manipulate the parent's control list directly, but we can indirectly through the parent property of all the controls in the control list. So what I do is make a copy of the current control list and reposition our self in there. Then I set the parent of all the controls currently in the list to nil, so they are removed from the list and after that I add them again (in the correct order) to the list by setting their parent property back. So this results in a correctly ordered list. 

But this is not all that has to be done in this method! If it was we wouldn't have had any visual form inheritance problems, because this part already worked in the TControl implementation. The problem lied in the call to ValidParentForm in that implementation. First I thought I could just remove that call from my implementation, but I wasn't really sure and since the solution was not so hard, I made a call to a new function ValidParentIWForm. This function was simply based on the equivalent functions in Forms, I copied them and replaced references to TCustomForm to TIWBaseForm:

// next funcs base on Forms.GetParentForm and Forms.ValidParentForm
function GetParentIWForm(Control: TControl): TIWBaseForm;
begin
  while Control.Parent <> nil do Control := Control.Parent;
  if Control is TIWBaseForm then
    Result := TIWBaseForm(Control) else
    Result := nil;
end;

function ValidParentIWForm(Control: TControl): TIWBaseForm;
begin
  Result := GetParentIWForm(Control);
  if Result = nil then
    raise EInvalidOperation.CreateFmt(SParentRequired, [Control.Name]);
end;

(I might be violating a little copyright of Borland here, but since this is actually the most obvious implementation and you can't use it if you don't have Delphi: I hope Borland allows me to publish it.)

Now only one thing in my SetZOrderPosition code might need some explanation. I needed to call the PalletteChanged method of the ParentForm, but that is a protected method of TControl, so I used a standard hack to call a protected method: define a descendant class, and call the method by type casting to that descendant class:

type
  THackIWBaseForm = class(TIWBaseForm);
Trying it out

And now the excitement begins: does it all work? So I started running my web application: and it gave no error! To really test it I made a lot of changes to my baseform, I made changes to my mainform and other forms: and it kept on working. I also changed the order of the controls on my baseform and mainform by selecting the control and right-click, choose Control -> sent to back and bring to front, since that actually manipulates that Z-Ordering, but it kept working.

Conclusion

So this finishes my solution for using visual form inheritance (VFI) with IntraWeb 5.1(.28) in Delphi 7 in application mode. We are using it for quite some time now, and haven't run into any bugs. You can get the source of this solution, including a small test project at codecentral.

Included in that source is a TIWBaseVFIForm (in IWBaseVFIForm.pas) that you can use to inherited from. Of course you can also just copy the code from that form to your own base form.

Remember one thing when using this solution: the developer of IntraWeb doesn't support VFI, so I have no idea if it will work for other versions of IntraWeb nor if it will work with pagemode! I also haven't tested this code with IntraWeb in Delphi 5 or 6, but a quick scan of the relevant source code of those implementations of TControl doesn't show any possible problems: so just try it out.

About the Author:

Tjipke A. van der Plaats works as a software engineer for Agrovision B.V. where he works with IntraWeb. He also has it's own company Tiriss, for which he creates software on request or just because he needs the software himself.

Copyright ) MMIII by Tjipke A. van der Plaats


Published on: 7/11/2003 12:00:00 AM


Server Response from: BDN9A

 

Borland® Copyright© 1994 - 2008 Borland Software Corporation. All rights reserved. Contact Us  |   Site Map  |   Legal Notices  |   Privacy Policy  |   Report Software Piracy