Search code examples
delphienumslivebindings

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


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.


Solution

  • 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.