6

The list-view control has the LVM_GETTOPINDEX message that allows to get the index of the topmost visible item.

Now I need to set the topmost visible item, but surprisingly there is no LVM_SETTOPINDEX message which would be natural.

Is there an easy clean way to set the topmost item?

My list-control is always in report mode.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • The `LVM_ENSUREVISIBLE` message is a start. Set `lParam` to `FALSE` to make sure the item is *entirely* visible. But this just makes sure it is visible, it doesn't necessarily zoom it up to the top of the list. It's going to be rather difficult to do that, of course, depending on the size of the list view control and the number of items in it. – Cody Gray - on strike Feb 04 '16 at 14:19
  • @CodyGray as you write `LVM_ENSUREVISIBLE` is a start, but that just ensures performs a minimal scroll so that the selected item is visible, it will be either at the first line of at the last line depending on if the selected item was above or below the view. `LVM_SETTOPINDEX` is again something that Microsoft forgot. – Jabberwocky Feb 04 '16 at 14:28
  • Consider if you have a very large list view control, and only a few items. The items don't fill up the box, and there's no reason for a scroll bar to appear. Now, how are you going to scroll them so that item #5 is at the top? You can't. You couldn't do it interactively, and you can't do it programmatically. Certainly there are times when it would work, but there are times when it wouldn't, so you can't have a generic message that does it. – Cody Gray - on strike Feb 04 '16 at 14:32
  • @CodyGray that's correct, but it would be the programmer's responsability to use the message correctly and even it the message is sent to tell that item #5 should be on top when this is ot possible, then nothing should happen and the SendMessage could return false. – Jabberwocky Feb 04 '16 at 15:51
  • 1
    Get the top item (`LVM_GETTOPINDEX`), if the desired item is above it you can just call `LVM_ENSUREVISIBLE`, If its below, get the count of items within the current viewport (`LVM_GETCOUNTPERPAGE`) then you can calculate the index of the item you need to use with `LVM_ENSUREVISIBLE` to bring the desired item to the top `(want_at_top_index + LVM_GETCOUNTPERPAGE) - 1` – Alex K. Feb 04 '16 at 20:52
  • 1
    What I do is ensure the desired item is visible first, then get the top index and if they are not the same item then calculate the number of items between them and scroll the ListView that many items multipled by the current item height. This also allows me to display any item at any particular position within the current view, not just at the top. For instance, sometimes I want an item to appear in the center instead. – Remy Lebeau Feb 04 '16 at 21:18
  • @RemyLebeau that's what I needed. See my own answer below. Thanks. – Jabberwocky Feb 08 '16 at 19:12

3 Answers3

2
  1. Use LVM_GETITEMPOSITION or LVM_GETITEMRECT to obtain the items position.
  2. Use LVM_SCROLL to scroll the list so that your item is the top item.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
2

First, it may NOT be possible. For example, if the list doesn't have enough items after your top index to fill the page.

As there is no direct way, you can count the number of items on the page, add that count to your index and call EnsureVisible(). This will make your sure that your top is above visible page. The next EnsureVisible() for your item will bring it into the view, at the top of the page. Of course, you would need to block updates to avoid jerking of the screen.

Example (updated by Vlad):

void CDlg::SetTopIndex(int top)
{
    int bottom = min(top + m_List.GetCountPerPage(), m_List.GetItemCount() - 1);
    m_List.SetRedraw(FALSE);
    m_List.EnsureVisible(bottom, TRUE);
    m_List.EnsureVisible(top, FALSE);
    m_List.SetRedraw(TRUE);
}
Vlad Feinstein
  • 10,960
  • 1
  • 12
  • 27
1

This function does the job:

void SetTopIndex(CListCtrl & listctrl, int topindex)
{
  int actualtopindex = listctrl.GetTopIndex();
  int horspacing;
  int lineheight;
  listctrl.GetItemSpacing(TRUE, &horspacing, &lineheight);

  CSize scrollsize(0, (topindex - actualtopindex) * lineheight);
  listctrl.Scroll(scrollsize);
}

No parameter sanitation is done here.

Thanks to David Heffernan and Remy Lebeau for giving me the idea.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115