List control LVM_SETTOPINDEX needed
Asked Answered
S

3

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.

Singleminded answered 4/2, 2016 at 14:13 Comment(7)
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.Edithe
@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.Singleminded
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.Edithe
@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.Singleminded
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) - 1Evette
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.Dinsmore
@RemyLebeau that's what I needed. See my own answer below. Thanks.Singleminded
S
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.

Singleminded answered 8/2, 2016 at 19:10 Comment(0)
B
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.
Bulahbulawayo answered 4/2, 2016 at 14:32 Comment(3)
That's what I needed, se my own answer below.Singleminded
I think my answer has all that's needed, no? The concept is what matters surely. I'm not sure what you have against this answer.Bulahbulawayo
Your answer is good, I just provided the implementation in my own answer.Singleminded
H
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);
}
Harmonia answered 4/2, 2016 at 14:32 Comment(1)
I don't mind, but I just updated your example to reflect my proposal.Harmonia
S
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.

Singleminded answered 8/2, 2016 at 19:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.