How Delphi object inspector grey some properties? - delphi

Recently I found that Delphi object inspector displays some properties in grey. Here is an example:
I wonder what does it mean? How such properties are defined? I did not find any differences in definition of let's say DSHostname and ProxyHost. But as you can see DSHostname is displayed normally and ProxyHost in grey.
Here is a relevant declaration of properties in question:
/// <summary>The host to proxy requests through, or empty string to not use a proxy.</summary>
property ProxyHost: string read FProxyHost write FProxyHost;
/// <summary>The port on the proxy host to proxy requests through. Ignored if DSProxyHost isn't set.
/// </summary>
[Default(8888)]
property ProxyPort: Integer read FProxyPort write FProxyPort default 8888;
/// <summary>The user name for authentication with the specified proxy.</summary>
property ProxyUsername: string read FProxyUsername write FProxyUsername;
/// <summary>The password for authentication with the specified proxy.</summary>
property ProxyPassword: string read FProxyPassword write FProxyPassword;

Finally I got a proof that Remy Lebeau was right in his guess. I made a descendant of TDSClientCallbackChannelManager which has published property TestProxyHost. This property does nothing but mirroring ProxyHost in Get and Set. Here is the full code for the component:
unit uTestCallbackChannelManager;
interface
uses
System.SysUtils, System.Classes, Datasnap.DSCommon;
type
TTestCallbackChannelManager = class(TDSClientCallbackChannelManager)
private
function GetTestProxyHost: string;
procedure SetTestProxyHost(const Value: string);
published
property TestProxyHost: string read GetTestProxyHost write SetTestProxyHost;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TTestCallbackChannelManager]);
end;
{ TTestCallbackChannelManager }
function TTestCallbackChannelManager.GetTestProxyHost: string;
begin
Result := ProxyHost;
end;
procedure TTestCallbackChannelManager.SetTestProxyHost(const Value: string);
begin
ProxyHost := Value;
end;
end.
After I installed TTestCallbackChannelManager into component palette I dropped in on a form in a test project.
In Object Inspector the ProxyHost property is displayed in grey and TestProxyHost as normal. Now if I change TestProxyHost then ProxyHost is changed as well. Here is a screenshot:
This means:
RTTI information of ProxyHost property was not altered in any way and it is indeed a read/write property in both design- and run-time.
The only way to achieve such behavior is on the Property Editor level. The Property Editor registered for this particular property name in this component type "tells" Object Inspector "Hey, I can not set this property for you" (but an other code can do it directly).
This also explains why if I uncheck "Show read only properties" flag in Object Inspector options ProxyHost (and 3 related properties) are still shown in the Object Inspector. It is because of Object Inspector reads the properties from dfm as read/write and then creates Property Editors for them and once Property Editor says it can't write the property they are shaded in grey (but still shown as Property Editor is already created).
The only problem is what logic behind the Property Editor? When the properties become available and how to use them? It looks like the properties are introduced very recently in xe10 or a bit earlier. And Embarcadero provides no documentation about these properties (at least for now I could not find any). But this is a subject of separate question. I suspect that support for these properties has not been tested (or may be not implemented) yet and so they are intended for use in future releases.

Related

Is it always safe to remove a published empty section?

I'm working on an old legacy project which have several classes in which the published section is always declared without anything inside of it, i.e:
TMyClass = class
public
procedure DoSomething();
published
end;
On compiling, I get the following warning message:
[DCC Warning] uMyUnit.pas(141): W1055 PUBLISHED caused RTTI ($M+) to
be added to type 'TMyClass'
I don't know if the predecessor developer has declared these published sections for some valid reason.
Is it always safe to remove an empty published section or could it cause some changes in the application's behavior?
The difference for the class itself is none - however the important thing is that the default visibility of any class that inherits from a class with {$M+} then changes from public to published!
See this example code:
uses
TypInfo;
type
TMyClass = class
private
fName: string;
property Name: string read fName;
published
end;
TMyOtherClass = class(TMyClass)
property Name;
end;
var
propCount, i: Integer;
props: PPropList;
begin
propCount := GetPropList(TypeInfo(TMyOtherClass), props);
for i := 0 to propcount - 1 do
Writeln(props^[i].Name);
Readln;
end.
You can see that it lists the Name property but when you remove the published from TMyClass it will not - that is because once TMyClass got {$M+} added any member declared without explicitly stating the visibility it will be published opposed to public.
Also other members declared without visibility like fields will be published. This is being used in the streaming system Delphi uses for forms and such.
You can for example then call TObject.FieldAddress or TObject.MethodAddress passing in the name of a field or method and get back pointers to the field or method. It only works with published fields and methods.
This is how loading from a dfm sets up all those IDE generated fields like Button1 or connects the Button1Click method to the Button1.OnClick - they are without explicit visibility at the top of your form which inherits from TComponent that has {$M+} declared.
It depends
Does the rest of the code actually require access to RTTI for the class?
Only TPersistent-derived classes have the {$M+} directlive applied to them by default without needing a published section.
A published section is used for DFM streaming, which requires RTTI. Non-persistent classes are not steamed in DFMs, but there are other uses for RTTI.
So, without knowing what the rest of the code does, it is not really known whether removing empty published sections is safe or not.

