Can a Variant property have default value? - delphi

I've written a component who has a Variant property for which I would like to set a default value.
TMyComponent = class(TComponent)
private
FVariantValue : Variant;
published
property VariantValue : Variant read FVariantValue write FVariantValue default False;
end;
On compilation, I get the following error on the VariantValue property line:
E2026 Constant expression expected
Doing the same thing with a Boolean property doesn't cause any kind of error.
I read a little bit of documentation but I didn't find nothing about Variant properties default values.

Be careful here. The default directive does not do anything to set the value of the property itself. It only affects whether or not the value is explicitly saved in the .dfm file. If you specify a default value for a property you still have to ensure that the constructor initializes the backing field to that value.
Properties : Storage Specifiers
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.
Note: Property values are not automatically initialized to the default value. That is, the default directive controls only when property values are saved to the form file, but not the initial value of the property on a newly created instance.
This is just a hint to the component streaming system that it doesn't need to store this value explicitly in the .dfm - your part of the contract is to ensure that you actually initialize the backing field to that value. The appropriate place to do this type of initialization is in the component's constructor:
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FVariantValue := False;
end;
That said, False is a boolean, not a variant, so it cannot be used as a constant expression of Variant type. Since a variant is a complex type it cannot be expressed as a single constant and, therefore, cannot have a default property.
Per Remy, if you want to ensure that the variant is not saved in the .dfm file when the backing variant is False, you can use the stored directive with a parameterless method which returns False when the variant evaluates to a boolean False. For example :
property VariantValue : Variant read FVariantValue write FVariantValue stored IsVariantValueStored;
where
function TMyComponent.IsVariantValueStored : Boolean;
begin
Result := not VarIsType(FVariantValue, varBoolean);
if not Result then
Result := FVariantValue;
end;

Variant properties cannot have default values.

The best thing to do is set
FVariantValue := false;
in the constructor or procedure AfterConstruction; override;

Related

Boolean property seen as constant [duplicate]

This question already has an answer here:
Get TAdvEdit.Text directly from procedure/function
(1 answer)
Closed 2 years ago.
I have a custom component, with a TPersistent published to the object inspector. This class is filled with boolean properties, such as...
type
TMyClass = class(TPersistent)
...
published
property SomeBool: Boolean read FSomeBool write SetSomeBool;
end;
Then, elsewhere outside of this component, I'm trying to write to these properties by using a subroutine, accepting a boolean as a var parameter:
procedure LoadValues;
procedure CF(var AVal: Boolean);
begin
AVal:= False;
end;
begin
CF(MyComponent.MyClass.SomeBool);
// ... repeated on 40 boolean properties in this class
end;
The problem is that it fails to compile, telling me:
E2197 Constant object cannot be passed as var parameter
I've searched around and cannot find out why. Solutions for other people were along the lines of "Use const, not var". But that completely defeats the purpose of what I'm trying to do here. There are 40 Boolean properties in this class, and I'm trying to consolidate assigning these values to just a single line for each.
Why do I get this error, and how do I get around it?
I found a work-around for the issue. Note that in reality, I'm not assigning them all to False - that was just an example. Really, there's more to specify whether each value should be true or false.
As mentioned in the comments, a var parameter (of course) requires a variable, whereas a property does not qualify. Rather than trying to pass each property as a var parameter, instead the subroutine could be a function, returning the appropriate boolean value:
procedure LoadValues;
function CF: Boolean;
begin
Result:= False; //Or whatever value is needed
end;
begin
MyComponent.MyClass.SomeBool:= CF;
// ... repeated on 40 boolean properties in this class
end;

How to get property of the 'record' type using TypInfo unit

