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!
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);