Delphi FMX ListView筛选后调用FDQuery.First出现空白及访问违规求助
兄弟,我之前做Delphi项目时也踩过类似的坑,你的问题核心大概率是搜索操作破坏了ListView项和FDQuery记录的关联关系,加上AccessoryObject引用了已失效的内存对象,导致调用Query.First时触发访问违规,同时ListView同步异常变空白。下面给你分步骤拆解解决办法:
1. 先排查搜索逻辑的数据集同步问题
你在搜索时是不是直接修改了FDQuery的SQL语句然后重新Open?这种操作会销毁原来的数据集记录对象,导致ListView项的AccessoryObject指向的旧记录已经被释放,后续遍历或调用First时就会触发内存访问违规。
优化方案:用Filter替代直接修改SQL
如果你的搜索是针对现有数据集的过滤,建议用FDQuery的内置Filter功能,而不是重新执行SQL:
// 搜索时的代码 FDQuery.DisableControls; // 先禁用控件绑定,避免ListView实时刷新出问题 try FDQuery.Filter := '字段名 LIKE ' + QuotedStr('%' + Edit_Search.Text + '%'); FDQuery.Filtered := True; finally FDQuery.EnableControls; // 恢复控件绑定 end;
这样数据集不会重新创建记录对象,原来的AccessoryObject引用的记录(如果是指向FDQuery的当前记录对象)还能保持有效,同时ListView也不会突然空白。
2. 重构AccessoryObject的存储方式
别直接把FDQuery的记录对象或字段指针存到AccessoryObject里!一旦数据集重新加载(比如搜索时重新Open),这些对象会被释放,后续访问就是非法内存操作。
正确做法:存储唯一标识(比如主键ID)
把记录的主键ID(整数或字符串)转成TObject存储,遍历的时候通过ID定位记录:
// 初始化ListView项时(比如FDQuery遍历填充ListView) while not FDQuery.Eof do begin var LItem := ListView1.Items.Add; LItem.Text := FDQuery.FieldByName('Name').AsString; // 存储主键ID,转成TObject类型 LItem.AccessoryObject := TObject(FDQuery.FieldByName('ID').AsInteger); FDQuery.Next; end; // 流程结束后遍历检查标记记录 FDQuery.DisableControls; try FDQuery.First; for var LItem in ListView1.Items do begin if LItem.Checked then // 假设用Checked标记,你用AccessoryObject的话调整判断逻辑 begin var TargetID := Integer(LItem.AccessoryObject); // 通过ID定位到对应的记录 if FDQuery.Locate('ID', TargetID, [loCaseInsensitive]) then begin // 获取你需要的信息 ShowMessage('选中记录ID:' + IntToStr(TargetID) + ',名称:' + FDQuery.FieldByName('Name').AsString); end; end; end; finally FDQuery.EnableControls; end;
这种方式完全不依赖数据集的内存对象,哪怕搜索后数据集重新加载,只要主键ID不变,就能准确找到对应记录。
3. 搜索后重新同步ListView和FDQuery
如果你的搜索必须重新执行SQL(比如涉及联表查询,Filter满足不了),那搜索后一定要重新遍历FDQuery填充ListView,并重新设置每个项的AccessoryObject,同时操作时要包裹DisableControls和EnableControls:
// 搜索执行SQL的代码 FDQuery.DisableControls; try FDQuery.SQL.Text := 'SELECT * FROM 表名 WHERE 字段名 LIKE ' + QuotedStr('%' + Edit_Search.Text + '%'); FDQuery.Open; // 清空ListView并重新填充 ListView1.Items.Clear; while not FDQuery.Eof do begin var LItem := ListView1.Items.Add; LItem.Text := FDQuery.FieldByName('Name').AsString; LItem.AccessoryObject := TObject(FDQuery.FieldByName('ID').AsInteger); FDQuery.Next; end; finally FDQuery.EnableControls; end;
这样搜索后ListView和FDQuery的记录是完全对应的,后续调用FDQuery.First也不会出现空白或访问违规。
4. 调试访问违规的小技巧
如果还是出问题,打开Delphi的调试器,触发访问违规时查看调用栈,看是哪一行代码出错——大概率是访问AccessoryObject时指向了无效内存,这时候就回到上面的第2点,把存储对象改成存储主键ID即可解决。
内容的提问来源于stack exchange,提问作者Tallys Ferrante

