Last time I introduced a bit of skullduggery with my Deltics.Forms unit as a way to easily “inject” a new TForm class into my projects.  We used this new class to add public property that we could use to add a size grip control to any form.

This time we shall introduce some more code the Deltics.Forms unit in order to add some frequently useful capabilities to the Application object.

What Is Application?

As you are no doubt aware, the Application object is a single object declared in the VCL Forms unit (but instantiated in the Controls unit), and is the heart of most Delphi applications.  It’s a well named object in that respect.

Unfortunately, the Application object is of a fixed class – TApplication.  This class cannot be extended or replaced with a class that might, for example, contain extensions that you might wish to have included in all of your applications.

It was a surprise to me to find some years ago, for example, that the Application object in a Windows Application project has nothing in common with the Application object in a Windows Service Application project, for example – one is a TApplication the other a TServiceApplication and both derive directly from TComponent.

It is also something of a surprise that the basic capabilities of the TApplication object have not evolved over the years to provide better access to version information or command line parameters, for example.  But despite the rather closed nature of TApplication, the Deltics.Forms unit provides the platform for adding these things ourselves.

Helpers For The Rest Of Us

A post on MelanderBlog shows how to do this with a class helper, but these things are a) a bad idea (imho) and b) not available to anyone using Delphi 2006 (I think) or earlier.

Class helpers are intrinsically a “bad idea” because only one can be in scope at a time, so if you had some other code that introduced another helper for the same class (perhaps without you even knowing it) you have a problem – one or other of those helpers will “disappear” and code that compiled perfectly well before suddenly stops compiling.

But we can achieve the same result more safely without class helpers.  They are merely syntactic sugar for a time-tested technique that is actually safer (and more opaque) than the formalised “helper” mechanism.  “Safer” because code that uses the non-helper technique cannot cause problems with other code using the same technique in the way that formal class helpers suffer from.

As we saw, the Deltics.Forms unit introduces a new TForm class that is compatible with the VCL TForm class.  We can also introduce a new TApplication class that is similarly compatible with the VCL TApplication:

  type
    TApplication = class(Forms.TApplication);

A problem here however is that we cannot alter the fact that the Controls unit instantiates a Forms.TApplication, and knows nothing of this new TApplication class.  Our new TApplication class must therefore observe the same limitations of a class helper – we cannot introduce new member data and we cannot override any virtual methods.

Another problem is that the Application variable in Forms is also declared using the Forms.TApplication class, so if we are to refer to Application as our new class we’ll need just a little more smoke and mirrors in Deltics.Forms:

interface

  function Application: TApplication;

implementation

  function Application: TApplication;
  begin
    result := TApplication(Forms.Application);
  end;

We still need to do a little work in our actual projects to make use of this new Application object – the Forms unit reference in our project source needs to be replaced by a reference to Deltics.Forms.  It is far less likely that we will need to have both Forms and Deltics.Forms referenced in the project source, but if that is necessary then instead of replacing the Forms reference, add the Deltics.Forms unit after it in the uses list.

With these fundamentals in place, we can now – with a little care – add some useful new capabilities to the Application object.  For starters let’s add a VersionInfo property.

Adding Properties Without Adding Member Data

The “care” that we need to exercise is as previously mentioned – we cannot add member data, and we cannot override any virtual methods. So how can we add any new properties?

In this case, things are made a great deal simpler by the fact that the Application object is a singleton – there is only ever one of them in any project.  There is also only ever one Deltics.Forms unit in an application, so we can declare variables in the unit to hold the data we would otherwise introduce as member data in the Application object.

These unit variables (sometimes mistakenly referred to as global variables – Delphi actually has no such thing) need only be declared in the implementation section as they are exposed only via our extensions to the TApplication class:

interface

  uses
    Forms,
    Deltics.VersionInfo;

  type
    TApplication = class(Forms.TApplication)
    private
      function get_VersionInfo: TVersionInfo;
    public
      property VersionInfo: TVersionInfo read get_VersionInfo;
    end;

