Delphi FMX TListview - DynamicAppearance 模式 - 访问冲突问题

Delphi FMX TListview - DynamicAppearance mode - Access Violation problem

设置 TListview,ItemAppearance = DynamicAppearance

在 OnClickItemEX 中,假设有 2 个文本对象。单击两个 TextObjects - 确定。单击其中 2 个之间的 space - 访问冲突。

如果单击项目未涵盖的任何位置,最简单的设置将导致访问冲突。

如果我只使用 OnButtonClick 而没有使用 OnClickItemEX,则没有这样的 AV。

我该如何解决这个问题?请参阅下面的最小工作示例。 (我不确定以这种方式附加它是否正确,因为我没有看到用于上传此迷你项目的 zip 文件的附件选项)。

unit bug_main;

interface


type
  TForm1 = class(TForm)
    ListView1: TListView;
    Button1: TButton;
    FDMemTable1: TFDMemTable;
    BindSourceDB1: TBindSourceDB;
    FDMemTable1CustomerID: TIntegerField;
    FDMemTable1CustomerName: TStringField;
    BindSourceDB2: TBindSourceDB;
    BindingsList1: TBindingsList;
    procedure Button1Click(Sender: TObject);
    procedure ListView1ItemClickEx(const Sender: TObject; ItemIndex: Integer;
      const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
  private
    FLinkFillControlToField : TLinkFillControlToField;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.Button1Click(Sender: TObject);
begin
  with FDMemTable1 do
  begin
    Open;

    Append;
    FieldByName('CustomerID').AsInteger := 1;
    FieldByName('CustomerName').AsString := 'ABC';
    Post;

    Append;
    FieldByName('CustomerID').AsInteger := 2;
    FieldByName('CustomerName').AsString := 'XYZ';
    Post;
  end;

  if not Assigned(FLinkFillControlToField) then
  begin
    FLinkFillControlToField := TLinkFillControlToField.Create(BindingsList1);
    FLinkFillControlToField.Control := listview1;

    with FLinkFillControlToField do
    begin
      Category := 'Quick Bindings';
      Track := False;
      Direction := linkDataToControl;
      AutoActivate := False;
      AutoFill := True;
      BindSourceDB1.DataSource.Enabled := True;
      FillDataSource := BindSourceDB1;
    end;
  end;

  with FLinkFillControlToField do
  begin
    with FillExpressions.AddExpression do
    begin
      SourceMemberName := 'CustomerID';
      ControlMemberName := 'Text1';
    end;
    with FillExpressions.AddExpression do
    begin
      SourceMemberName := 'CustomerName';
      ControlMemberName := 'Text2';
    end;
  end;
  FLinkFillControlToField.Active := True;
end;

procedure TForm1.ListView1ItemClickEx(const Sender: TObject; ItemIndex: Integer;
  const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
begin
  if itemobject.Name = 'Text1' then
  begin
    showmessage('clicked on Text1');
  end else if itemobject.Name = 'Text2' then
  begin
    showmessage('clicked on Text2');
  end;
end;

end.


object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 404
  ClientWidth = 763
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  DesignerMasterStyle = 0
  object ListView1: TListView
    ItemAppearanceClassName = 'TDynamicAppearance'
    ItemEditAppearanceClassName = 'TDynamicAppearance'
    HeaderAppearanceClassName = 'TListHeaderObjects'
    FooterAppearanceClassName = 'TListHeaderObjects'
    Position.X = 16.000000000000000000
    Position.Y = 24.000000000000000000
    Size.Width = 561.000000000000000000
    Size.Height = 353.000000000000000000
    Size.PlatformDefault = False
    ItemAppearanceObjects.ItemObjects.ObjectsCollection = <
      item
        AppearanceObjectName = 'Text1'
        AppearanceClassName = 'TTextObjectAppearance'
        Appearance.Width = 223.000000000000000000
        Appearance.Height = 44.000000000000000000
      end
      item
        AppearanceObjectName = 'Text2'
        AppearanceClassName = 'TTextObjectAppearance'
        Appearance.Width = 208.000000000000000000
        Appearance.Height = 44.000000000000000000
        Appearance.PlaceOffset.X = 326.000000000000000000
      end>
    ItemAppearanceObjects.ItemEditObjects.ObjectsCollection = <
      item
        AppearanceObjectName = 'Text1'
        AppearanceClassName = 'TTextObjectAppearance'
      end>
    OnItemClickEx = ListView1ItemClickEx
  end
  object Button1: TButton
    Position.X = 592.000000000000000000
    Position.Y = 24.000000000000000000
    Size.Width = 161.000000000000000000
    Size.Height = 57.000000000000000000
    Size.PlatformDefault = False
    Text = 'Button1'
    OnClick = Button1Click
  end
  object FDMemTable1: TFDMemTable
    FetchOptions.AssignedValues = [evMode]
    FetchOptions.Mode = fmAll
    ResourceOptions.AssignedValues = [rvSilentMode]
    ResourceOptions.SilentMode = True
    UpdateOptions.AssignedValues = [uvCheckRequired, uvAutoCommitUpdates]
    UpdateOptions.CheckRequired = False
    UpdateOptions.AutoCommitUpdates = True
    Left = 576
    Top = 128
    object FDMemTable1CustomerID: TIntegerField
      FieldName = 'CustomerID'
    end
    object FDMemTable1CustomerName: TStringField
      FieldName = 'CustomerName'
      Size = 30
    end
  end
  object BindSourceDB1: TBindSourceDB
    DataSet = FDMemTable1
    ScopeMappings = <>
    Left = 576
    Top = 192
  end
  object BindSourceDB2: TBindSourceDB
    DataSet = FDMemTable1
    ScopeMappings = <>
    Left = 576
    Top = 248
  end
  object BindingsList1: TBindingsList
    Methods = <>
    OutputConverters = <>
    Left = 20
    Top = 5
  end
end

如果您在 ListView1ItemClickEx 方法的第一行设置断点,然后 运行 应用程序并在两个项目之间单击,您会看到没有 ItemObject因为你没有点击一个项目(你在它们之间点击)。这导致 ItemObject 为零,然后您尝试从该未分配的对象中读取 Text 值,从而导致 AV.

您可以更正此问题,方法是检查以确保 ItemObject 在您使用它之前已分配了一个值。

procedure TForm1.ListView1ItemClickEx(const Sender: TObject; ItemIndex: Integer;
  const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
begin
  if ItemObject <> nil then
  begin
    if itemobject.Name = 'Text1' then
    begin
      ShowMessage('clicked on Text1');
    end else if itemobject.Name = 'Text2' then
    begin
      ShowMessage('clicked on Text2');
    end;
  end else
    ShowMessage('ItemObject is not assigned');
end;

学习使用调试器单步执行代码将使您能够自己解决这类简单的问题。在您的程序员工具箱中,没有比一个好的调试器更好的工具了,Delphi 的调试器非常好。