Search code examples
listviewdelphivcldelphi-10.2-tokyo

How to prevent listview from jumping to selected/focused row on item.count change?


I have a virtual listview, which I'm planning on using to display things from a rather large log file.

Any time a line is added or removed and I have a row selected or focused (or both) in the listbox, it will automatically scroll back to it, which is very annoying.

Feels like something makes a call for the MakeVisible (or something that does the same thing) when the item count is modified.

Very simplified example to reproduce it:

procedure TForm1.FormCreate(Sender: TObject);
var
  Col: TListColumn;
begin
  ListView1.OwnerData := True;
  ListView1.ViewStyle := vsReport;
  ListView1.RowSelect := True;

  Col := ListView1.Columns.Add;
  Col.Caption := 'LineNum';
  Col.Alignment := taLeftJustify;
  Col.Width := 70;
end;
// listview onData event
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
  Item.Caption := IntToStr(Item.Index+1);
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  ListView1.Items.Count := ListView1.Items.Count + 10;
end;

Edit: Testing different ViewStyles, this only happens with vsReport and vsList


Solution

  • The problem is that the TListItems.Count property setter calls ListView_SetItemCountEx() without the LVSICF_NOSCROLL flag:

    The list-view control will not change the scroll position when the item count changes.

    procedure TListItems.SetCount(Value: Integer);
    begin
      if Value <> 0 then
        ListView_SetItemCountEx(Handle, Value, LVSICF_NOINVALIDATEALL)
      else
        ListView_SetItemCountEx(Handle, Value, 0);
    end;
    

    That is why the ListView scrolls whenever the Count changes. You will have to call ListView_SetItemCountEx() yourself directly so that you can specify the LVSICF_NOSCROLL flag.

    uses
      ..., CommCtrl;
    
    procedure TForm1.Timer1Timer(Sender: TObject);
    begin
      //ListView1.Items.Count := ListView1.Items.Count + 10;
      ListView_SetItemCountEx(ListView1.Handle, ListView1.Items.Count + 10, LVSICF_NOINVALIDATEALL or LVSICF_NOSCROLL);
    end;