7

I would like to subclass TToolBar with another class called MyTToolBar so that I can override a method. I'm new to Delphi, but after two hours of trying various methods, I can't get MyTToolBar to be used instead of TToolBar. I can't be the first person who's wanted to override a method on a visual component class.

I come more from an Xcode background where subclassing a visual component is easy. You create a subclass (e.g., MySubclass) of a parent class (e.g., MySuperClass) and then just simply assign the subclass in the Interface Builder view of Xcode. The subclass is automatically recognized and used.

Why can't I seem to do this in Delphi RAD Studio XE3?

After adding a TToolBar to a TForm, it doesn't seem possible to change the class. I tried through the Object Inspector as well as through the .PAS source code file. If I change the class in the .PAS file, I get an error message saying the toolbar "should be of type Vcl.ComCtrls.TToolBar but is declared as MyTToolbar. Correct the declaration?" This just seems silly...

Oh, and I've also used the new component wizard from selecting: File -> New -> Other -> Delphi Projects -> Delphi Files -> Component. I select the ancestor for MyTToolBar as TToolBar and tell it to register in the 'Samples' palette page. However, it doesn't appear in the 'Samples' page.

spurgeon
  • 1,082
  • 11
  • 34
  • Regarding your last paragraph, my answer addresses that issue (the part about creating a package first, and installing that package at the end after your component has been created). – Ken White Feb 09 '13 at 01:48
  • .. Maybe I was wrong about the close vote, this doesn't seem to be about runtime... I believe [documentation](http://docwiki.embarcadero.com/RADStudio/XE3/en/Introduction_to_component_creation_Index) is quite comprehensive about creating components at design time. – Sertac Akyuz Feb 09 '13 at 12:45
  • @KenWhite Thanks for the information about packaging the component. I was looking for the ability to modify the component on-the-fly during development and therefore didn't want to use the packaging solution for this simple override. – spurgeon Feb 09 '13 at 13:10
  • @KenWhite Now it appears I'll actually have to use the packaging method you described for a separate custom `TPanel` component, because I want this custom `TPanel` behavior to apply to only one instance within a `TForm` but not the others. – spurgeon Feb 09 '13 at 19:54

3 Answers3

18

The closest equivilent to your XCode approach is to use an "interposer" class in Delphi. Basically, you do not change the code that the IDE creates for the standard TToolBar usage. You instead declare a new class that derives from the standard TToolBar component but is also named TToolBar and you make it visible to the compiler after the standard TToolBar has been declared. Whichever TToolBar class is last seen by the compiler will be the actual class type that gets instantiated whenever the TForm DFM is streamed.

You can make your custom TToolBar class be seen by the compiler after the standard TToolBar class by one of two different ways:

  1. declare the custom TToolBar class in the same unit as your TForm class:

    unit MyForm;
    
    interface
    
    uses
      ..., Vcl.ComCtrls, ...;
    
    type
      TToolBar = class(Vcl.ComCtrls.TToolBar)
        // override what you need...
      end;
    
      TMyForm = class(TForm)
        ToolBar1: TToolBar; // <-- do not change this!
        ...
      end;
    
    implementation
    
    // implement custom TToolBar as needed...
    
    // normal TForm implementation code as needed ...
    
    end.
    
  2. you can declare the custom TToolBar class in its own unit that is then added to the TForm unit's uses clause after the ComCtrls unit has been added:

    unit MyToolBar;
    
    interface
    
    uses
      ..., Vcl.ComCtrls;
    
    type
      TToolBar = class(Vcl.ComCtrls.TToolBar)
        // override what you need...
      end;
    
    implementation
    
    // implement custom TToolBar as needed...
    
    end.
    

    .

    unit MyForm;
    
    interface
    
    uses
      ..., Vcl.ComCtrls, ..., MyToolBar;
    
    type
      TMyForm = class(TForm)
        ToolBar1: TToolBar; // <- do not change this!
        ...
      end;
    
    implementation
    
    // normal TForm implementation code as needed ...
    
    end.
    

This approach works on a per-project basis only. If you want to use your custom TToolBar class in multiple projects, then you are better off installing it into the IDE, like @KenWhite describes, and use it instead of the standard TToolBar. Go back to naming it TMyToolBar (or whatever), do not name it TToolBar anymore since it is not going to be used as an interposer. Make sure the Package is marked as "Runtime and Designtime" in its Project Options (creating separate runtime-only and designtime-ony Packages is outside the scope of this discussion). TMyToolBar will be available at design-time for you to drop on your TForm like any other component. If it is not, then you did not set it up correctly.

