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 的调试器非常好。
设置 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 的调试器非常好。