implementation

  var
    _VersionInfo: TVersionInfo = NIL;

  function TApplication.get_VersionInfo: TVersionInfo;
  begin
    if NOT Assigned(_VersionInfo) then
      _VersionInfo := TVersionInfo.Create;

    result := _VersionInfo;
  end;

initialization
  // NO-OP

finalization
  FreeAndNIL(_VersionInfo);

end.

Obviously the above code is not entirely complete, only showing the changes and additions needed to add the VersionInfo property.   The details of the TVersionInfo implementation aren’t particularly important – there are lots of implementations of such a class freely available (the one I use will be available shortly – more on that in my next post).

So we can add some useful new properties, but I also mentioned previously that we could introduce some behavioural changes inspired by an article about Creating Vista Ready Applications.

How do we do that without overriding any virtuals?

Changing Behaviour Without Overriding

The original article about creating Vista ready applications mentioned adding some code to a FormCreate event (presumably the FormCreate of the main form) to modify the characteristics of the hidden application window (Application.Handle). Not only is this a little inelegant (using an event in one form to make changes to some other form), it is also going to be cumbersome having to do this in every application.

Now that we have our own TApplication class we can introduce these changes once and have that change apply in our applications simply by using the Deltics.Forms unit.  The key method in this case is the TApplication.CreateForm method.  This is not virtual and typically isn’t even referenced outside of our project source file anyway, so replacing this method with our own implementation is quite safe:

interface

  type
    TApplication = class(Forms.TApplication)
    public
      procedure CreateForm(InstanceClass: TComponentClass; var Reference);
    end;

implementation

  procedure TApplication.CreateForm(InstanceClass: TComponentClass;
                                    var Reference);
  var
    bMainForm: Boolean;
  begin
    bMainForm := (Forms.Application.MainForm = NIL);

    inherited;

    if NOT bMainForm
     or NOT MainFormOnTaskBar
     or NOT (Application.MainForm.InheritsFrom(TForm)) then
      EXIT;

    ShowWindow(Application.Handle, SW_HIDE);
    SetWindowLong(Application.Handle,
                  GWL_EXSTYLE,
                  GetWindowLong(Application.Handle, GWL_EXSTYLE)
                   and NOT WS_EX_APPWINDOW or WS_EX_TOOLWINDOW);
    ShowWindow(Application.Handle, SW_SHOW);

    TForm(Application.MainForm).RecreateWnd;
  end;

Again the above code showns only the changes and additions and is not complete.

The MainFormOnTaskBar property is another property that I introduce to TApplication in a similar way that VersionInfo is introduced.  It is deliberately named for a property that is introduced in Delphi 2007, and is used to determine whether the form shown on the Task Bar will be the hidden application hidden window (normal Delphi behaviour) or the MainForm window (the new behaviour introduced here).

Our new CreateForm takes care of modifying the characteristics of the hidden application window when creating the Application MainForm (by design in the VCL, the MainForm is the first form created via a call to the Application.CreateForm method).

We also only apply these changes if the MainForm is derived from our Deltics.Forms.TForm (otherwise it won’t be aware of our Vista readiness changes).

Finally, if the changes are applied, the MainForm window itself needs to be re-created to ensure that the necessary window parameter changes are applied that will present that form on the task bar.  These parameter changes are applied in an override of CreateParams in the Deltics.Forms.TForm class:

  procedure TForm.CreateParams(var Params: TCreateParams);
  begin
    inherited;

    Params.Style := Params.Style and (not WS_CHILD) or WS_GROUP or WS_TABSTOP;

    if ShowOnTaskBar then
      Params.ExStyle := Params.ExStyle and (NOT WS_EX_TOOLWINDOW) or WS_EX_APPWINDOW;
  end;

Time for a quick confession – I originally did all this stuff quite a while ago now and rather foolishly neglected to document a lot of the finer details. The changes to Params.Style being one such undocumented detail.

It solves a puzzling problem – changing the caption of an AutoSize TLabel on a Deltics.Form.TForm results in that form changing size (a result of AlignControls).  Setting these Style params fixes that behaviour.  I must have reasoned this out myself at the time or come across some information that gave me the insight, but I’ve since lost that information.  I hate code that “just works”, so if anyone can explain why this is necessary I would be very grateful.

