Title: Write access to a read-only property
Question: Some times in the VCL code, the properties are read-only, byt you are extending the VCL code and really needs to write it. How to do it?
Answer:
Here I put an example of setting the PixelsPerInch property of TScreen class. It is read-only and sometimes you need to change it without changing the resolution of your screen.
First you need to create a descendant of the class you want access. Inside published you must redeclare the property you want to access, like this:
type
TScreenEx = class(TScreen)
published
property PixelsPerInch;
end;
Note that TScreen indirectly inherits from TPersistent, and TPersistent was compiled in the $M+ mode, so published properties in our TScreenEx class will have RTTI generated for them. We need the RTTI information to write to this propery.
But PixelsPerInch is still a read-only property - and there is no way our TScreenEx can make it writeable, because the backing field FPixelsPerInch is private, not protected.
The RTTI generated for the TScreenEx.PixelsPerInch property includes enough information about where to find the backing field in the object instance. We will use TPropInfo record, from TypInfo.pas unit, because it describes the RTTI for each property. We can get the address of the original property with the GetProc pointer. For backing fields, this contains the offset off the field in the object instance (sans some flag bits). After decoding this offset and adding it to the base address of the object instance, we now can get a pointer to the backing field and thus modify it - voila write-access!
Here is the accesing code version:
procedure SetPixelsPerInch(Value: integer);
begin
PInteger(Integer(Screen) + (Integer(GetPropInfo(TScreenEx,
'PixelsPerInch').GetProc) and $00FFFFFF)))^ := Value;
end;
Note 1: You should not put this code within your TScreenEx class (do not put this code in a method, instead call a local function that implements it) because it will not work unless it is out of the refering class.
Note 2: To use it with classes taht does not have global variables like Screen use the folllowing code (supposing ReadOnlyProperty is of Float Type):
TClassEx = class(TOriginalClass)
published
property ReadOnlyProperty;
end;
procedure SetPixelsPerInch(OriginalObject: TOriginalClass;
Value: Float);
begin
PInteger(Integer(OriginalObject) +
(Integer(GetPropInfo(TClassEx,
'ReadOnlyProperty').GetProc) and $00FFFFFF)))^ := Value;
end;