locked
Set ListView scroll position RRS feed

  • Question

  • Hi,

    I'm using a ListView in Details view and set Scrollable true. Frequently I update the ListView (clear all Items and add new Items). But this causes the scrollbars to set the scroll position to 0. Is it possible to save the scroll position before updating the View and set the position after updating?

     

    With Win32Api “int horzScroll = GetScrollPos(lvAnalysis.Handle, SBS_HORZ);” I can get the current position, but when I use “SetScrollPos(lvAnalysis.Handle, SBS_HORZ, horzScroll, true);” only the scrollbar had a new position and the ListView itself doesn’t scroll. Also “SendMessage(lvAnalysis.Handle, WM_HSCROLL, (UIntPtr)(horzScroll<<16 | SB_THUMBPOSITION), IntPtr.Zero);” doesn’t work. Only with SB_PAGERIGHT or SB_LINERIGHT the ListView scrolls.

     

    Does anybody know how to set the scroll position of a ListView?

     

    Thanks

     

    Monday, June 23, 2008 7:54 AM

Answers

  • Hello,

     

    Thank you very much for the answers.

     

    I’m using GetScrollPos for receiving the horizontal scroll position. Also I need LockWindowUpdate to avoid flickering after setting new scroll position.

     

    But it doesn’t work with the vertical scroll position, because  GetScrollPos(…, SBS_VERT) returns the position in count of rows and SendMessage(...) needs the position in pixels. Does somebody know how to calculate the pixelsize with the count of rows???

     

     

    Here my code with horizontal scrole:

     

    Code Snippet

    const Int32 LVM_FIRST = 0x1000;

    const Int32 LVM_SCROLL = LVM_FIRST + 20;

    const int SBS_HORZ = 0;

     

    [DllImport("user32.dll")]

    static extern int GetScrollPos(System.IntPtr hWnd, int nBar);

    [DllImport("user32.dll")]

    static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]

    static extern bool LockWindowUpdate(IntPtr Handle);

     

    private void UpdateListview()

    {

    LockWindowUpdate(lvListview.Handle);

    int horzScroll = GetScrollPos(lvAnalysis.Handle, SBS_HORZ);

     

    //Code for changing

     

    SendMessage(lvListview.Handle, LVM_SCROLL, (IntPtr)horzScroll, IntPtr.Zero);

    LockWindowUpdate(IntPtr.Zero);

    }

     

     

     

    Wednesday, June 25, 2008 1:11 PM
  • I think the vertical size of a ListView row is the larger of (1) the height of the images in the SmallImageList property and (2) the line height of the ListView's Font. So you could use one of these values to determine the row height and convert row position to pixels.

     

    BTW, nice tip on the LockWindowUpdate.

     

    As an aside, I'm noticing that, when you change the sort direction of a ListView, the ListView seems to get a new window handle, complete re-draw itself, and arbitrarily scroll the view downward some number of pixels (occasionally it will be upward), even if you send it an LVM_SCROLL message with a vertical component. Very strange behavior.

    Wednesday, June 25, 2008 4:44 PM

