How to set ListView scroll info?

  • Thread starter Michael J. Salamone
  • Start date
M

Michael J. Salamone

I have a listview whose contents are constantly being refreshed. When a
refresh occurs, I delete all the items and add back. I want to maintain the
scroll position before deleting all the items, and then want to restore it
once the items are added back.

I'm using P/Invoke Get/SetScrollInfo to get and restore the scroll
information. GetScrollInfo is working fine - it returns expected
information. SetScrollInfo has no effect - the scroll positions are
unchanged (both horz and vert are 0).

I also tried Get/SetScrollPos - no luck there, either.

Is this the correct way to do this? Anyone know what might be wrong?

Here's some of my code:

private void RefreshList()
{
User32.SCROLLINFO hsi = User32.GetScrollInfo((uint)
MyListView.Handle.ToInt32(), Constants.SB_HORZ);
User32.SCROLLINFO vsi = User32.GetScrollInfo((uint)
MyListView.Handle.ToInt32(), Constants.SB_VERT);

MyListView.BeginUpdate();

MyListView.Items.Clear();

// Add new items here...

User32.SetScrollInfo((uint)MyListView.Handle.ToInt32(),
Constants.SB_HORZ, false, hsi);
User32.SetScrollInfo((uint)MyListView.Handle.ToInt32(),
Constants.SB_VERT, false, vsi);

MyListView.EndUpdate();
}

public class Constants
{
public const int SB_HORZ = 0;
public const int SB_VERT = 1;
public const int SB_CTL = 2;
public const int SB_BOTH = 4;

public const int SIF_RANGE = 0x0001;
public const int SIF_PAGE = 0x0002;
public const int SIF_POS = 0x0004;
public const int SIF_DISABLENOSCROLL = 0x0008;
public const int SIF_TRACKPOS = 0x0010;
public const int SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS |
SIF_TRACKPOS);
}

public class User32
{
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLINFO
{
public UInt32 cbSize;
public UInt32 fMask;
public Int32 nMin;
public Int32 nMax;
public UInt32 nPage;
public Int32 nPos;
public Int32 nTrackPos;
}

[DllImport("user32.dll", EntryPoint = "GetScrollInfo", SetLastError =
true)]
private static extern int GetScrollInfoWnd(uint hWnd, int nBar, ref
SCROLLINFO si);

public static SCROLLINFO GetScrollInfo(uint hWnd, int nBar)
{
SCROLLINFO si = new SCROLLINFO();

si.cbSize = (uint) System.Runtime.InteropServices.Marshal.SizeOf(si);
si.fMask = Constants.SIF_ALL;

// also tried si.fMask = Constants.SIF_PAGE | Constants.SIF_POS |
Constants.SIF_RANGE

GetScrollInfoWnd(hWnd, nBar, ref si); // returns correct data

return si;
}

[DllImport("user32.dll", EntryPoint = "SetScrollInfo", SetLastError =
true)]
private static extern int SetScrollInfoWnd(uint hWnd, int nBar, bool
bRedraw, ref SCROLLINFO si);

public static int SetScrollInfo(uint hWnd, int nBar, bool bRedraw,
SCROLLINFO si)
{
int rc = SetScrollInfoWnd(hWnd, nBar, bRedraw, ref si);

return rc; // return is 0 - presumably previous scroll position
}
}

Thanks
 
N

Nick Hounsome

Michael J. Salamone said:
I have a listview whose contents are constantly being refreshed. When a
refresh occurs, I delete all the items and add back. I want to maintain
the scroll position before deleting all the items, and then want to restore
it once the items are added back.

Why not just overwrite your existing items (and delete any excess). It would
probably be quicker and would avoid all scrolling problems and the use of
P/Invoke
 
M

Michael J. Salamone

Unless I can solve the scroll problem, that's probably what I'm going to do.
The code is a whole lot simpler to clear and add back. And I don't know
about quicker (you have to search and update if found, and then search and
delete). But, you're right, if I write a little more code, I don't have to
worry about scrolling *and* should have an additional benefit of not being
such a litterbug creating all that garbage when clearing the listview :)
Which in the end, would be faster on the garbage collection side (should be
fewer invocations).

I may have just talked myself into it, but still would like to know how to
restore the scroll position if anyone knows.
 
N

Nick Hounsome

