Boolean property seen as constant [duplicate] - delphi

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;

Related

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.

Convert Integer to Enum in property getter/setter

I currently have an option management which works with properties, that use all the same getter/setter method but with an index.
This works fine for Ints and Bools, but I'd like to get this working for enums, too.
TIntegerOptions = (kInt1, kInt2, kInt3);
class property Integer1: Integer index kInt1 read GetOptionsValueInt write SetOptionsValueInt;
class property Integer2: Integer index kInt2 read GetOptionsValueInt write SetOptionsValueInt;
class property Integer3: Integer index kInt3 read GetOptionsValueInt write SetOptionsWertInt;
FOptionsListInt: array[TIntegerOptions] of IniIntObject; // Objects contain Inifile read&save functionalities
...
class function GetOptionsValueInt(const aOption: TIntegerOptions) : Integer
begin
Result := Ord(FOptionsListInt[aOption].GetValue());
end;
class procedure SetOptionsValueInt(const aOption: TIntegerOptions; const aValue: Integer);
begin
FOptionslistInt[aOption].ChangeTo(aValue);
end;
This is working so far, and now my problem:
TEnumOptions = (kEnum1, kEnum2, kEnum3);
TEnum1 = (e1o1, e1o2, e1o3);
TEnum2 = (e2o1, e2o2, e2o3);
TEnum3 = (e3o1, e3o2, e3o3);
// these props fail because my functions return / excpect Integers, not the matching Enums
class property Enum1: TEnum1 index kEnum1 read GetOptionsValueInt write SetOptionsValueEnum;
class property Enum2: TEnum2 index kEnum2 read GetOptionsValueInt write SetOptionsValueEnum;
class property Enum3: TEnum3 index kEnum3 read GetOptionsValueInt write SetOptionsValueEnum;
FOptionsListEnum: array[TEnumOptions] of IniIntObject; // Objects contain Inifile read&save functionalities
I marked the problems in the code. Can I somehow cast the Integer values returned by the getters/setters to the matching enum for each property?
For those who read the old question:
I decided to just use the same getter/setter for the enums, cause in the end they get saved as Integers, too. If I need to cast inside the getters somehow, I'll add them again, but i hoped for a solution inside the property-declaration.
Regarding your update, you want to use code like this:
class property Enum1: TEnum1 index kEnum1 read GetOptionsValueInt;
That fails to compile because GetOptionsValueInt returns a value of type Integer. Since the property has type TEnum1, that is a simple type mismatch. In order to have a property of type TEnum1, the getter must be a function that returns a value of type TEnum1.
If each of these properties has a different type, then you cannot share getter and setter. Shared indexed getters and setters can only be used for properties that share a common type. Since it looks like none of these properties share a type, you will not be able to share getters and setters.
Original answer to original question
You don't show all the declarations, so it's hard for us to know why your code does not compile. We don't know what FOptionsListeEnum is, and we don't know what Wert() is.
However, here's a complete program that demonstrates that enumerated types can be used as indexes for indexed properties:
{$APPTYPE CONSOLE}
type
TMyEnum = (evOne, evTwo);
TMyClass = class
private
class function GetValue(const Option: TMyEnum): Integer; static;
public
class property ValueOne: Integer index evOne read GetValue;
class property ValueTwo: Integer index evTwo read GetValue;
end;
class function TMyClass.GetValue(const Option: TMyEnum): Integer;
begin
Result := ord(Option);
end;
begin
Writeln(TMyClass.ValueOne);
Writeln(TMyClass.ValueTwo);
end.
So, that makes it clear that there's no issue using enumerated types as indices. In which case, what is your problem. Let's look at the code you have:
class function TKmpOption.GetOptionsWertEnum(const aOption: TEnumOptionen): Integer;
begin
Result := Ord(FOptionsListeEnum[aOption].Wert());
end;
class procedure TKmpOption.SetOptionsWertEnum(const aOption: TEnumOptionen; const aWert: Integer);
begin
FOptionslisteEnum[aOption].Aendern(aOption(aWert));
end;
If Ord(FOptionsListeEnum[aOption].Wert()) does not compile with a type mismatch error, then it would seem that Wert() is not an ordinal value.
As for aOption(aWert) that makes no sense at all. That can never compile. Perhaps you meant TEnumOptionen(aWert) but that also makes no sense. Why would the value be of the same type used to index the possible options.
I hope that the code above shows that the issue is not related to enumerated types as indexers and is in fact of a rather more prosaic nature.

Use Rtti to set method field

