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