Michael J. Salamone said:
Unless I can solve the scroll problem, that's probably what I'm going to
do. The code is a whole lot simpler to clear and add back. And I don't
know about quicker (you have to search and update if found, and then
search and delete).

You misunderstand - I am not suggesting that you match current items to new
items. The following works for intial fill AND updates:

AddExtraItems(Math.Max(newItems.Count-view.Items.Count,0));
DeleteExcessItems(Math.Max(view.Items.Count-newItems.Count,0));
for(int i = 0; i < newItems.Count; ++i)
view.Items.Text = newItems.Text;

You only need searching if you want to maintain the proper selection not the
scroll position.
But, you're right, if I write a little more code, I don't have to worry
about scrolling *and* should have an additional benefit of not being such
a litterbug creating all that garbage when clearing the listview :) Which
in the end, would be faster on the garbage collection side (should be
fewer invocations).

I may have just talked myself into it, but still would like to know how to
restore the scroll position if anyone knows.

--
Michael Salamone [eMVP]
Entrek Software, Inc.
www.entrek.com


Nick Hounsome said:
Why not just overwrite your existing items (and delete any excess). It
would probably be quicker and would avoid all scrolling problems and the
use of P/Invoke
 
J

Jared

3 solutions for you, if you want to detect scroll there is a way to do that
as well

(
..Item(x).Selected=true
listview.select
)

listview.topitem=x

..Item(x).EnsureVisible = true


Michael J. Salamone said:
I have a listview whose contents are constantly being refreshed. When a
refresh occurs, I delete all the items and add back. I want to maintain
the scroll position before deleting all the items, and then want to restore
it once the items are added back.

I'm using P/Invoke Get/SetScrollInfo to get and restore the scroll
information. GetScrollInfo is working fine - it returns expected
information. SetScrollInfo has no effect - the scroll positions are
unchanged (both horz and vert are 0).

I also tried Get/SetScrollPos - no luck there, either.

Is this the correct way to do this? Anyone know what might be wrong?

Here's some of my code:

private void RefreshList()
{
User32.SCROLLINFO hsi = User32.GetScrollInfo((uint)
MyListView.Handle.ToInt32(), Constants.SB_HORZ);
User32.SCROLLINFO vsi = User32.GetScrollInfo((uint)
MyListView.Handle.ToInt32(), Constants.SB_VERT);

MyListView.BeginUpdate();

MyListView.Items.Clear();

// Add new items here...

User32.SetScrollInfo((uint)MyListView.Handle.ToInt32(),
Constants.SB_HORZ, false, hsi);
User32.SetScrollInfo((uint)MyListView.Handle.ToInt32(),
Constants.SB_VERT, false, vsi);

MyListView.EndUpdate();
}

public class Constants
{
public const int SB_HORZ = 0;
public const int SB_VERT = 1;
public const int SB_CTL = 2;
public const int SB_BOTH = 4;

public const int SIF_RANGE = 0x0001;
public const int SIF_PAGE = 0x0002;
public const int SIF_POS = 0x0004;
public const int SIF_DISABLENOSCROLL = 0x0008;
public const int SIF_TRACKPOS = 0x0010;
public const int SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS |
SIF_TRACKPOS);
}

public class User32
{
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLINFO
{
public UInt32 cbSize;
public UInt32 fMask;
public Int32 nMin;
public Int32 nMax;
public UInt32 nPage;
public Int32 nPos;
public Int32 nTrackPos;
}

[DllImport("user32.dll", EntryPoint = "GetScrollInfo", SetLastError =
true)]
private static extern int GetScrollInfoWnd(uint hWnd, int nBar, ref
SCROLLINFO si);

public static SCROLLINFO GetScrollInfo(uint hWnd, int nBar)
{
SCROLLINFO si = new SCROLLINFO();

si.cbSize = (uint) System.Runtime.InteropServices.Marshal.SizeOf(si);
si.fMask = Constants.SIF_ALL;

// also tried si.fMask = Constants.SIF_PAGE | Constants.SIF_POS |
Constants.SIF_RANGE

GetScrollInfoWnd(hWnd, nBar, ref si); // returns correct data

return si;
}

[DllImport("user32.dll", EntryPoint = "SetScrollInfo", SetLastError =
true)]
private static extern int SetScrollInfoWnd(uint hWnd, int nBar, bool
bRedraw, ref SCROLLINFO si);

public static int SetScrollInfo(uint hWnd, int nBar, bool bRedraw,
SCROLLINFO si)
{
int rc = SetScrollInfoWnd(hWnd, nBar, bRedraw, ref si);

return rc; // return is 0 - presumably previous scroll position
}
}