One last thing we can do is introduce the WM_SYSCOMMAND message handling described in the Vista readiness article:

  procedure TForm.WMSysCommand(var aMessage: TWMSysCommand);
  begin
    if Application.MainFormOnTaskBar then
    begin
      case (aMessage.CmdType and $FFF0) of
        SC_MINIMIZE : begin
                        ShowWindow(Handle, SW_MINIMIZE);
                        aMessage.Result := 0;
                      end;

        SC_RESTORE  : begin
                        ShowWindow(Handle, SW_RESTORE);
                        aMessage.Result := 0;
                      end;
      else
        inherited;
      end;
    end
    else
      inherited;
  end;

Code In Preparation

There is no code to download this time – for the time being – other than that provided in the article above.

I am currently in the process of setting up a CodePlex hosted project where I shall release all code that I cover in my blog – and more. I have decided to release under the X11 (a.k.a “MIT”) License. I find the Team Foundation version control model as unpalatable as SVN (as I understand it CodePlex projects can be accessed using Team Explorer or SVN) – the tight coupling between repository and local workspace is particularly uncomfortable to me.

But this, and other aspects of the CodePlex experience, is something I shall blog about separately.

Once the project is available I shall post the details in my blog.

10 thoughts on “More Deltics.Forms Magic

  1. I am not sure if I understand this in the context of SVN: “the tight coupling between repository and local workspace is particularly uncomfortable to me”?

    Could you explain what you mean?

  2. Jolyon, don’t you think that your same-class-name wizardry is an extremely bad programming practice that can lead to intricate and hard to fix bugs should the code be maintained by a person that didn’t write it (or even by the same person after some time passes)?

  3. Well, a couple of examples from my CodePlex experience so far (which I’m told is how SVN works also)…

    1) having setup a project by checking in initial versions from one machine (one workspace) I then connect from a different machine (different workspace) that has more current versions of the same files.

    But the repository is unaware of the local files on the new machine because it has not yet “got” a workspace for that machine – I can’t even do a diff.

    And if I “get” that workspace my local files will be replaced with the repository versions, which is absolutely NOT what I want, but I get no choice because the workspace IS my local representation of the repository, not just a file system location that happens to be mapped for convenience.

    To have the repository be aware that I have local, more up to date versions I have to move my local files to a temp location, get the workspace “up to date” then move my local files from the temp location back into the workspace.

    2) I deleted a file from the repository. When connecting to the repository from another machine (i.e. a different workspace) the file still appears as present in the repository but is shown as “Not latest”.

    If I “Get Latest” my local (workspace) copy is deleted, because that is the latest “version” of the file in repository. I get no warning (and in TFS I can’t find anywhere where I might set any option to GET a warning). Again, the workspace is the local representation of the repository, not a separate file system.

    “So what? The file is deleted now, you *should* delete your local copy.”

    Nope – it’s deleted in the repository. My workspace copy might be important – someone else may have deleted the repository copy unaware that I was going to check in changes. Changes which I have now lost.

    I’m aware that these are possibly “mind set” issues. i.e. that once I’ve gotten used to the SVN/CodePlex way of working then it’ll be OK.

    My problem is that I might get used to it, but it’ll never be natural to me.

    To me, my local workspace is just that – local. When it comes time to update the repository that is a whole separate activity. The way TFS and (I’m told) SVN work, it’s is no longer a separate activity because it’s not a separate space.

    Once a file system area has been associated with a repository (i.e a workspace has been “created”), it seems to me that the two can no longer be considered separate spaces.

  4. @Alexander – Yes and No.

    Yes in so far as I can see why you might be concerned and if it were something I indulged in with carefree abandon.

    No because Delphi is a lot cleverer than we sometimes give it credit for, especially when it comes to type names, scoping and qualification (the VCL persistence implementation is *not* so clever, but that doesn’t come into play here).

    Could I have done things differently? Of course.

    One alternative would have been to have called it a TDelticsForm (and used TDelticsApplication and DelticsApplication etc etc). But that would not actually have made any material difference other than to make it more cumbersome to use these classes.

    And the idea (for me) was to make the usage of the new classes utterly transparent and straightforward, whilst being perfectly opaque in the implementation.

    Note that if you ever try to use a VCL TForm as if it were a Deltics.Forms TForm in ways that the VCL Form does not support, your code simply won’t compile. If you use a Deltics.Forms TForm as if it were a VCL TForm… well, no problem. It *is* a VCL TForm – a properly derived extension of that very class.

    Yet another alternative, and an approach I see quite commonly advocated in such cases – is to modify the VCL code.

    Now that IS a bad idea imho. First, it requires copying the VCL unit(s) involved – being careful to use the same UNIT names and carefully organising your paths so that your XYZ unit hides/replaces the normal VCL XYZ unit. I think it would also make it rather difficult to use runtime packages, although that may not be important to you.

    And in this case you end up with a TForm that has been contrived to look even *more* like a VCL TForm than a Deltics.Forms.TForm does, but which isn’t actually the same VCL TForm *at all*.

    And at the end of the day, no-one is going to force anyone to use Deltics.Forms if they don’t wish to.

    🙂

  5. Using a TForm imposter class – great, do it myself, and I agree that doing things by imposter classes is better than modifying the VCL source (alongside the reasons you list, another is that modifying the VCL source makes moving between Delphi versions much more of a pain than it needs to be). However, I don’t think your TApplication hack is very appealing at all – it’s doing exactly the same as what a class helper would do, only without compiler errors being caused when you forget to stick to static, public extensions to the class. (Your complaint that only one helper for a particular class can be in scope at any one point is a red herring, since the same goes for your hack – and if a person must, a class helper *can* inherit from another, so there’s no benefit of your method in that score either.) Lastly, the bit needed for Vista could easily enough be added to the TForm imposter class by overriding CreateNew:

    var
    FixedAppWnd: Boolean;

    constructor TForm.CreateNew(AOwner: TComponent; Dummy: Integer);
    begin
    if not FixedAppWnd then
    begin
    FixedAppWnd := True;
    SetWindowLong(Application.Handle, GWL_EXSTYLE,
    GetWindowLong(Application.Handle,
    GWL_EXSTYLE) and not WS_EX_APPWINDOW or WS_EX_TOOLWINDOW);
    end;
    inherited;
    end;

    Anyhow, that’s the code I use (more or less)…

  6. (The article is great, thanks, and I’ll be making use of that technique fairly soon, but I want to comment on the version control stuff.)

    Regarding deletion first. I can’t speak for codeplex, but svn doesn’t delete locally modified files when they are removed from the repository by someone else. They simply become “unversioned” files, keeping their changes but no longer under version control. A status check will show them as being unversioned, and the developer can choose to add them back in explicitly if they require. That seems pretty reasonable to me, in the circumstances.

    Back to point 1), one local workspace being used to create a repository, then working in a different local workspace.

    I’m a little confused as to your working practice here. You have two local working copies of a project not under version control, to which you make independent changes, THEN you want to put them under version control as the SAME project, but using the workspace with the oldest copies. Not sure I follow that.

    Even so, you can do at least part of what I think you want with subversion. The trick is to create an empty project in the repository first, then do a checkout to one of your project working copies. You might have to override a warning that files already exist there, but that’s safe. Then you can just add those files to the repository via “svn add” and “svn commit”. (This is a fairly standard way of adding an existing project to subversion.)

    For the other workspace, first create a dummy folder and “get” from the repository. This will then look like the first location, since that’s what it was created from. Then just drag and drop the contents of the second location into this dummy folder. You might have problems if you’ve deleted files, or worse, folders, but other than that this will get you where you want to be. Add any new files with “svn add”, then “svn commit”.

    I should say though, that this is all the more awkward because of where you’ve started (as the old joke goes: “How do I get to Doncaster?” “Well, I wouldn’t start from here.”). You’ve got two versions of a project that aren’t under version control, and you now want them to be, while still being the same project . I’ll be impressed to hear of a system that deals with that out of the box. This sort of thing is better handled when under version control from the start.

    For what it’s worth, you might be better off with a distributed system like git, or bazaar. I haven’t used either in anger, but I know that bazaar at least will let you run it locally while pushing and pulling changes to an upstream svn server (possibly others too, but I’m only really interested in subversion at the moment).

    Hope this is useful.

  7. Hi Chris,

    Yes you could add the Vista bit in the CreateNew of every TForm, except that the first TForm created might not be the first form created in a call to CreateForm, so may not be the MainForm.

    It doesn’t really matter because that TForm.CreateNew code isn’t modifying the form being created but the application hidden window. Again, I see it as desirable that TApplication code should look after a TApplication behaviour. ymmv

    And adding VersionInfo and CommandLine properties (not yet discussed) to the Application object is something that cannot be done without extending TApplication in some way.

    I don’t see that the one class helper in scope problem is a red herring at all. Yes, either *my* Application is in scope, or Forms.Application is in scope, but whichever one is in scope will not prevent the other from working in the same way that occurs with class helpers.

    i.e. if Deltics.Forms.Application is the “default”, I could still qualify and reference Forms.Application, something which you cannot do with class helpers.

    (not to mention that official position is that you shouldn’t use the class helper feature!)

    The lack of compiler “support” is a fair point, but this technique of pseudo-subclassing (if you like) has the benefit of being portable across all Delphi versions and being a well established technique.

    🙂

  8. “I don’t see that the one class helper in scope problem is a red herring at all. Yes, either *my* Application is in scope, or Forms.Application is in scope, but whichever one is in scope will not prevent the other from working in the same way that occurs with class helpers.”

    The difference in flexibility is exactly this (I’ve just checked): fully qualifying the identifier ‘Application’ with the unit name has no effect with the class helper version where it does for the phoney subclassing one. ‘Ah’, you might reply, ‘but what of how D2007’s Forms.pas already defines a class helper for TApplication?’ ‘Nothing a tiny bit of conditional complication can’t fix’, I say:

    . . {$IF DECLARED(TApplicationHelper)}{$DEFINE DoInherit}{$IFEND}

    . . TCCRApplicationHelper = class helper{$IFDEF DoInherit}
    . . . (TApplicationHelper){$ENDIF} for TApplication

    (The ‘$IF DECLARED’ syntax has been available since D6.)

    “this technique of pseudo-subclassing (if you like) has the benefit of being portable across all Delphi versions and being a well established technique”

    ‘Well-established’? I’d hope not! Class helpers proper are bad enough…

  9. “fully qualifying the identifier ‘Application’ with the unit name has no effect with the class helper version”

    Exactly my point – if some unit you use introduces another class helper for a class for which you rely on some *other* class helper, then your code breaks and you cannot fix it without fixing the ordering of your unit uses, assuming that that is straightforward or even possible/desirable in the specific case.

    “what of how D2007’s Forms.pas already defines a class helper for TApplication?”

    I didn’t even know it did, but good case in point. Code that I had written using my own class helper would mysteriously and seemingly inconsistently perhaps, stop compiling – depending on which helper had default scope in any particular case, and which helper was hidden.

    I’d have to say that your work around is a lot less appealing to me than a psuedo-subclass and in fact is not even completely reliable.

    It will detect and work-around a class helper in the VCL Forms unit (or other unit “used” by the unit containing the $IFDEFs – in this case Deltics.Forms) but it won’t protect against some other unit also potentially containing a helper for TApplication if Deltics.Forms does not use that unit itself.

    As for psuedo-subclassing being a well established technique, it certainly is, although perhaps not typically for the use to which it is being put here.

    Normally I see it used to create psuedo-subclasses that bring inherited protected members into scope, e.g. when dealing with controls only as TControl references and wishing to access the Color property which is only protected in the TControl class:

    TControlHelper = class(TControl);

    :

    TControlHelper(someControl).Color := … ;

    It’s not exactly the same thing – granted – but is the well-establishedness of pseudo-subclassing that I had in mind.

    The only real difference is the availability of the Application function that removes the need to constantly write:

    Deltics.Forms.TApplication(Application). ….

    As you say class helpers are indeed bad enough, but pseudo-subclassing has two redeeming qualities by comparison: it does not risking breaking other, unrelated code that uses the same technique and it works in all versions of Delphi.

Comments are closed.