delphifiremonkeytlistview

Delphi FMX TListview - DynamicAppearance mode - Access Violation problem


Setup TListview, ItemAppearance = DynamicAppearance

in the OnClickItemEX, let's say there's 2 Textobjects. click on both TextObjects - OK. Click on the space between 2 of them - Access Violation.

if click anywhere not covered by an item will result in an Access Violation for the simplest setup.

If I use OnButtonClick only without using OnClickItemEX, no such AV.

How can I resolve this? see below for a Minimal Working Example. (I'm not sure if it's correct to attach it this way as I don't see an Attachment option to upload a zip file of this mini project).

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

Solution

  • If you set a breakpoint on the first line of your ListView1ItemClickEx method and then run the app and click between two items, you'll see that there is no ItemObject because you're not clicking on an item (you're clicking between them). This results in ItemObject being nil, and you then try to read the Text value from that unassigned object, causing the AV.

    You can correct this by checking to make sure that ItemObject is assigned a value before you use it.

    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;
    

    Learning to use the debugger to step through the code will enable you to figure out this sort of simple issue yourself. There's no better tool to have in your programmers toolbox than a good debugger, and Delphi's debugger is quite good.