Thanks
 
M

Michael J. Salamone

I had already tried that. It's not working for me. I can select the
previous item - that's fine. Setting listviewitem.EnsureVisible doesn't
seem to do anything. Maybe I have to do that after adding all the other
items. That is, after calling EnsureVisible on the selected item, I add
more items to the listview. Maybe doing that invalidates the
EnsureVisible()?

Anyway, even if it works, it doesn't necessarily restore the scroll position
to where it was before the update. It would scroll so that the selected
item was either at the top or bottom of the client area.

--
Michael Salamone [eMVP]
Entrek Software, Inc.
www.entrek.com


Jared said:
3 solutions for you, if you want to detect scroll there is a way to do that
as well

(
.Item(x).Selected=true
listview.select
)

listview.topitem=x

.Item(x).EnsureVisible = true


Michael J. Salamone said:
I have a listview whose contents are constantly being refreshed. When a
refresh occurs, I delete all the items and add back. I want to maintain
the scroll position before deleting all the items, and then want to
restore it once the items are added back.

I'm using P/Invoke Get/SetScrollInfo to get and restore the scroll
information. GetScrollInfo is working fine - it returns expected
information. SetScrollInfo has no effect - the scroll positions are
unchanged (both horz and vert are 0).

I also tried Get/SetScrollPos - no luck there, either.

Is this the correct way to do this? Anyone know what might be wrong?

Here's some of my code:

private void RefreshList()
{
User32.SCROLLINFO hsi = User32.GetScrollInfo((uint)
MyListView.Handle.ToInt32(), Constants.SB_HORZ);
User32.SCROLLINFO vsi = User32.GetScrollInfo((uint)
MyListView.Handle.ToInt32(), Constants.SB_VERT);

MyListView.BeginUpdate();

MyListView.Items.Clear();

// Add new items here...

User32.SetScrollInfo((uint)MyListView.Handle.ToInt32(),
Constants.SB_HORZ, false, hsi);
User32.SetScrollInfo((uint)MyListView.Handle.ToInt32(),
Constants.SB_VERT, false, vsi);

MyListView.EndUpdate();
}

public class Constants
{
public const int SB_HORZ = 0;
public const int SB_VERT = 1;
public const int SB_CTL = 2;
public const int SB_BOTH = 4;

public const int SIF_RANGE = 0x0001;
public const int SIF_PAGE = 0x0002;
public const int SIF_POS = 0x0004;
public const int SIF_DISABLENOSCROLL = 0x0008;
public const int SIF_TRACKPOS = 0x0010;
public const int SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS |
SIF_TRACKPOS);
}

public class User32
{
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLINFO
{
public UInt32 cbSize;
public UInt32 fMask;
public Int32 nMin;
public Int32 nMax;
public UInt32 nPage;
public Int32 nPos;
public Int32 nTrackPos;
}

[DllImport("user32.dll", EntryPoint = "GetScrollInfo", SetLastError =
true)]
private static extern int GetScrollInfoWnd(uint hWnd, int nBar, ref
SCROLLINFO si);

public static SCROLLINFO GetScrollInfo(uint hWnd, int nBar)
{
SCROLLINFO si = new SCROLLINFO();

si.cbSize = (uint) System.Runtime.InteropServices.Marshal.SizeOf(si);
si.fMask = Constants.SIF_ALL;

// also tried si.fMask = Constants.SIF_PAGE | Constants.SIF_POS |
Constants.SIF_RANGE

GetScrollInfoWnd(hWnd, nBar, ref si); // returns correct data

return si;
}

[DllImport("user32.dll", EntryPoint = "SetScrollInfo", SetLastError =
true)]
private static extern int SetScrollInfoWnd(uint hWnd, int nBar, bool
bRedraw, ref SCROLLINFO si);

public static int SetScrollInfo(uint hWnd, int nBar, bool bRedraw,
SCROLLINFO si)
{
int rc = SetScrollInfoWnd(hWnd, nBar, bRedraw, ref si);

return rc; // return is 0 - presumably previous scroll position
}
}

Thanks
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top