Float property non-zero default, is it possible?
I would like to use a float property in my component, but set it to some non-zero default value (let's say it is 1000.0). If I try to do this in the Create, the property start to behave wildly since the default value for floats it 0 (see classes.TWriter.WriteProperty.WriteFloatProp.IsDefaultValue) so when I override some value with 0 in the form designer, delphi doesn't save this value (it's default in this case), but my Create will set it to 1000.0 when the compoent will be loaded the next time, so the actually I have the value I didn't set.
The problem is that there is no way to set d开发者_StackOverflow社区efault with 'default' directive (the compiler says 'Default values must be of ordinal, pointer or small set type') and it's also not possible to force storing with stored directive, it doesn't work (Delphi 5)
So is there a chance to find a workaround?
Thanks,
Max
Maybe you can used the stored
directive:
property MyFloat: Float read GetValue write SetValue stored IsMyFloatStored;
with a Boolean function IsMyFloatStored
that returns True iff MyFloat doesn't have its default value.
I fixed this by implementing the DefineProperties method in my descendant of TPersistent. I wanted to make a general solution to this problem that I could easily implement in all of my objects for all the relevant properties. Here's how it looks:
TOverridePropFiler = class
private
FReadWritePropName: string;
FObject: TObject;
procedure ReadOverrideProp(Reader: TReader);
procedure WriteOverrideProp(Writer: TWriter);
public
constructor Create(Filer:TFiler; Obj:TObject; Name:string);
end;
{ TOverridePropFiler }
constructor TOverridePropFiler.Create(Filer:TFiler; Obj:TObject; Name:string);
begin
FReadWritePropName:=Name;
FObject:=obj;
if (Name='') or not Assigned(GetPropInfo(Obj,Name)) then
Raise Exception.CreateFmt('Property %s not found in object %s',[Name,Obj.ClassName]);
Filer.DefineProperty(Name, ReadOverrideProp, WriteOverrideProp,
GetPropValue(FObject,FReadWritePropName,False)<>uiFloat);
end;
procedure TOverridePropFiler.ReadOverrideProp(Reader: TReader);
begin
SetPropValue(FObject,FReadWritePropName,Reader.ReadFloat);
end;
procedure TOverridePropFiler.WriteOverrideProp(Writer: TWriter);
begin
Writer.WriteDouble(GetPropValue(FObject,FReadWritePropName,False));
end;
I call it in my DefineProperties method as follows:
procedure TMyObj.DefineProperties(Filer: TFiler);
begin
inherited;
TOverridePropFiler.Create(Filer,Self,'dTOverride').Free;
end;
To make this generalized, I had to use RTTI and save the property name locally (It's not possible to get this out of TWriter, and requires a hack for TReader). To make it thread safe (and because TFiler.DefineProperty wants "of object" functions) I encapsulated the whole thing in an object.
In my case I want the default value to be uiFloat. This constant can be set to whatever you want. If you want different defaults for different properties, you could easily add the default as a parameter to the Create function.
Note that you still have to set the property to the default in TMyObj's constructor.
This seems to me to be a fairly efficient way to work around a pretty serious limitation in Delphi.
Edit: You need to remember to add "stored false" to all of the properties that use this.
Just came across this problem and solved it by setting the value I want as default in the constructor and using the nodefault directive for that property.
As the compiler says, floating-point values cannot have a default value. The only thing I can think of is to store the floating-point value in a scaled integer. For instance, if you only need three decimals of yourFloat
, you can store yourInt = 1000 * yourFloat
instead. The default value would then be yourInt := 1000000
, corresponding to yourFloat := 1000.000
.
Of course, you can do this a bit more elegantly, like
private
FMyFloat: real;
function GetValue: integer;
procedure SetValue(Value: integer);
published
property MyInteger: integer read GetValue write SetValue default 1000000;
implementation
function TMyClass.GetValue: integer;
begin
result := round(1000 * FMyFloat);
end;
procedure TMyClass.SetValue(Value: integer);
begin
FMyFloat := Value / 1000;
end;
This way you will still only see the floating-point field FMyFloat
from inside the class itself. But the property will be a scaled integer, and thus perfectly storable.
The Default value actually only tells the stream code what the default value is, and if it is that value, then it is not streamed. This saves having to stream all the default values. The component must actually set the default in the .Create anyway. The compiler does not do any magic to set the default for you.
In this case, it will not matter, as you can set the default value in your .Create constructor, and then just leave out the default in the definition. The value will always be stored, but it will work as you want.
精彩评论