Background
The following is the relevant code.
TCustomEdit = class(TWinControl) private FReadOnly: Boolean; procedure SetReadOnly(Value: Boolean); protected property ReadOnly: Boolean read FReadOnly write SetReadOnly default False; end; TEdit = class(TCustomEdit) published property ReadOnly; end; |
First Attempts (Did not work)
The first thing I tried was to simply replace the existing property with a new definition.
property ReadOnly: Boolean read FReadOnly write SetReadOnly default False; procedure TMyCustomEdit.SetReadOnly(const Value: Boolean); begin FReadOnly := Value; // sets the local value if FReadOnly then color := FColorWhenReadOnly else color := FColorWhenReadWrite; end; |
procedure TCustomEdit.SetReadOnly(Value: Boolean); begin if FReadOnly <> Value then begin FReadOnly := Value; if HandleAllocated then SendMessage(Handle, EM_SETREADONLY, Ord(Value), 0); // key line - actually changes the component behavior end; end; |
Unfortunately, there is no way to directly access the setter method (since it is private). Instead, I was able to access the parent property using the following.
property ReadOnly: Boolean read FReadOnly write SetReadOnly default False; procedure TMyCustomEdit.SetReadOnly(const Value: Boolean); var tempEdit : TEdit; begin tempEdit := self as TEdit; // cast to parent class tempEdit.ReadOnly := Value; // this calls the parent class setter method FReadOnly := Value; // sets the local value, displayed in Object Inspector if FReadOnly then color := FColorWhenReadOnly else color := FColorWhenReadWrite; end; |
As I said, my first attempt at the simple solution (above) failed. Next I tried copying the code from TCustomEdit.SetReadOnly to my property .. and it also failed.
property ReadOnly: Boolean read FReadOnly write SetReadOnly default False; procedure TMyCustomEdit.SetReadOnly(Value: Boolean); begin if FReadOnly <> Value then begin FReadOnly := Value; if HandleAllocated then SendMessage(Handle, EM_SETREADONLY, Ord(Value), 0); end; if FReadOnly then color := FColorWhenReadOnly else color := FColorWhenReadWrite; end; |
When I set a breakpoint and single stepped the code, it worked .. when I ran it without breakpoints, it failed. The reason was far from obvious.
(To debug this type of problem, you must enable Debug DUCs under Project / Options... / Compiler.)
I eventually determined that when properties are set as the program starts, none of the windows implemented objects exist. As a result, HandleAllocated returns false because FHandle equals zero. However, when setting breakpoints and single stepping the program, I let the mouse hover over the Handle variable .. and THIS caused the program to go ahead and create the edit window.
To be clear, when I
(I love intermittent software .. don't you?)
This is because window'ed components (such as TEdit) are not created until AFTER TForm.FormCreate is called. As a result, the code that actually creates the components must read the available properties and send them to windows. In the case of ReadOnly, I had overridden the original definition and used my own FReadOnly variable. When the component was finally created, the following code used the original variable.
TCustomEdit = class(TWinControl) protected procedure CreateParams(var Params: TCreateParams); override; end; procedure TCustomEdit.CreateParams(var Params: TCreateParams); const Passwords: array[Boolean] of DWORD = (0, ES_PASSWORD); ReadOnlys: array[Boolean] of DWORD = (0, ES_READONLY); CharCases: array[TEditCharCase] of DWORD = (0, ES_UPPERCASE, ES_LOWERCASE); HideSelections: array[Boolean] of DWORD = (ES_NOHIDESEL, 0); OEMConverts: array[Boolean] of DWORD = (0, ES_OEMCONVERT); begin inherited CreateParams(Params); CreateSubClass(Params, 'EDIT'); with Params do begin Style := Style or (ES_AUTOHSCROLL or ES_AUTOVSCROLL) or BorderStyles[FBorderStyle] or Passwords[FPasswordChar <> #0] or ReadOnlys[FReadOnly] or CharCases[FCharCase] or HideSelections[FHideSelection] or OEMConverts[FOEMConvert]; if NewStyleControls and Ctl3D and (FBorderStyle = bsSingle) then begin Style := Style and not WS_BORDER; ExStyle := ExStyle or WS_EX_CLIENTEDGE; end; end; end; |
procedure TMyCustomEdit.CreateParams(var Params: TCreateParams); begin inherited CreateParams(Params); with Params do begin if FReadOnly then Style := Style or ES_READONLY else Style := Style and not ES_READONLY; end; end; |
Other Techniques
var tempEdit : TEdit; begin tempEdit := self as TEdit; // cast to parent class tempEdit.ReadOnly := Value; // this calls the parent class setter method |
(TEdit)self.ReadOnly := Value; // this calls the parent class setter method |
(self as TEdit).ReadOnly := Value; // this calls the parent class setter method |
function TCustomMaskEdit.GetEditText: string; begin Result := inherited Text; // simply access the property - never thought of this end; procedure TCustomMaskEdit.SetText(const Value: string); begin if not IsMasked then inherited Text := Value // simply access the property - never thought of this else ..... |
Final Code
TMyCustomEdit = class(TEdit) private FColorWhenReadOnly : TColor; // the *color* property is the currently displayed color FColorWhenReadWrite: TColor; // these allow the color to indicate the readonly state procedure SetReadOnly(const Value: Boolean); public constructor Create(AOwner: TComponent); override; published property ColorWhenReadOnly : TColor read FColorWhenReadOnly write SetColorWhenReadOnly default clBtnFace; property ColorWhenReadWrite: TColor read FColorWhenReadWrite write SetColorWhenReadWrite default clWindow; property ReadOnly write SetReadOnly; end; constructor TMyCustomEdit.Create(AOwner: TComponent); begin inherited; FColorWhenReadOnly := clBtnFace; FColorWhenReadWrite := clWindow ; end; procedure TMyCustomEdit.SetReadOnly(const Value: Boolean); var tempEdit : TEdit; begin tempEdit := self as TEdit; // cast to parent class tempEdit.ReadOnly := Value; // this calls the parent class setter method if Value then color := FColorWhenReadOnly else color := FColorWhenReadWrite; end; |
Comment
I hope you find these notes useful.
Advice