如何找到下一个未选中的 ListView 项?

How to find the next ListView item that is not selected?

我想在 ListView 中搜索下一个未选中的项目,但只能使用 windows API。

我尝试使用 ListView_FindItem 宏,但它不起作用。结果总是 -1:

function TNewListView.NextUnselected(I: Integer): Integer;
var FindInfo: TLVFindInfo;
    ItemInfo: TLVItem;
begin
 if not HandleAllocated then Exit(-1)
 else begin
   FillChar(ItemInfo, SizeOf(ItemInfo), 0);
   ItemInfo.mask:= LVIF_STATE;
   ItemInfo.state:= 0;
   ItemInfo.stateMask:= LVIS_SELECTED;

   FillChar(FindInfo, SizeOf(FindInfo), 0);
   FindInfo.flags:= LVFI_PARAM;
   FindInfo.lParam:= LPARAM(@ItemInfo);
   Result:= ListView_FindItem(Handle, I, FindInfo);
 end;

您正在使用 LVFI_PARAM 标志调用 ListView_FindItem()

LVFI_PARAM

Searches for a match between this structure's lParam member and the lParam member of an item's LVITEM structure.

告诉 ListView 将指定的 TLVFindInfo.lParam 按原样 与每个列表项的 lParam 进行比较,直到找到匹配项。

如果您在 非虚拟 模式 (OwnerData=False) 中使用 TListView,列表项的 lParam 值将保留其对应的值TListItem 对象指针。

如果您在 虚拟 模式 (OwnerData=True) 中使用 TListView,则列表项的 lParam 值始终为 0。

ListView_FindItem()(以及底层的 LVM_FINDITEM 消息)可以通过 Caption(全部或部分)、lParam [=104] 来搜索列表项=]1,或它的位置,但 没有别的

1:例如TListItems.IndexOf()方法使用ListView_FindItem()到return指定TListItem对象的索引使用lParam 搜索(仅在非虚拟模式下有效,其中每个项目的 lParam 是一个 TListItem 对象指针)。

您也在尝试执行 lParam 搜索,但您使用的 错误 lParam 值进行搜索!您将 TLVFindInfo.lParam 值设置为 指向本地 TLVItem 变量的指针 ,因此 LVFI_PARAM 比较将 永远不会 找到匹配的列表项。这就是为什么您总是得到 -1 的结果。

ListView_FindItem() 本质上 在您的示例中执行以下逻辑:

function ListView_FindItem(hWnd: HWND; iStart: Integer; const plvfi: TLVFindInfo): Integer;
var
  lvi: TLVItem;
begin
  for Result := iStart+1 to ListView_GetItemCount(hWnd)-1 do
  begin
    FillChar(lvi, SizeOf(lvi), 0);
    lvi.iIndex := Result;
    lvi.mask = LVIF_PARAM;
    ListView_GetItem(hWnd, lvi);
    if lvi.lParam = plvfi.lParam then // <-- NEVER FINDS A MATCH!
      Exit;
  end;
  Result := -1;
end;

如您所见,您的本地 TLVItem 变量的 contents 根本未被使用,因此您设置 TLVItem 字段到。

期望 ListView_FindItem() 本质上 改为执行以下逻辑,这不是它的工作原理,而是没有记录以这种方式工作:

function ListView_FindItem(hWnd: HWND; iStart: Integer; const plvfi: TLVFindInfo): Integer;
var
  lvi: TLVItem;
begin
  for Result := iStart+1 to ListView_GetItemCount(hWnd)-1 do
  begin
    FillChar(lvi, SizeOf(lvi), 0);
    lvi.iIndex := Result;
    lvi.mask = LVIF_STATE;
    lvi.stateMask := PLVItem(plvfi.lParam)^.stateMask;
    ListView_GetItem(hWnd, lvi);
    if lvi.state = PLVItem(plvfi.lParam)^.state then // <-- BUZZ, WRONG!
      Exit;
  end;
  Result := -1;
end;

因此,您根本无法使用 ListView_FindItem()/LVM_FINDITEM 按州搜索项目,他们不支持这种搜索。

您可能想改用 ListView_GetNextItem()/LVM_GETNEXTITEM

Searches for a list-view item that has the specified properties and bears the specified relationship to a specified item.

但是,它们只能用于搜索启用了指定特征的列表项(例如启用了 LVNI_SELECTED)。它们不能用于查找具有 ABSENCE 指定特征(例如禁用 LVNI_SELECTED)的项目。

因此,要执行您想要的操作,您只需手动遍历列表项,使用 ListView_GetItem()ListView_GetItemState() 检索每个项的当前状态,直到找到您想要的寻找。

例如:

function TNewListView.NextUnselected(StartIndex: Integer): Integer;
begin
  if HandleAllocated then
  begin
    for Result := StartIndex+1 to ListView_GetItemCount(Handle)-1 do
    begin
      if (ListView_GetItemState(Handle, Result, LVIS_SELECTED) and LVIS_SELECTED) = 0 then
        Exit;
    end;

    // if you want to implement wrap-around searching, uncomment this...
    {
    for Result := 0 to StartIndex-1 do
    begin
      if (ListView_GetItemState(Handle, Result, LVIS_SELECTED) and LVIS_SELECTED) = 0 then
        Exit;
    end;
    }
  end;
  Result := -1;
end;

或:

function TNewListView.NextUnselected(StartIndex: Integer): Integer;

  function IsNotSelected(Index: Integer): Boolean;
  var
    ItemInfo: TLVItem;
  begin
    FillChar(ItemInfo, SizeOf(ItemInfo), 0);
    ItemInfo.iItem := Index;
    ItemInfo.mask := LVIF_STATE;
    ItemInfo.stateMask := LVIS_SELECTED;
    ListView_GetItem(Handle, ItemInfo);
    Result := (ItemInfo.state and LVIS_SELECTED) = 0;
  end;

begin
  if HandleAllocated then
  begin
    for Result := StartIndex+1 to ListView_GetItemCount(Handle)-1 do
    begin
      if IsNotSelected(Result) then
        Exit;
    end;

    // if you want to implement wrap-around searching, uncomment this...
    {
    for Result := 0 to StartIndex-1 do
    begin
      if IsNotSelected(Result) then
        Exit;
    end;
    }
  end;
  Result := -1;
end;

这两种方法都适用于您的尝试。