I have this record type
TDoublePoint = record
X : Double;
Y : Double;
end;
then I have object with this property
uses ..TypInfo;
TCell = class(TPersistent)
private
FZoom : TDoublePoint
published
property Zoom : TDoublePoint read FZoom write FZoom;
end;
But when I want to get PropInfo of this property with this function:
function GetKind(AObject:TObject; Propertyname :shortstring):TTypeKind;
var p :ppropinfo;
begin
p:=GetPropInfo(AObject, Propertyname); // <p = nil
Result:= p^.proptype^.Kind;
end;
..
..
c := TCell.Create;
GetKind(c, 'Zoom'); // <- error
c.Free;
I get error, because variable p is nil in the function.
But Why?
There is tkRecord in the TTypeKind, so I expected no problems to read/write the property of record type, but it seems, it is not possible (?)
Google search did not tell much.
Delphi 7 does not generate RTTI for a record type by default, and so a published property that uses a record type will not have RTTI, either (you can use TypInfo.GetPropList() to confirm that).
At one point, this was a documented limitation:
Published properties are restricted to certain data types. Ordinal, string, class, interface, variant, and method-pointer types can be published.
However, there is a workaround. IF a record type contains any compiler-managed data types (long strings, interfaces, dynamic arrays, etc), then RTTI will be generated for that record type, as will any published property that uses that record type, and thus GetPropInfo() can find such properties (I have confirmed that this does work in Delphi 7).
Your TDoublePoint record does not contain any compiler-managed data types, so that is why GetPropInfo() is returning nil for your TCell.Zoom property.
That RTTI issue was fixed in a later version (not sure which one. I'm guessing maybe in Delphi 2010, when Extended RTTI was first introduced). For instance, the code you have shown works for me as-is in XE. GetPropInfo() can find the Zoom property as expected, without having to introduce any compiler-managed types into the TDoublePoint record type.

Duplicate identifier of property and method parameter of a class

I transferred my project from Delphi to Lazarus. In a form I have a private method with parameter var Active: Boolean. In Delphi it was ok, but Lazarus give an error Error: Duplicate identifier "Active" and Hint: Identifier already defined in unit FORMS at line 641, on line 641 there is:
property Active: Boolean read FActive;
It is not difficult to change parameter name (with refactoring), but why can't I use the same name for property and parameter of method?
To make sure it is not an error of automatic conversion from Delphi, I created new project in Lazarus and added private method
procedure Test(var Active: Boolean);
The result was the same. Even if I use const or nothing instead of var.
I've looked into FPC docs and didn't find any such limitations. I'm just curious.
You should be able to use the same name for a property and a parameter. They have different scope, so the one nearest in scope (the parameter, which should be treated as being in the same scope as a local variable) should hide the one "further away" in scope (the property). In Delphi, you can still access the property, even inside that method, but then you should qualify it as Self.Active:
procedure TForm1.Test(var Active: Boolean);
var
ParamActive: Boolean;
FormActive: Boolean;
begin
ParamActive := Active; // gets the var parameter
FormActive := Self.Active; // gets the property
...
end;
I have no idea why FPC flags it as an error. It shouldn't.
Update
FWIW, if you change
{$mode objfpc}
to
{$mode delphi}
It does compile as expected, and you won't get an error. I just tried this.

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.

Class default property of boolean set to true gives false on run-time

I've got a simple component class with boolean property:
TmyClass = class(TComponent)
private
fSomeProperty: boolean;
published
property SomeProperty: boolean
read fSomeProperty
write fSomeProperty
default true;
end;
I put it on my form, set it to true (SomeProperty is set to false, why?), but when i'm trying to access SomeProperty from run-time it's giving me false. Why is that so?
Thats because the default specifier don't actually assign the value to the property, it just says to the streaming system which value is the default (and thus doesn't need to be saved). You still have to initialize the prop/field in the constructor to the desired default value. This is documented in the help btw, read the "Storage Specifiers" section
You should also set the property to True in the constructor - otherwise it is an error:
constructor TMyClass.Create(AOwner: TComponent);
begin
inherited;
FSomeProperty:= True;
...
end;
Default values determine what will be stored in *.DFM file. If you set FSomeProperty to True at design time, and default value for FSomeProperty is True, then FSomeProperty will not be stored in *.DFM.
If you don't initialize FSomeProperty to True in the constructor you get an error you described - FSomeProperty appears False at runtime, though it was set True at design time.

Resources