Как найти следующий элемент ListView, который не выбран? - PullRequest
0 голосов
/ 01 сентября 2018

Я хочу найти в ListView следующий невыбранный элемент, но только с помощью API Windows.

Я пытался с макросом 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;

1 Ответ

0 голосов
/ 01 сентября 2018

Вы звоните ListView_FindItem(), используя флаг LVFI_PARAM:

LVFI_PARAM

Поиск соответствия между элементом lParam этой структуры и элементом lParam структуры LVITEM элемента.

Это говорит ListView сравнивать указанное TLVFindInfo.lParam значение как есть с lParam каждого элемента списка, пока не найдет совпадение.

Если вы используете TListView в не виртуальном режиме (OwnerData=False), значение lParam элемента списка содержит соответствующий TListItem указатель объекта.

Если вы используете TListView в режиме virtual (OwnerData=True), значение lParam элемента списка всегда равно 0.

ListView_FindItem() (и базовое сообщение LVM_FINDITEM) могут искать элемент списка либо по Caption (полностью или частично), либо по lParam 1 , либо по его позиции, но больше ничего .

1: Например, метод TListItems.IndexOf() использует ListView_FindItem() для возврата индекса указанного объекта 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 НИКОГДА НЕ ИСПОЛЬЗУЕТСЯ, поэтому не имеет значения, для каких полей 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 вместо:

Выполняет поиск элемента списка, который имеет указанные свойства и имеет указанное отношение к указанному элементу.

Но их можно использовать только для поиска элемента списка, для которого включены указанные характеристики (например, включен LVNI_SELECTED). Их нельзя использовать для поиска предмета, у которого ОТСУТСТВИЕ указанных характеристик (например, отключено 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;

Оба подхода работают на то, что вы пытаетесь.

...