All replies

  • Hi,

     

    Welcome to the MSDN forums.

     

    A simple way to do this would be to use EnsureVisible(). So let us say you want the scroll to go to item 20

     

    ListView1.Items[21].EnsureVisible();

     

    or

     

    ListView1.EnsureVisible(21);

     

    HTH,

    Suprotim Agarwal

     

    Monday, June 23, 2008 8:57 AM
  • Hello Suprotim Agarwal,

     

    Thanks for your answer. But your function only set the vertical scrollbar. I need to set both vertical and horizontal scrollbar (exactly on the position the had bevore). Is there another solution?

    Monday, June 23, 2008 11:00 AM
  • This seems like a really common request, and it's baffling to me that no one has come up with an answer that seems to work consistently. I've tried using GetScrollPos/SetScrollPos and GetScrollInfo/SetScrollInfo, but neither of these seem to have any effect on the Listview. (Occasionally these calls do move the scrollbars, but they are out of sync with the ListView's contents, which have reset to the default position.)

     

    In my particular case, all I'm trying to do is restore the horizontal scroll position of a ListView following a sort (which resets the ListView's position). Oddly enough, sorting the ListView causes it to obtain a new window handle, but I'm referencing the new handle post-sort, so it's not a matter of calling SetScrollInfo on the wrong handle.

     

    Can anyone post working code that properly saves and restores the horizontal / vertical scroll positions of a ListView (including both its scrollbars and list items), which is what I believe is what the original poster was asking about?

     

    Thanks.

     

     

    Tuesday, June 24, 2008 10:11 PM
  • Okay, I've got it working for my sorting scenario. I'm using a combination of GetScrollInfo and sending an LVM_SCROLL message to the ListView. I'm sure there's a more efficient way to do this, but this is what's currently working for me. This code below is running within my subclassed ListView control:

     

    Code Snippet

        Private Const LVM_FIRST As Int32 = &H1000
        Private Const LVM_SCROLL As Int32 = LVM_FIRST + 20

        Structure SCROLLINFO
            Dim cbSize As Integer
            Dim fMask As Integer
            Dim nMin As Integer
            Dim nMax As Integer
            Dim nPage As Integer
            Dim nPos As Integer
            Dim nTrackPos As Integer
        End Structure

     

        Private Const SB_HORZ = 0
        Private Const SB_VERT = 1

        Private Const SIF_RANGE = &H1
        Private Const SIF_PAGE = &H2
        Private Const SIF_POS = &H4
        Private Const SIF_TRACKPOS = &H10
        Private Const SIF_ALL = (SIF_RANGE Or SIF_PAGE Or SIF_POS Or SIF_TRACKPOS)

     

        <DllImport("user32.dll")> _
        Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Int32, ByVal wParam As Int32, ByRef lParam As Int32) As Boolean
        End Function


        <DllImport("user32.dll")> _
        Private Shared Function GetScrollInfo(ByVal hWnd As IntPtr, ByVal n As Integer, ByRef lpScrollInfo As SCROLLINFO) As Integer
        End Function

     

        Public Shadows Sub Sort(ByVal ColumnIndex As Integer, ByVal CompareType As ListViewCompareType, ByVal Order As SortOrder)

     

    ' Get scroll position info

            Dim si As SCROLLINFO
            si.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(si)
            si.fMask = SIF_ALL


            GetScrollInfo(Handle, SB_HORZ, si)

     

    ' Not included: sorting logic, performed here

     

    ' Restore the scrollbar position

    SendMessage(Handle, LVM_SCROLL, si.nPos, 0)

     

    End Sub

     

     

     

     

     

    Tuesday, June 24, 2008 10:44 PM
  • Hi,

     

    Subclass the ListView control and then add the method for horizontal scrolling as given by Ken over here

    Code Snippet

    [DllImport("user32")]
    static extern IntPtr SendMessage(IntPtr Handle, Int32 msg, IntPtr wParam,
    IntPtr lParam);

    protected void ScrollH(int pixelsToScroll)
    {
    const Int32 LVM_FIRST = 0x1000;
    const Int32 LVM_SCROLL = LVM_FIRST + 20;
    SendMessage(lvwList.Handle, LVM_SCROLL, (IntPtr) pixelsToScroll,
    IntPtr.Zero);
    }

    To know when the user scrolls horizonatally, you'll need to capture the
    WM_HSCROLL message in the WndProc() method, e.g.

    protected override void WndProc(ref Message m)
    {
    const Int32 WM_HSCROLL = 0x114;

    if (m.Msg == WM_HSCROLL)
    HandleHorizontalScroll();

    base.WndProc(ref m);
    }

     

     


    HTH,

    Suprotim Agarwal

     

    Wednesday, June 25, 2008 2:20 AM
  • Hello,

     

    Thank you very much for the answers.

     

    I’m using GetScrollPos for receiving the horizontal scroll position. Also I need LockWindowUpdate to avoid flickering after setting new scroll position.

     

    But it doesn’t work with the vertical scroll position, because  GetScrollPos(…, SBS_VERT) returns the position in count of rows and SendMessage(...) needs the position in pixels. Does somebody know how to calculate the pixelsize with the count of rows???

     

     

    Here my code with horizontal scrole:

     

    Code Snippet

    const Int32 LVM_FIRST = 0x1000;

    const Int32 LVM_SCROLL = LVM_FIRST + 20;

    const int SBS_HORZ = 0;

     

    [DllImport("user32.dll")]

    static extern int GetScrollPos(System.IntPtr hWnd, int nBar);

    [DllImport("user32.dll")]

    static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]

    static extern bool LockWindowUpdate(IntPtr Handle);

     

    private void UpdateListview()

    {

    LockWindowUpdate(lvListview.Handle);

    int horzScroll = GetScrollPos(lvAnalysis.Handle, SBS_HORZ);

     

    //Code for changing

     

    SendMessage(lvListview.Handle, LVM_SCROLL, (IntPtr)horzScroll, IntPtr.Zero);

    LockWindowUpdate(IntPtr.Zero);

    }

     

     

     

    Wednesday, June 25, 2008 1:11 PM
  • I think the vertical size of a ListView row is the larger of (1) the height of the images in the SmallImageList property and (2) the line height of the ListView's Font. So you could use one of these values to determine the row height and convert row position to pixels.

     

    BTW, nice tip on the LockWindowUpdate.

     

    As an aside, I'm noticing that, when you change the sort direction of a ListView, the ListView seems to get a new window handle, complete re-draw itself, and arbitrarily scroll the view downward some number of pixels (occasionally it will be upward), even if you send it an LVM_SCROLL message with a vertical component. Very strange behavior.

    Wednesday, June 25, 2008 4:44 PM
  • OK, it's a two year old thread, but maybe someone else will need this info one day...

    I've found the best answer to this (for me) is the following:

     

    //save scroll position

    int idx = listView.Items.IndexOf(listView.TopItem);

    ...

     

    //restore vertical scroll position (if records were added or deleted things may get weird)

     

    try

    {

    listView.TopItem = listView.Items[idx];

    }

     

    catch (Exception ex)

    {

     

    Debug.WriteLine("Error restoring scroll position: " + ex.Message);

    }

    The LockWindowUpdate function is cool -- it totally eliminates the flash when I clear/refill the listview.

     

    • Proposed as answer by Edgemeal Monday, December 6, 2010 6:52 AM
    Wednesday, June 9, 2010 6:27 PM
  • Thanks ptaber!
    Monday, December 6, 2010 6:51 AM
  • ListView top item is not working somehow, at least on VS2013. It is always scrolling to the pos-1, rather than pos.

    I have to use LVM_SCROLL, and as you know it requires pixels.

    To convert from scroll position to pixels, you can get the item size e.g. listView.GetItemRect(0).Width, then multiply by the scroll position. Then sending LVM_SCROLL with the resultant pixel value - and it works.

    Cheers.

    Monday, September 15, 2014 11:00 PM