как (правильно) использовать перечисляемый тип с живыми привязками (TObjectBindSourceAdapter)

Я использую TObjectBindSourceAdapter для использования livebindings с объектом. Одно из свойств объекта, который я использую с TObjectBindSourceAdapter, имеет перечисляемый тип, но поле в адаптере никогда не создается, когда я использую перечисляемый тип в своем объекте.

Единственное решение, которое я нашел на данный момент, - это определить перечисляемый тип как целое число в моем объекте и привести его к типу. Кажется, это работает нормально, но вы должны поддерживать приведение типов к перечисляемому типу и целым числам.

Вот пример кода, чтобы объяснить, что я имею в виду.

Первый пример, в котором используется перечисляемый тип, который я пробовал изначально и, похоже, не работает:

 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;

Второй пример, который, кажется, работает путем приведения типов к целым числам

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;

Мне было интересно, сталкивался ли кто-то еще с этой проблемой, и есть ли, возможно, какое-то другое решение для ее решения, не возвращаясь к целым числам и продолжая использовать перечисленные типы. Я также не уверен, является ли мой обходной путь обычным способом сделать это или нет.


person Willems Davy    schedule 05.06.2013    source источник
comment
Я думаю, что у вас нет выбора - я давно пришел к выводу, что приведение к целым числам - лучшее решение для этого.   -  person Jerry Dodge    schedule 05.06.2013
comment
Сообщается об ошибке в RAD Studio Quality здесь -› quality.embarcadero.com/browse/RSP-34259. Я понимаю, что это могло быть помечено как улучшение.   -  person Didier Cabalé    schedule 06.07.2021


Ответы (2)


Я считаю, что лучший способ - зарегистрировать конвертер. Это оказывается очень просто, но только после того, как вы покопаетесь в исходном коде VCL. Я не нашел никакой полезной документации. Но вот оно.

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.

Короче говоря, вы вызываете TValueRefConverterFactor.RegisterConversion() и передаете:

  • Тип, который преобразовывает этот преобразователь ИЗ
  • Тип, который этот преобразователь преобразует В
  • TConverterDescription, который содержит анонимную процедуру для фактического выполнения преобразования вместе с некоторыми другими метаданными.

В приведенном выше коде секция initialization вызывает RegisterConverters, поэтому все, что необходимо, — это включить модуль в ваш проект, и платформа живых привязок будет использовать преобразователь всякий раз, когда ей нужно преобразовать значение TMyEnum в string.

person Pat    schedule 22.10.2013
comment
Я боюсь, что это решение не может работать, по крайней мере, с текущим Delphi - Sydney 10.4: беглый взгляд на Data.Bind.ObjectScope.pas, процедура класса TBindSourceAdapter.AddPropertiesToList(...) говорит: случай LProperty.PropertyType.TypeKind tkEnumeration : если SameText(LTypeName, 'boolean') или SameText(LTypeName, 'bool') then // 'bool' для C++ LCollectionEditorField := CreateRttiPropertyField‹Boolean›(LProperty, ABindSourceAdapter, AGetMemberObject, mtBoolean); У приведенного выше мало шансов добавить перечисляемое поле в TBindSourceAdapter. - person Didier Cabalé; 03.12.2020

Преобразование перечислений в целые числа и обратно - действительно подходящий способ приспособиться к этому. Скажем, например...

type
  TMyEnum = (meOne, meTwo, meThree);

Как вы уже продемонстрировали, их можно преобразовать в целые числа. При приведении к целому числу используется индекс, в котором каждый из них указан в определении. Так...

0 = meOne
1 = meTwo
2 = meThree

Вы бы использовали TMyEnum как Integer как...

Something := Integer(MyEnumValue);

а потом откинул обратно как...

Something := TMyEnum(MyIntegerValue);

Это широко используется для решения вашей точной проблемы, и я сам использую ее все время. Я давно исследовал тот же сценарий и пришел к выводу, что это действительно единственный способ сделать это - если только вы не хотите выполнить более сложное преобразование, такое как использование строк...

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;

(Я уверен, что есть и другие способы использования операторов if для StrToMyEnum)

Использование строк таким образом может сделать текст более читабельным. Более реальный пример...

type
  TCustomerType = (cmRetail, cmWholesale, cmDesigner);

куда...

cmRetail = 'Retail Customer'
cmWholesale = 'Wholesale Customer'
cmDesigner = 'Designer Customer'
person Jerry Dodge    schedule 06.06.2013