GetPropList с TRectangle возвращает StrokeThickness как свойство, которое должно быть частью класса Stroke.

Я использую Delphi Seattle Update1 Win64 и пытаюсь извлечь свойства с помощью RTTI. Моя цель — сериализовать свойства компонента в JSON, потому что мне нужно использовать эту информацию в среде, отличной от Delphi.

Мой вопрос касается GetPropList для TRectangle (пример) и почему он возвращает свойства, которые затем нельзя передать GetPropValue, а именно:

  1. StrokeThickness как тип tkFloat
  2. StrokeCap как тип tkEnumeration
  3. StrokeDash как тип tkEnumeration
  4. StrokeJoin как тип tkEnumeration.

GetPropList действительно правильно возвращает Stroke как тип tkClass, чего я и ожидал, и при анализе класс Stroke возвращает Thickness, Cap, Dash и Join, и я могу получить из них правильные значения.

Проблема в том, что выполнение GetPropValue на StrokeThickness вызывает исключение. Поэтому у меня есть особый случай "сломанных" свойств, возвращаемых GetPropList, которых я хотел бы избежать.

Сначала я подумал, что это проблема с GetPropList, возвращающим несуществующее свойство, но я могу выполнить следующее в коде, и они оба работают:

   Rectangle1.StrokeThickness := 5;   //works

   Rectangle1.Stroke.Thickness := 10; //and also works

Другие свойства типа tkFloat или tkEnumeration работают должным образом и возвращают правильные значения.

Я создал небольшое тестовое приложение, чтобы попытаться отладить это. Я обнаружил, что в случае StrokeThickness M.Code равен нулю в функции System.TypeInfo.TPropSet.GetProp (строка 2397), что, я думаю, объясняет, почему это вызывает исключение.

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

Форма:

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 202
  ClientWidth = 542
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  DesignerMasterStyle = 0
  object Rectangle1: TRectangle
    Position.X = 40.000000000000000000
    Position.Y = 40.000000000000000000
    Size.Width = 97.000000000000000000
    Size.Height = 97.000000000000000000
    Size.PlatformDefault = False
  end
  object StrokeThickness: TButton
    Position.X = 40.000000000000000000
    Position.Y = 144.000000000000000000
    Size.Width = 97.000000000000000000
    Size.Height = 22.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 1
    Text = 'RTTI'
    OnClick = StrokeThicknessClick
  end
  object Memo1: TMemo
    Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
    DataDetectorTypes = []
    Position.X = 152.000000000000000000
    Position.Y = 40.000000000000000000
    Size.Width = 353.000000000000000000
    Size.Height = 129.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 2
    Viewport.Width = 349.000000000000000000
    Viewport.Height = 125.000000000000000000
  end
end

Код теста:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
  FMX.Controls.Presentation, FMX.Edit, FMX.Objects, FMX.ScrollBox, FMX.Memo;

type
  TForm1 = class(TForm)
    Rectangle1: TRectangle;
    StrokeThickness: TButton;
    Memo1: TMemo;
    procedure StrokeThicknessClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses System.TypInfo;

{$R *.fmx}

procedure TForm1.StrokeThicknessClick(Sender: TObject);
var
  vValue : String;
  PropList : PPropList;
  PropInfo : PPropInfo;
  PropType : PPTypeInfo;
  PropListCount : Integer;
  I: Integer;
