Delphi - Get and Set Scrollbar Position of a ListView
Asked Answered
V

2

10

It might seem like a silly & simple question, and yet, I've been unable to find a satisfying answer. Basically, I have a TListview (style = vsReport) with data. Sometimes, I have to update it, and therefore, I have to clear the listview and fill it again with the updated data.

However, when I do that, the scrollbar position is reseted to 0. I would like to be able to get the scrollbar position before the clearing and set it back to what it was before. If the updated data has the exact same amount of rows as the old data, I need the scrollbar to be at the exact same position as before; if not, I just need it to be more-or-less at the same place as before.

Seems easy, right? Yet, all I've found are hacks or tweaks with TopItem and MakeVisible. Is there any appropriate method to do that?

Thanks!

Varela answered 29/12, 2013 at 22:25 Comment(7)
Why clear the ListView in the first place? You can update the existing items instead, which will leave the selection (and scrollbar) alone.Figure
@KenWhite It seemed to me more logical because the updated data might have more or less rows than old data. Am I wrong?Varela
@KenWhite What if the number of items in the list increases? What if the old item is "decommissioned" (ie, doesn't appear in the "updated" list)?Sybaris
Your question was "If the updated data has the exact same amount of rows as the old data". If there are fewer rows, just update the ones you need and delete the rest (one by one). If there are "decommissioned" items, just replace the current content with the new content that should be there instead now. Clearing/recreating the list items is an expensive operation.Figure
@Sam: I never said "delete items to keep the scrollbar at the same position" anywhere in what I wrote. In fact, I advised the opposite (not to delete items at all if you don't have to do so). Please read what I wrote again.Figure
@Sam: I don't think i did any such thing. I understand the requirements just fine. I disagree with clearing and repopulating the listview without needing to do so. I also didn't add the scrollbar tag, so why you're telling me about it is unclear.Figure
TopItem is not a hack. But virtual list view is what you really want.Juan
I
15

Save the top item before clearing,

FSaveTop := ListView1.TopItem;

After updating, scroll the listview so that the saved top item's 'y' position will be 0 (+ header height):

var
  R: TRect;
begin
  if Assigned(FSaveTop) then begin
    // account for header height
    GetWindowRect(ListView_GetHeader(ListView1.Handle), R);
    ListView1.Scroll(0, FSaveTop.Position.Y - (R.Bottom - R.Top));
  end;
end;

Actually, since you're re-populating the listview, you have to devise a mechanism to find which item you want to be at the top instead of saving a reference to it.


If you don't like modifying scroll position through 'top item', since functions like SetScrollInfo, SetScrollPos won't update the client area of the control, you can use GetScrollInfo to get the 'nPos' of a TScrollInfo before clearing the list, and then send that many WM_VSCROLL messages with 'SB_LINEDOWN` after populating.

Save scroll position:

var
  FPos: Integer;
  SInfo: TScrollInfo;
begin
  SInfo.cbSize := SizeOf(SInfo);
  SInfo.fMask := SIF_ALL;
  GetScrollInfo(ListView1.Handle, SB_VERT, SInfo);
  FPos := SInfo.nPos;
  ...

After populating, scroll (assuming scroll position is 0):

var
  R: TRect;
begin
  ...
  R := ListView1.Items[0].DisplayRect(drBounds);
  ListView1.Scroll(0, FPos * (R.Bottom - R.Top));

or,

var
  i: Integer;
begin
  ...
  for i := 1 to FPos do
    SendMessage(ListView1.Handle, WM_VSCROLL, SB_LINEDOWN, 0);
Ianiana answered 29/12, 2013 at 23:28 Comment(4)
Where is the source of ListView_GetHeader? Which unit must be used?Kymric
@sam - Use 'commctrl'Ianiana
Wow, how sloppy. I would use the TopItem, except in my case I refresh the list because a user may have used (and thus removed) an item from the list - and if that top item was the one the user chose to remove, it wouldn't work. So I guess I'm stuck with the sloppy solution of sending scroll messages X number of times and hoping it gets to the precise position it was before.Ament
By R := ListView1.Items[0].DisplayRect(drBounds); you should check if the list is not empty.Oscaroscillate
O
-2
type
  TForm1 = class(TForm)
    ListView1: TListView;
    Timer1: TTimer;
    Timer2: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Timer2Timer(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
    pb:array [0..20] of Tprogressbar;
    i:integer;
      mintop,spacetop,readtop:integer;
    implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);


begin

mintop:=19;
spacetop:=14;
for i:=0 to 20 do
begin
listview1.AddItem('Item no'+inttostr(i),nil);
pb[i]:=Tprogressbar.create(self);
pb[i].Parent:=listview1;
pb[i].width:=120;
pb[i].height:=14;
pb[i].top:=mintop+i*spacetop;
pb[i].position:=i*5;
pb[i].Left:=listview1.Column[0].Width;



end;


end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  z,FPos: Integer;
  SInfo: TScrollInfo;
begin
  SInfo.cbSize := SizeOf(SInfo);
  SInfo.fMask := SIF_ALL;
  GetScrollInfo(ListView1.Handle, SB_VERT, SInfo);
  FPos := SInfo.nPos;
  form1.caption:='FPOS='+inttostr(fpos);
if fpos>0 then
begin
for z:=0 to 11 do
begin
pb[z+fpos-1].Top:=mintop+z*spacetop;
form1.caption:=form1.caption+' Z='+inttostr(z)+' !';
     end;
     end;

 end;
procedure TForm1.Timer2Timer(Sender: TObject);
var x:integer;
begin
  for  x:=0 to 20 do
  begin
  pb[x].Position:=Pb[x].position+1;
  end;


end;

end.


/// Code by BRTH1 - Simao Coelho - Portugal
Oxy answered 23/5, 2015 at 16:48 Comment(2)
if you are looking for tlistview with tprogressbars scrolling take a lookOxy
This code helped me to find the vertical scroll position of PDFium control. Excellent!Silva

© 2022 - 2024 — McMap. All rights reserved.