How can I create an event handler for a non published but public event in delphi?

In RAD Studio 10.1 Berlin quite a few things has changed from previous version. In FMX there are a few previously published events that has now been changed to only be public.
I have a Multi Platform Project that uses a TStringGrid component and the OnDblClick event. When opening this project in Studio 10.1 I get warned that the Property OnDblClick does not exist.
The question is now how I can use the no longer published event?
(I must say that It's hard to understand why they haven't set mouse events to Published anymore. As far as I know most regular PCs and OSX machines doesn't have touch. A true Multi Target Project should be able to target these systems without hassle as they did in Studio 10 Seattle)
In case the event handlers already exist (which I imply by the error message), you can assign these handlers to their events in FormCreate.
procedure TForm1.FormCreate;
begin
StringGrid1.OnDblClick := StringGrid1DblClick;
end;
One solution is to make your own component where you extend the FMX.TStringGrid to have published event handlers again.
See here how to create a new FMX component: creating a firemonkey component
Here's the code to re-publish the mouse events.
unit MyStringGrid;
interface
uses FMX.Grids;
type
TMyStringGrid = class(TStringGrid)
published
property OnDblClick;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnMouseWheel;
property OnMouseEnter;
property OnMouseLeave;
end;
procedure Register;
implementation
uses FMX.Types;
procedure Register;
begin
RegisterComponents('NewPage', [TMyStringGrid]);
end;
initialization
RegisterFmxClasses([TMyStringGrid]);
end.
This has been reported as a bug here.
Looking at the source code in Delphi 10.1 berlin the public OnDblClick event is actually inherited from TControl class.
Similar the OnDblClick event is also inherited from TControl class with the exception that it is made public, like many other events that are inherited from TControl˙ class.
Any way it seems that guys at Embarcadero have been doing some refactoring by cleaning the parent property redeclarations˙(not sure if this is the right term) like:
type
TParentClass = clas(Tobject)
public
property ParentPropery: Integer read GetParentProperty write SetParentProperty;
TExampleClass = class(TParentClass)
public
property ParentPropery;
end;
Redeclaring ParentProperty in the above case is not needed as it will be available in all child classes unless you want to change its visibility from public to published for instance.
If you look at Delphi 10 Seattle source code you see that property OnDblClick is redeclared in several TStringGrid parent classes being published in TCustomScrollBox for the first time.

Don´t save my published property in component frame

I have a Component inherited of the TFrame... But my published properties don't save the values in .dfm... The error occurs when closing Delphi and open again, reloading the project.
This error is: "Error reading MyComponent1.Obs: property Obs does not exists..."
TMyComponent = class(TFrame)
FObs: string;
procedure SetObs(const Value: string);
published
property Obs: string read FObs write SetObs;
end;
procedure register;
implementation
procedure register;
begin
RegisterComponents('My Components', [TMyComponent]);
end;
procedure TMyComponent .SetObs(const Value: string);
begin
if FObs <> Value then
FObs := Value;
end;
I've managed to reproduce the error and then fix it, but can't understand exactly why it happens so, has to do with visual inheritance which works in rather complex way (hope David or Remy could explain what happens here).
First of all, this error indicates that property actually was saved to dfm file. Point is, it's perfectly normal situation when some properties are absent in dfm, it just means that property must have default value (or value which ancestor had), so IDE never raises error because it didn't find some property in file.
Here the opposite happens: property 'obs' was found in dfm, but wasn't found in class itself. The same happened when I created new project and then added frame unit to project itself. It seems, in that case visual inheritance took place instead of normal inheritance, which is: IDE found, that class TMyComponent is described in 'visual' unit belonging to project, found that line:
TMyComponent = class (TFrame)
and made TMyComponent merely alias for TFrame. That's why error occurred: TFrame really doesn't have 'obs' property.
And here is workaround: Don't add this frame to project itself. Instead, add frame from component palette. This way it works as expected. If IDE can't find files of this component, add folder containing them to search path.

Delphi: browsing components inside a property editor

When a property is a simple component of any class, the IDE's property editor is able to drop down a list of all compatible components in all the project's forms.
I want to do some equivalent task, but with some filtering based on acceptable component classes for the property; these classes common ancestor is only TComponent and they have custom interfaces.
Currently I have a working property editor that uses a paValueList attribute and some filtering in the GetValues procedure, based on checking the supported interfaces, but it is limited to the current form :-(.
How to browse all the forms like the IDE does?
I want to do some equivalent task, but with some filtering based on acceptable component classes for the property; these classes common ancestor is only TComponent and they have custom interfaces.
If you are filtering for only 1 interface, you should change the property in question to accept that interface type instead of a TComponent, and then the default property editor for interface properties (TInterfaceProperty) will filter the components automatically for you:
property MyProperty: IMyInterface read ... write ...;
Currently I have a working property editor that uses a paValueList attribute and some filtering in the GetValues procedure, based on checking the supported interfaces, but it is limited to the current form :-(.
How to browse all the forms like the IDE does?
To manually filter the components in a custom property editor, you need to do the same thing that the default component property editor (TComponentProperty) does to obtain the compatible components, and then you can filter them further as needed.
Internally, TComponentProperty.GetValues() simply calls Designer.GetComponentNames(), passing it the PTypeData of the property type that is being edited:
procedure TComponentProperty.GetValues(Proc: TGetStrProc);
begin
Designer.GetComponentNames(GetTypeData(GetPropType), Proc);
end;
So, if your property accepts a TComponent (since that is the only common ancestor of your intended components):
property MyProperty: TComponent read ... write ...;
Then GetPropType() in this case would return TypeInfo(TComponent).
GetComponentNames() (whose implementation is in the IDE and not available in the VCL source code) enumerates the components of the Root (Form, DataModule, or Frame) that owns the component being edited, as well as all linked Root objects that are accessible in other units specified in the edited Root's uses clause. This is documented behavior:
DesignIntf.IDesigner60.GetComponentNames
Executes a callback for every component that can be assigned a property of a specified type.
Use GetComponentNames to call the procedure specified by the Proc parameter for every component that can be assigned a property that matches the TypeData parameter. For each component, Proc is called with its S parameter set to the name of the component. This parameter can be used to obtain a reference to the component by calling the GetComponent method.
Note: GetComponentNames calls Proc for components in units that are in the uses clause of the current root object's unit (Delphi) or included by that unit (C++), as well as the entity that is the value of Root.
So, in your GetValues() implementation, call Designer.GetComponentNames() specifying the PTypeData for TComponent and let the IDE enumerate all available units and provide you with a list of each component's Name. Then you can loop through that list calling Designer.GetComponent() to get the actual TComponent objects and query them for your desired interface(s):
procedure TMyComponentProperty.GetValues(Proc: TGetStrProc);
var
Names: TStringList;
I: Integer;
begin
Names := TStringList.Create;
try
Designer.GetComponentNames(GetTypeData(TypInfo(TComponent)), Names.Append);
for I := 0 to Names.Count-1 do
begin
if Supports(Designer.GetComponent(Names[I]), IMyInterface) then
Proc(Names[I]);
end;
finally
Names.Free;
end;
end;
In fact, this is very similar to what the default TInterfaceProperty.GetValues() implementation does:
procedure TInterfaceProperty.ReceiveComponentNames(const S: string);
var
Temp: TComponent;
Intf: IInterface;
begin
Temp := Designer.GetComponent(S);
if Assigned(FGetValuesStrProc) and
Assigned(Temp) and
Supports(TObject(Temp), GetTypeData(GetPropType)^.Guid, Intf) then
FGetValuesStrProc(S);
end;
procedure TInterfaceProperty.GetValues(Proc: TGetStrProc);
begin
FGetValuesStrProc := Proc;
try
Designer.GetComponentNames(GetTypeData(TypeInfo(TComponent)), ReceiveComponentNames);
finally
FGetValuesStrProc := nil;
end;
end;
The only difference is that TInterfaceProperty does not waste memory collecting the names into a temp TStringList. It filters them in real-time as they are being enumerated.
Remy's solution works perfectly for my needs.
Nevertheless I've "simplified" a bit the filtering procedure:
procedure TMyComponentProperty.ReceiveComponentNames(const S: string);
var
Temp: TComponent;
Intf: IInterface;
begin
if Assigned(FGetValuesStrProc) then
begin
Temp := Designer.GetComponent(S);
if Assigned(Temp) then
if Temp.GetInterface(IMyInterface, IntF) then
FGetValuesStrProc(S);
// May add other interfaces checks here
end;
end;

Unable to declare string property defaults

I'm writing a component which consists of many properties which are to appear in the Delphi IDE Object Inspector (published properties)...
type
TMyComponent = class(TComponent)
private
FMyProperty: String;
published
property MyProperty: String read FMyProperty write SetMyProperty default 'Something';
end;
However, it's not allowing me to apply a default value to a string property...
[DCC Error] MyUnit.pas(278): E2146 Default values must be of ordinal, pointer or small set type
All other property defaults work fine (Integer, Enum, etc.).
My goal is to A) not save string properties to the DFM if they're the default value, and B) show the value in the Object Inspector as Bold if it's not the default, and regular if it is. There are over 130 properties which show for this component, and about 50 of them are string properties, some with rather large default values.
Why am I not allowed to declare a string property with a default value? Is this a shortcoming with Delphi, or is there a technical reason why strings can't be defaulted?
EDIT
If you really want to know what I'm doing, I'm encapsulating Inno Setup and wrapping the functionality into a component with an extensive property/collection editor. This topic pertains to just the Setup section alone, which consists of actually over 100 properties. Only about 20 of these properties are expected to actually be used for simple implementation, and therefore I don't want all the rest of those string properties to bloat the size of the DFM (if they're set to their defaults). Based on how the component is set up, it will produce an Inno Setup script file.
Only numeric properties can have a default value specified in the property declaration. However, you can use the stored specifier instead, eg:
type
TMyComponent = class(TComponent)
private
FMyProperty: String;
function MyPropertyIsStored: Boolean;
procedure SetMyProperty(const Value: String);
public
constructor Create(AOwner: TComponent); override;
published
property MyProperty: String read FMyProperty write SetMyProperty stored MyPropertyIsStored;
end;
constructor TMyComponent.Create(AOwner: TComponent);
begin
Inherited;
FMyProperty := 'my default value';
end;
function TMyComponent.MyPropertyIsStored: Boolean;
begin
Result := FMyProperty <> 'my default value';
end;
procedure TMyComponent.SetMyProperty(const Value: String);
begin
if FMyProperty <> Value then
begin
FMyProperty := Value;
// update component as needed...
end;
end;
It's not clear what you're trying to do. Assigning default to a property has two uses:
To decide whether the property value is streamed to the DFM or not (used for ordinal or Boolean properties, usually, such as Visible - since the default would be True, there's no reason to write it to the DFM unless it's False. (See note below)
For array properties, to indicate that the array is the default property of the class (such as Delphi's TList.Items, where having Items the default allows you to use List[x] instead of List.Items[x] in your code.
If your intent is to provide a default value to the string so it shows in the Object Inspector, simply set the value in the component constructor. If the user has assigned a different value, the value set in the constructor will be overwritten when the DFM content is streamed in.
As far as why string default values are not allowed, this is clearly stated in the documentation (see "Storage Specifiers" (emphasis mine):
The default and nodefault directives are supported only for ordinal types and for set types, provided the upper and lower bounds of the set's base type have ordinal values between 0 and 31; if such a property is declared without default or nodefault, it is treated as if nodefault were specified. For reals, pointers, and strings, there is an implicit default value of 0, nil, and '' (the empty string), respectively.
Note: The default is used in conjunction with the stored specifier that Remy's answer describes. From the same docs linked and quoted above (same section):
When saving a component's state, the storage specifiers of the component's published properties are checked. If a property's current value is different from its default value (or if there is no default value) and the stored specifier is True, then the property's value is saved. Otherwise, the property's value is not saved.

Resources