views:

1388

answers:

3

This should be very simple but I can't find the exact answer I want. I have a custom delphi control based on TSpeedButton. I want the Caption Property of the SpeedButton to always be 'Comments' but I don't want to set it at run-time I want to set it in the component itself so that when I place it on my form it's already populated with this text. I also want to set the height and width of the button but I imagine the method for doing this will be the same as for setting the caption.

For the sake of completeness, here is the component code:

unit CustomSpeedButton;

interface

uses
  SysUtils, Classes, Controls, Buttons;

type
  TCustomSpeedButton = class(TSpeedButton)
  private
    FCommentText: string;
    FCommentTitle: string;

    procedure SetCommentText(const Value: string);
    procedure SetCommentTitle(const Value: string);

    { Private declarations }
  protected
    { Protected declarations }
  public
    { Public declarations }

  published
    { Published declarations }
    property CommentTitle: string read FCommentTitle write SetCommentTitle;
    property CommentText: string read FCommentText write SetCommentText;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Standard', [TCustomSpeedButton]);
end;

{ TCustomSpeedButton }

procedure TCustomSpeedButton.SetCommentText(const Value: string);
begin
  FCommentText := Value;
end;

procedure TCustomSpeedButton.SetCommentTitle(const Value: string);
begin
  FCommentTitle := Value;
end;

end.
+2  A: 

You need to set the original values in the component's constructor.

EDIT: You'll also want to put add ControlStyle := ControlStyle - [csSetCaption]; to the constructor.

Mason Wheeler
@Mason: That won't always work. The constructor is called, and then properties are set as the DFM is streamed in. What you do in the constructor can be replaced by content in the DFM.
Ken White
Yes, and that's the expected and desired behavior. If the user enters something in a property, saves, reloads, and what they saved has been changed, they're going to be annoyed.
Mason Wheeler
I've read that it should be done in the constructor before and I swear I used to know where that was but now I'm pulling my hair out trying to find it!
Rafe
It's not there because you haven't written it yet. Declare a constructor on the class that looks like this: `constructor Create(AOwner: TComponent); override;` Have it call the inherited constructor and then set up default values.
Mason Wheeler
Hehe, apparently I need to go find me a 'Delphi for Dummies' or some such reference! That was the info I needed, Mason. Thanks!
Rafe
-1: You cannot use 'default' for string type properties; you also need to add "ControlStyle := ControlStyle - [csSetCaption];" to the constructor.
Jeroen Pluimers
Oops! Thanks. Fixed now.
Mason Wheeler
A: 

Override the Loaded event, and set your caption there. Loaded is called immediately after the component is streamed in from the DFM, and anything you do there has priority over what was done at designtime.

You might want to test for csDesigning in ComponentState in Loaded, and not do anything if it's set. It's only set at designtime.

Ken White
It's a very bad idea to override changes that the user has made, especially if you do it with no explanation.
Mason Wheeler
I didn't recommend it. I answered the question that was asked.
Ken White
+3  A: 

Since you wanted the Caption property to be done properly, Mason's answer is not going to work because he missed the 'csSetCaption' thing, and his suggestion about 'default' will not work either because both the Caption and your properties are string types.

Below are the units as you want them.

The behaviour is as follows:

  1. intially the value of the Caption property will be 'Comments'
  2. users can override that at design time by setting a new value

(If you do not want 2., then you need to assign the Caption property in an overrided Loaded method like Ken mentioned; however, it was not clear from your question if you wanted that. If you do, then please rephrase your question.)

This is how the code works.

For string properties, you cannot hint the streaming system of any default. But you can set an initial value for design time in the constructor: Caption := DefaultCustomSpeedButtonCaption;

For the Caption property, you must also disable the default assignment of the Caption property (otherwise your component would be automatically get a caption like 'CustomSpeedButton1'). This line does that for you: ControlStyle := ControlStyle - [csSetCaption];

Finally it is good practice to split your component registration into a separate unit. That allows you to have a design-time package that registers your components in the IDE and a run-time package (or no package at all) for using your components in your applications.

If you have a component icon, then you load that in the registration unit as well (since it is only needed at design time).

Ray Konopka has written an excellent book on component writing which is still very valid: Developing Custom Delphi 3 Components Like many good Delphi books, it is out of print, but you can order a PDF copy on his site.

I'm not sure what your CommentTitle and CommentText properties are for, so I have kept them in the source below.

Listing 1: actual component

unit CustomSpeedButtonUnit;

interface

uses
  SysUtils, Classes, Controls, Buttons;

const
  DefaultCustomSpeedButtonCaption = 'Comments';

type
  TCustomCustomSpeedButton = class(TSpeedButton)
  strict private
    FCommentText: string;
    FCommentTitle: string;
  strict protected
    procedure SetCommentText(const Value: string); virtual;
    procedure SetCommentTitle(const Value: string); virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property CommentTitle: string read FCommentTitle write SetCommentTitle;
    property CommentText: string read FCommentText write SetCommentText;
  end;

  TCustomSpeedButton = class(TCustomCustomSpeedButton)
  published
// note you cannot use 'default' for string types; 'default' is only valid for ordinal ordinal, pointer or small set type
// [DCC Error] CustomSpeedButtonUnit.pas(29): E2146 Default values must be of ordinal,  
//    property Caption default DefaultCustomSpeedButtonCaption;

pointer or small set type property CommentTitle; property CommentText; end;

implementation

constructor TCustomCustomSpeedButton.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Caption := DefaultCustomSpeedButtonCaption;
  ControlStyle := ControlStyle - [csSetCaption];
end;

destructor TCustomCustomSpeedButton.Destroy;
begin
  inherited Destroy;
end;

procedure TCustomCustomSpeedButton.SetCommentText(const Value: string);
begin
  FCommentText := Value;
end;

procedure TCustomCustomSpeedButton.SetCommentTitle(const Value: string);
begin
  FCommentTitle := Value;
end;

end.

Listing 2: component registration

unit CustomSpeedButtonRegistrationUnit;

interface

procedure Register;

implementation

uses
  CustomSpeedButtonUnit;

procedure Register;
begin
  RegisterComponents('Standard', [TCustomSpeedButton]);
end;

end.
Jeroen Pluimers
Thanks Jeroen. After several false starts I came to the conclusion I was just asking for too much and ended up just selecting all the buttons after pasting them to the page and altering their characteristics as a 'batch'. The complete listing you give above though shows that all is not lost so I appreciate your input. Also, I actually own a copy of that book on Delphi 3 components (D3 was my first exposure to Delphi back around '98) .. I never got around to reading it but will do so now!
Rafe
Hm, interesting. I started a new component to try it your way and after creating the component registration unit and then saving and attempting to switch to another project the IDE kicked me out and wont restart now .. only thing I did differently was modify the register procedure to just say 'begin register; end;' because registercomponents kept throwing an error about it being unrecognized. Must be a way to clear the IDE cache somehow ..
Rafe
Ooops - if you do a "procedure Register; begin Register; end;" then you get an infinite loop. And when you put such a loop in a design-time package that gets loaded in the IDE, then the IDE will hang.The best solution for that is to close the Delphi IDE, find the component package .bpl that you got the infinite loop into, then delete that .bpl file, then start the Delphi IDE.Hope that helps. If not, drop me an email (almost anything at pluimers dot com will get to me, but the easiest is to use my first name). I can do a TeamViewer session with you to resolve it.Good luch!--jeroen
Jeroen Pluimers