Gabriel
  • 20,797
  • 27
  • 159
  • 293
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 2
    +1, but I find a (local) class helper to be much better. Even has access to private members if you prefix them with Self in the helper methods. – Marjan Venema Feb 09 '13 at 08:33
  • 2
    A class helper can add new properties/methods to an existing class, but it cannot override existing methods. And only versions from the past few years support class helpers. – Remy Lebeau Feb 09 '13 at 11:26
  • Yep, I switched when I learned about them. And possibly inheriting could be faked by declaring a same-name method and using Self.MethodName in that. Compiler may barf on the same-name thing though. Can't try at the moment as I only have D5 on this machine. – Marjan Venema Feb 09 '13 at 12:34
  • @RemyLebeau Thanks, this is exactly what I needed. I chose option 2. It almost seems like this is a trick you learn in Delphi, particularly adding the `TMyToolBar` include last in the list (or at least after `Vcl.ComCtrls`). But, I guess the word "interceptor" makes sense then. – spurgeon Feb 09 '13 at 13:08
  • 2
    @Remy: Explanation for the edit: *"Contents, The Delphi Magazine Issue 33, May 1998"* ... .... *"Interposer Classes   Stephen Posey describes a clever way of exposing extra (usually hidden) properties and methods, as well as adding new ones, in components without all the hassle of creating a new component."* Not available online anymore AFAIK. – Sertac Akyuz Feb 09 '13 at 13:23
  • @SertacAkyuk: yes, I meant interposer, thanks. I don't use them myself, so I was fuzzy on the naming. – Remy Lebeau Feb 09 '13 at 20:25
  • interposer classes sound amazing. but I find them actually very dangerous: just (accidentally) change the order of your units and.... boooom. maybe a "there are dragons! (do not change order!)" comment next to the unit would calm down my fear a bit :) – Gabriel Feb 27 '19 at 10:43
5

To change a component on the existing form, it has to actually be a component that the IDE can create an instance of at design-time. This means the IDE has to be aware of it first, of course.

The way to do this is to create your own descendant component, and actually install it into the IDE in a design-time package. You can then drop it on your form instead of the standard version, or replace it on existing forms with a little work. (You do have to create your version and install it first, though.)

Start with File->New->Package (Delphi) from the IDE's menu. Save the package as you would any other project (for instance, MyComponents.dpk).

Now use File->New->Other->Delphi Files, and double-click Component in the right pane. The New Component wizard will start, where you can choose the existing component you want to descend from (or design a new one).

Follow the steps of the wizard, and you'll end up with the basic shell of your component:

unit MyToolBar1;

interface

uses
  System.SysUtils, System.Classes, Vcl.Controls, Vcl.ToolWin, Vcl.ComCtrls;

type
  TMyToolBar = class(TToolBar)
  private
    { Private declarations }
  protected
    { Protected declarations }
  public
    { Public declarations }
  published
    { Published declarations }
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TMyToolBar]);
end;

end.

Implement whatever functionality you want in the new descendant, and then save the file.

Right-click on the package in the Project Manager (by default the upper right window in the IDE), and choose Install from the context menu. This will compile and build the package, and automatically install it in the IDE. (The example I've shown would put the new component on the Samples page in the palette based on what's indicated in the RegisterComponents call.)

After doing the above, you can change an existing form (make a backup of the .pas and .dfm files first!). I'll use the TToolBar you mentioned, and the sample replacement I've posted the shell for in the instructions below.

Manually change the classname in the source code editor from TToolBar to TMyToolBar.

Right-click on the form, and choose View as Text from the context menu.

Find the TToolBar, and change it from TToolBar to TMyToolBar.

Right-click again, and choose View as Form from the context menu. If you've done these steps correctly, clicking on the toolbar should show you TMyToolBar in the Object Inspector. If you don't see it (or if you get error messages) you've done something wrong; you can close the tab by right-clicking it at the top of the Code Editor and choosing Close tab, and answer "No" to the prompt about saving changes, and then if necessary restore from the backup copies I told you to make first.

Ken White
  • 123,280
  • 14
  • 225
  • 444
  • Although this wasn't accepted as the answer, this is really a good quick tutorial for creating a custom Delphi component! – Edwin Yip Aug 02 '13 at 07:26
1

Create a unit for you class:

Unit YourComponent;
interface
uses
 ....
Type
 TYourNewClass=Class(ExistingClass)
   private
   ...
   protected
   ...   
   public
   ...
   published
  end;

  procedure Register;

implementation

.....

procedure Register;
begin
  RegisterComponents('YourPalette', [TYourNewClass]);
end;

create a new package (or open an own existing) and add you unit choose install on you Package.bpl.

bummi
  • 27,123
  • 14
  • 62
  • 101
  • By doing this (packaging and installing), how can I edit the new subclass on the fly? Do I have to re-package it every time I want to make an edit to the subclassed visual component? – spurgeon Feb 09 '13 at 00:28
  • 1
    Yes, you would have to recompile the Package every time you change its code. If you want something more "on-the-fly", then use the interceptor approach instead, like I describe in my answer. – Remy Lebeau Feb 09 '13 at 00:43