How can I implement my own custom property editor for all instances of a certain type?
Asked Answered
E

1

10

I have followed a few tutorials on creating a custom property editor dialog, but there are so many things involved that I could not get it to work right. What I am trying to accomplish is a custom form with a date picker (calendar), a time picker, and OK and Cancel buttons. The form is no problem at all, but how would I go about implementing this so that I can publish a property in any component of a certain type with a button to launch the property editor?

I would like to completely override the TDateTime type and put my custom editor in its place, so wherever a TDateTime is published and visible in the Object Inspector, I can use this editor to modify both date and time together in the same window.

The problem is that the documentation on creating a custom property editor is poor and although some resources are very thorough, they go into too much detail of the capabilities and lack getting to the point on the most common scenarios.

Eulogistic answered 19/12, 2012 at 2:47 Comment(0)
E
15

I did not want to ask this question here and expect anyone to answer it for me, so I did the research myself to solve my issues and I would like to share the unique experience involved in this mini project, as I'm sure others are frustrated with the same thing.

There are many different possibilities with custom property editors, dialogs, and component editors. This in particular would call for a TDateTimeProperty descendant. This would allow you to be able to edit the value of the property directly in the Object Inspector as plain text (String) while keeping the DateTime formatting.

I am assuming that you already have a general knowledge of creating custom components and a package in which you can publish this property editor from, because that's a lesson in its own which I will not cover. This calls for just one line of code to be placed inside the Register procedure, but we'll get to that later.

First, you need to create a new form in your Design-Time package, where your components are registered. Name the unit DateTimeProperty.pas, and name the form DateTimeDialog (thus making the form's class TDateTimeDialog). Place whatever controls you need, in this case a TMonthCalendar, TDateTimePicker (with Kind set to dtkTime), and 2 TBitBtn controls, one labeled OK with ModalResult of mrOK and the other labeled Cancel with ModalResult of mrCancel.

Your unit should look something like this:

unit DateTimeProperty;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.ComCtrls, Vcl.StdCtrls, Vcl.Buttons;

type
  TDateTimeDialog = class(TForm)
    dtDate: TMonthCalendar;
    dtTime: TDateTimePicker;
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
  private

  public

  end;         

var
  DateTimeDialog: TDateTimeDialog;

implementation

{$R *.dfm}

end.

And here is the DFM code behind this form:

object DateTimeDialog: TDateTimeDialog
  Left = 591
  Top = 158
  BorderIcons = [biSystemMenu]
  BorderStyle = bsToolWindow
  Caption = 'Pick Date/Time'
  ClientHeight = 231
  ClientWidth = 241
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  DesignSize = (
    241
    231)
  PixelsPerInch = 96
  TextHeight = 13
  object dtDate: TMonthCalendar
    Left = 8
    Top = 31
    Width = 225
    Height = 166
    Anchors = [akLeft, akRight, akBottom]
    Date = 41261.901190613430000000
    TabOrder = 1
  end
  object dtTime: TDateTimePicker
    Left = 8
    Top = 8
    Width = 113
    Height = 21
    Date = 41261.000000000000000000
    Time = 41261.000000000000000000
    Kind = dtkTime
    TabOrder = 2
  end
  object BitBtn1: TBitBtn
    Left = 158
    Top = 200
    Width = 75
    Height = 25
    Caption = 'OK'
    Default = True
    ModalResult = 1
    TabOrder = 0
  end
  object BitBtn2: TBitBtn
    Left = 77
    Top = 200
    Width = 75
    Height = 25
    Caption = 'Cancel'
    ModalResult = 2
    TabOrder = 3
  end
end

Now, add DesignEditors and DesignIntf to your uses clause. Make sure you have DesignIDE declared in the Requires of this Design-Time package. This is required for publishing any property editors.

In the form, create a new public property called DateTime of type TDateTime with a property getter and setter. This property will allow you to easily read/write the full TDateTime value the selection actually represents. So you should have this in your form:

private
  function GetDateTime: TDateTime;
  procedure SetDateTime(const Value: TDateTime);
public
  property DateTime: TDateTime read GetDateTime write SetDateTime;

....

function TDateTimeDialog.GetDateTime: TDateTime;
begin
  Result:= Int(dtDate.Date) + Frac(dtTime.Time);
end;

procedure TDateTimeDialog.SetDateTime(const Value: TDateTime);
begin
  dtDate.Date:= Value;
  dtTime.DateTime:= Value;
end;

Next we need to add the actual property editor class. Create this class just beneath the {$R *.dfm} which is just under implementation:

type
  TDateTimeEditor = class(TDateTimeProperty)
  public
    procedure Edit; override;
    function GetAttributes: TPropertyAttributes; override;
    function GetValue: String; override;
    procedure SetValue(const Value: String); override;
  end;

procedure TDateTimeEditor.Edit;
var
  F: TDateTimeDialog;
begin
  //Initialize the property editor window
  F:= TDateTimeDialog.Create(Application);
  try
    F.DateTime:= GetFloatValue;
    if F.ShowModal = mrOK then begin
      SetFloatValue(F.DateTime);
    end;
  finally
    F.Free;
  end;
end;

function TDateTimeEditor.GetAttributes: TPropertyAttributes;
begin
  //Makes the small button show to the right of the property
  Result := inherited GetAttributes + [paDialog];
end;

function TDateTimeEditor.GetValue: String;
begin
  //Returns the string which should show in Object Inspector
  Result:= FormatDateTime('m/d/yy h:nn:ss ampm', GetFloatValue);
end;

procedure TDateTimeEditor.SetValue(const Value: String);
begin
  //Assigns the string typed in Object Inspector to the property
  inherited;
end;

Finally, we need to add a Register procedure to perform the actual registration of this new property editor:

procedure Register;
begin
  RegisterPropertyEditor(TypeInfo(TDateTime), nil, '', TDateTimeEditor);
end;

Now there's an important piece to understand in this call to RegisterPropertyEditor. Since the 2nd and 3rd parameters are nil and an empty string, this means the editor will apply to all instances of TDateTime. Look into this procedure for more information about making it specific to certain components and property instances.

And here's the final result after installing...

Sample of final property editor

Some good resources for custom property editors which contributed are as follows:

  1. how to make custom component property?
  2. http://delphi.about.com/library/bluc/text/uc092501d.htm
  3. http://www.sandownet.com/propedit.html
Eulogistic answered 19/12, 2012 at 2:47 Comment(6)
more good resources can be FLOSS libraries like CnWizards or JediVCL, that implement and register some global property editors, just read and learnDisused
And don't forget Ray Konopka's Developing Custom Delphi 3 Components D3 and no longer in print, but still applicable. Ray has a pdf version for sale on his site: raize.com/DevTools/Ordering/Pricing.aspHyatt
+1 Nicely done. Would change the order of the time and date controls though... ;-)Mcginley
+1 I don't understand why you override GetValue and SetValue. In particular the GetValue forces your preferred date format on the world. But the SetValue doesn't. I'd just remove those two methods.Tobey
@DavidHeffernan Yes I do agree, I wanted to point out that it can be overridden for validation, etc although I didn't explain that part.Eulogistic
By the way, just for reference, this theoretically should apply to all Delphi versions 2+, right? Or is it 3+? But either way, I recall hearing that D1 had entirely different property editors than any later versions.Eulogistic

© 2022 - 2024 — McMap. All rights reserved.