begin
   memo1.Lines.Clear;

   PropListCount := GetPropList(Rectangle1, PropList);

   for I := 0 to PropListCount-1 do
   begin
     PropInfo := PropList^[I];
     PropType := PropInfo^.PropType;

     Memo1.Lines.Add('Name: '+String(PropInfo^.Name) );
     Memo1.Lines.Add('PropType: '+String(PropInfo^.PropType^.Name) );
     Memo1.Lines.Add('PropKind: '+GetEnumName(TypeInfo(TTypeKind), Ord(PropType^.Kind)) );
     Memo1.Lines.Add('');
   end;

   vValue := GetPropValue(Rectangle1, 'Name');               //test string
   Memo1.Lines.Add('Proprty Name = '+VarToStr(vValue) );

   vValue := GetPropValue(Rectangle1, 'Height');             //test float
   Memo1.Lines.Add('Property Height = '+VarToStr(vValue) );

   vValue := GetPropValue(Rectangle1, 'Sides');             //test enumeration
   Memo1.Lines.Add('Property Sides = '+VarToStr(vValue) );

   //The following would cause an exception
   {
   vValue := GetPropValue(Rectangle1, 'StrokeThickness');
   Memo1.Lines.Add('Property StrokeThickness ='+VarToStr(vValue));
   }

   Rectangle1.StrokeThickness := 5;   //works ??

   //Still fails after it was explicitly set
   {
   vValue := GetPropValue(Rectangle1, 'StrokeThickness');
   Memo1.Lines.Add('Property StrokeThickness ='+VarToStr(vValue));
   }

   Rectangle1.Stroke.Thickness := 10; //and also works... as expected

   //The following with cause an exception
   {
   vValue := GetPropValue(Rectangle1, 'StrokeDash');
   Memo1.Lines.Add('StrokeDash = '+VarToStr(vValue) );
   }

end;

end.

person Alain Thiffault    schedule 20.02.2016    source источник
comment
Отправьте отчет об ошибке на QualityPortal, а затем попробуйте переключиться на новый стиль RTTI из модуля System.Rtti вместо использования старого -стиль RTTI из модуля System.TypInfo, посмотрите, не произойдет ли сбой на том же свойстве.   -  person Remy Lebeau    schedule 21.02.2016
comment
@RemyLebeau Спасибо; Сделаю. Хорошо получить подтверждение того, что я не делал что-то неправильно, от кого-то с большим опытом, прежде чем отправлять сообщение об ошибке. И я буду разветвлять и тестировать с System.Rtti, но я читал, что это несколько увеличивает размер кода, поэтому вместо этого я выбрал System.TypInfo. Я сравню, чтобы увидеть, есть ли влияние.   -  person Alain Thiffault    schedule 21.02.2016
comment
Я не подтверждаю и не отрицаю, является ли это ошибкой, так как у меня еще не было возможности попробовать это самому. Но это действительно похоже на ошибку, и сообщить об этом не помешает, вы всегда можете закрыть ее позже.   -  person Remy Lebeau    schedule 22.02.2016


Ответы (1)


используйте код как

var
  LProperty: TRttiProperty;
  LType: TRttiType;
  LContext: TRttiContext;
  LArray: TArray<TRttiProperty>;
begin
  LContext := TRTTIContext.Create;
  LType := LContext.GetType(TRectangle);
  LArray := LType.GetDeclaredProperties;
  for LProperty in LArray do
  begin
    Memo1.Lines.Add('Name: ' + LProperty.Name);
    Memo1.Lines.Add('PropType: ' + LProperty.PropertyType.Name);
    Memo1.Lines.Add('PropKind: ' + GetEnumName(TypeInfo(TTypeKind), Ord(LProperty.PropertyType.TypeKind)));
    if LProperty.IsReadable then
    begin
      Memo1.Lines.Add('Value: ' + LProperty.GetValue(Rectangle1).ToString);
    end
    else
    begin
      Memo1.Lines.Add('Value of the property cannot be read');
    end;
    Memo1.Lines.Add('');
  end;
end;
person Vitkus    schedule 02.03.2016
comment
Не могли бы вы добавить пояснение к вашему ответу? - person Sergii Zhevzhyk; 02.03.2016
comment
Извините, я был слишком коротким. Итак, причиной проблемы автора стало то, что свойства StrokeThickness, StrokeCap, StrokeDash и StrokeJoin доступны только для записи. Поэтому исключение происходит при попытке прочитать их значения. Итак, решение состоит в том, чтобы проверить, можно ли прочитать свойство или нет. На данный момент я не знаю, как это сделать с помощью PPTypeInfo - не вижу там подходящей информации. В аналогичном случае в моем собственном проекте я использую модуль System.Rtti и TRttiProperty, который обеспечивает необходимую проверку LProperty.IsReadable - person Vitkus; 03.03.2016