I'm using Delphi XE to write a base class, which will allow descending classes to have dll methods mapped by applying an annotation. However I get a typecasting error, which is understandable.
In essence the base class should look like this:
TWrapperBase = class
public
FLibHandle: THandle;
procedure MapMethods;
end;
procedure TWrapperBase.MapMethods;
var
MyField: TRttiField;
MyAttribute: TCustomAttribute;
pMethod: pointer;
begin
FLibHandle := LoadLibrary(PWideChar(aMCLMCR_dll));
for MyField in TRttiContext.Create.GetType(ClassType).GetFields do
for MyAttribute in MyField.GetAttributes do
if MyAttribute.InheritsFrom(TMyMapperAttribute) then
begin
pMethod := GetProcAddress(FLibHandle, (MyAttribute as TMyMapperAttribute).TargetMethod);
if Assigned(pMethod) then
MyField.SetValue(Self, pMethod); // I get a Typecast error here
end;
And a descending class could look like this:
TDecendant = class(TWrapperBase)
private type
TSomeDLLMethod = procedure(aParam: TSomeType); cdecl;
private
[TMyMapperAttribute('MyDllMethodName')]
FSomeDLLMethod: TSomeDLLMethod;
public
property SomeDLLMethod: TSomeDLLMethod read FSomeDLLMethod;
end;
I could implement this differently, by hard coding the linking for each method in an overriden 'MapMethods'. This would however require each descendant to do so which I'd like to avoid.
I know that the TValue as used in this case will contain a pointer and not of the correct type (procedure(aParam: TSomeType); cdecl; in this case).
My question: Is there a way to pass the pointer from 'GetProcAdress' as the correct type, or to set the field directly (for example by using the field address 'PByte(Self)+MyField.Offset', which you can use to set the value of a record property)?
With the old Rtti, this could be done but only for published properties and without any type checking:
if IsPublishedProp(Self, 'SomeDLLMethod') then
SetMethodProp(Self, 'SomeDLLMethod', GetProcAddress(FLibHandle, 'MethodName');
There are two problems:
First your EInvalidCast is caused by TValue being very strict about type conversions. You are passing in a Pointer and want to set a field of type TSomeDLLMethod. You need to explicitly pass a TValue that has the correct type info.
if Assigned(pMethod) then
begin
TValue.Make(#pMethod, MyField.FieldType.Handle, value);
MyField.SetValue(Self, value);
end;
Now you will run into another EInvalidCast exception which is triggered because of a bug in XE inside the GetInlineSize method of the Rtti.pas which returns 0 for a tkProcedure kind of type. I don't know in what version this got fixed but it does not exist anymore in XE5.
For XE this can be fixed by using a unit I wrote some while ago (and which I just updated to fix this bug): RttiPatch.pas.
I also reported the original issue because Pointer is assignment compatible to a procedure type so TValue should also handle this: http://qc.embarcadero.com/wc/qcmain.aspx?d=124010
You could try something like:
Move(pMethod, PByte(Self) + Field.Offset, SizeOf(Pointer));
or
PPointer(PByte(Self) + Field.Offset)^ := pMethod;

How to pass StringGrid Row Number to another form?

I have a StrinGrid component and a procedure:
procedure TForm3.StringGrid1Click(Sender: TObject);
begin
SelectedElement := stringgrid1.Cells[0,stringgrid1.Row];
end
SelectedElement is declared in public section:
public
SelectedElement : String;
end;
When I use it in this unit, for example Label1.Caption := SelectedElement, it works fine. But in another unit, where I specified uses unit1 in implementation, and I try to use this variable like this Label1.Caption := Form1.SelectedElement it sets label to empty string. But when I set variable manually for example on first form create, then this value shows up in second form, even if variable is later changed to value from stringgrid.
Given the small amount of code you have shown so far, it is difficult to diagnose your problem for sure, but based on your comments so far, it sounds to me like you are probably dynamically creating your TForm3 object at run-time using TForm3.Create() and not assigning the object to the global Form3 pointer, but are trying to use the global Form3 pointer to access the SelectedElement value. Is that correct?
Also, you show TForm3.StringGrid1Click() is setting TForm3.SelectedElement, but you are accessing Form1.SelectedElement instead of Form3.SelectedElement. Does TForm1 have its own SelectedElement member? Or are you not showing your real code copy/pasted from your real project?
You should add a property to the form that returns the desired value:
....
private
function GetSelectedElement: string;
public
property SelectedElement: string read GetSelectedElement;
....
And implement it like this:
function TForm3.GetSelectedElement: string;
begin
Result := StringGrid1.Cells[0, StringGrid1.Row];
end;
This will always return the current state which I believe is what you want.

Delphi: problem setting length of a TByteDynArray

Question closed (answer accepted) replaced by Delphi 7: Access violation - TByteDynArray problem)
I have the following delcartion given to me
MyPhoto = class(TRemotable)
private
FhasPhoto: Boolean;
FphotoData: TByteDynArray;
published
property hasPhoto: Boolean read FhasPhoto write FhasPhoto;
property photoData: TByteDynArray read FphotoData write FphotoData;
end;
and I want to
var photo : MyPhoto;
photo := MyPhoto.Create();
SetLength(photo.photoData, 4);
but I get
[Error] mainForm.pas(340): Constant object cannot be passed as var parameter
1) How do I code it correctly, given that I can't change the definition of the class Photo?
2) can I effectively 'cast' any structure to a TByteDynArray just by assigning it?
(as you might have guessed, I'm a BCB guy trying to get into Ddlphi :-)
p.s I will settle for being able to assign each byte of the photo data individually...
This is a flaw in Delphi's implementation of properties. Even if the property can be read and written to, even if both the read and the write point to the same field in the underlying object, you can't pass a property to a var parameter, such as to SetLength. There's no good reason for this, but that's the way it works, for the time being at least.
There's a fairly simple workaround for this issue, though. You need a method on the object that takes an integer and will set the length of FPhotoData internally.
Here is a way :
MyPhoto = class(TRemotable)
...
public
procedure SetSize( aSize : Integer );
end;
procedure MyPhoto.SetSize( aSize : Integer );
begin
SetLength( FphotoData, aSize );
end;
var photo : MyPhoto;
photo := MyPhoto.Create();
MyPhoto.SetSize(4);

Resources