how to (correctly) use an enumerated type with livebindings (TObjectBindSourceAdapter)

此生再无相见时 提交于 2021-02-09 11:01:51

问题


I'm using TObjectBindSourceAdapter to use livebindings with an object. One of the properties of the object i'm using with TObjectBindSourceAdapter has an enumerated type, but the field in the adapter is never generated when i use an enumerated type in my object

The Only solution i have found for now is to define the enumerated type as an integer in my object and typecast it. This seems to work fine but you have to keep type casting from and back the enumerated type and integers.

Here is some example code to explain what i mean.

First example which uses the enumerated type that i tried initially and does not seem to work:

 uses Data.Bind.ObjectScope;

 Type
   TMyEnumtype = (meOne, meTwo, meThree);

   TMyObject = class
     public
       MyEnumType: TMyEnumtype;
  end;

procedure TForm9.But1Click(Sender: TObject);
var
  MyObject: TMyObject;
  aBindSourceAdapter: TBindSourceAdapter;
begin
  MyObject := TMyObject.Create;
  MyObject.MyEnumType := meTwo;
  aBindSourceAdapter := TObjectBindSourceAdapter<TMyObject>.Create(nil, MyObject, False);
  if aBindSourceAdapter.FindField('MyEnumType') <> nil then
    ShowMessage('MyEnumType found')
  else
    showmessage('MyEnumType not found');
  FreeAndNil(MyObject);
  FreeAndNil(aBindSourceAdapter);
end;

Second example that seems to work by typecasting to integers

uses Data.Bind.ObjectScope;

Type
  TMyEnumtype = (meOne, meTwo, meThree);

  TMyObject = class
    public
      MyEnumType: integer;
  end;

procedure TForm9.But1Click(Sender: TObject);
var
  MyObject: TMyObject;
  aBindSourceAdapter: TBindSourceAdapter;
  aEnumType : TMyEnumtype;
begin
  MyObject := TMyObject.Create;
  MyObject.MyEnumType := Integer(meTwo);
  aBindSourceAdapter := TObjectBindSourceAdapter<TMyObject>.Create(nil, MyObject, False);
  if aBindSourceAdapter.FindField('MyEnumType') <> nil then
    ShowMessage('MyEnumType found')
  else
    showmessage('MyEnumType not found');

  aEnumType := TMyEnumtype(aBindSourceAdapter.FindField('MyEnumType').GetTValue.AsInteger);

  if aEnumType =  meTwo then
    showmessage('meTwo');

  FreeAndNil(MyObject);
  FreeAndNil(aBindSourceAdapter);
end;

I was wondering if someone else had come across this problem and if there is perhaps some other solution to solve this without reverting to integers and keep using the enumerated types. I'm also not sure if my workaround is the common way to do this or not.


回答1:


I believe the best way is to register a converter. It turns out to be very easy, but only after digging through the VCL source code. I didn't find any useful documentation. But here it is.

unit MyConverters;

interface

uses System.Rtti, System.Bindings.Outputs;

type
  TMyEnum = (Value1, Value2, Value3);

implementation

procedure RegisterConverters;
begin
  TValueRefConverterFactory.RegisterConversion(TypeInfo(TMyEnum), TypeInfo(string),
    TConverterDescription.Create(
      procedure(const InValue: TValue; var OutValue: TValue)
      var
        MyEnum: TMyEnum;
        S: string;
      begin
        MyEnum := InValue.AsType<TMyEnum>;
        case MyEnum of
          Value1:  S := 'First Value';
          Value2:  S := 'Second Value';
          Value3:  S := 'Third Value';
          else     S := 'Other';
        end;
        OutValue := TValue.From<string>(S);
      end,
      'TMyEnumToString',
      'TMyEnumToString',
      '', // TODO what is the AUnitName param used for?
      True, // TODO what is ADefaultEnabled used for?  What does it mean?
      'Converts a TMyEnum value to a string',
      nil)
  );
end;

initialization
  RegisterConverters;
end.

In a nutshell, you call TValueRefConverterFactor.RegisterConversion() and pass in:

  • The type that this converter converts FROM
  • The type that this converter converts TO
  • A TConverterDescription that contains an anonymous procedure to actually perform the conversion along with some other metadata.

In the above code, the initialization section calls RegisterConverters, so all that is necessary is to include the unit in your project and the live bindings framework will use the converter whenever it needs to convert a TMyEnum value to a string.




回答2:


Casting enums as integers and back is really the appropriate way to accommodate for this. Let's say for example...

type
  TMyEnum = (meOne, meTwo, meThree);

As you already demonstrate, these can be casted as integers. When casting as an integer, it uses the index in which each one is listed in the definition. So...

0 = meOne
1 = meTwo
2 = meThree

You would cast TMyEnum as Integer like...

Something := Integer(MyEnumValue);

and then cast it back like...

Something := TMyEnum(MyIntegerValue);

This is widely used to solve your exact issue, and I use it all the time myself. I investigated the same scenario long ago and came to the conclusion that it's really the only way to do this - unless you want to do some more sophisticated conversion such as using strings...

function MyEnumToStr(const MyEnum: TMyEnum): String;
begin
  case MyEnum of
    meOne: Result:= 'meOne';
    meTwo: Result:= 'meTwo';
    meThree: Result:= 'meThree';
  end;
end;

function StrToMyEnum(const Str: String): TMyEnum;
var
  S: String;
begin
  S:= UpperCase(Str);
  if S = 'MEONE' then Result:= meOne
  else if S = 'METWO' then Result:= meTwo
  else if S = 'METHREE' then Result:= meThree;
end;

(I'm sure there are other ways of using if statements for StrToMyEnum)

Using strings in this manner can make things more readable. A more real-world example...

type
  TCustomerType = (cmRetail, cmWholesale, cmDesigner);

where...

cmRetail = 'Retail Customer'
cmWholesale = 'Wholesale Customer'
cmDesigner = 'Designer Customer'


来源:https://stackoverflow.com/questions/16943387/how-to-correctly-use-an-enumerated-type-with-livebindings-tobjectbindsourcead

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!