jay_t55
jay_t55

Reputation: 11652

Why doesn't ListView.ScrollIntoView ever work?

I am trying to scroll into view so that the very last item in a vertical listivew is always showing, but ListView.ScrollIntoView() never works.

I have tried:

button1_Click(object sender, EventArgs e)
{

            activities.Add(new Activities()
            {
                Time = DateTime.Now,
                Message = message
            });

            ActivityList.ItemsSource = activities;

            // Go to bottom of ListView.
            ActivityList.SelectedIndex = ActivityList.Items.Count;
            ActivityList.ScrollIntoView(ActivityList.SelectedIndex);
}

I have also tried:

ActivityList.ScrollIntoView(ActivityList.Items.Count), and ActivityList.ScrollIntoView(ActivityList.Items.Count + 1); but nothing is working.

Please help. This is really annoying. And the documentation isn't very helpful in this case.

Upvotes: 14

Views: 23141

Answers (12)

Martí Climent
Martí Climent

Reputation: 557

NOTE: This anwer applies to WinUI3, but may also apply to WPF

This may be happening to you because you have the ListView encapsulated into a ScrollView, which handles the scroll.

<ScrollView ...>
    <ListView ...>
       ...
    </ListView>
</ScrollView>

Therefore, the ListView expands to the max height and it does not scroll to the specified item (since it technically is already visible, at least from the ListView's pov).

What you need to do is remove the ScrollView and let the ListView handle the scrolling, and then it will be able to properly detect that the given item is out of view, and scroll it into the view again.

<!-- There is no ScrollView, ListView will take care of overflows and scrollviews-->
<ListView ...>
   ...
</ListView>

Upvotes: 0

Huw Pendry
Huw Pendry

Reputation: 95

One other reason that this can happen is because the item has been virtualized out by the ItemsPanel which by default is a VirtualizingStackPanel. If you don't require the virtualization because you only have a relatively small number of items then you can use the normal StackPanel as your ItemsPanel instead.

<ListBox>
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <StackPanel Orientation="Vertical" />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
</ListBox>

Upvotes: 0

Adephx
Adephx

Reputation: 317

In accordance with what keyboardP stated on Jun 5 '13 at 14:37 here: https://stackoverflow.com/a/16942650/2717521

if that still doesn't work, try calling ActivityList.UpdateLayout(); just before the ScrollIntoView method.

I had a simmilar problem between what I initially thought, identical DataGrids. First one would ScrollIntoView without any issues, second one refused to do so no matter what, unless I triggered UpdateLayout. It turns out that I was also grouping the second DataGrid:

        mycollection.GroupDescriptions.Clear();
        mycollection.GroupDescriptions.Add(new PropertyGroupDescription("PART"));
        datagrid_parts.ItemsSource = mycollection.View;

Apparently if your DataGrid is grouping, the ScrollViewer can not get correct "visual" space, because some visible areas stay inside Group as GroupItem. Refference: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b809f5ae-08e7-415f-9e86-fc0086cb49e7/datagrid-scrollintoview-does-not-bring-given-item-into-view-when-using-virtualization-and-grouping?forum=wpf

Upvotes: 0

ThePennyDrops
ThePennyDrops

Reputation: 171

I am not sure if this topic is still of interest, but I could not get the above options to work on my LISTBOX. I read somewhere that unequal item sizes also stopped the scroll working. I did get the below Powershell code to work; the 'UpdateLayout()' seemed to be vital:

$zItemsCount = $ListBox.Items.Count - 1
Write-Host -ForegroundColor Cyan "ListBox Items Count - 1:" $zItemsCount
(0..$zItemsCount) | ForEach-Object {
  $ListBox.SelectedIndex = $_
  $ListBox.UpdateLayout()
  $ListBox.ScrollIntoView($ListBox.SelectedItem)
  }#-EndOf: each item -----

Upvotes: 0

panzer
panzer

Reputation: 133

Just call method UpdateLayout...

UpdateLayout();
ActivityList.ScrollIntoView(ActivityList.Items[ActivityList.Items.Count - 1]);

This will work...

Or more easier way...just compile app :) and it will work without UpdateLayot()

Upvotes: -2

Paul
Paul

Reputation: 61

This is an old post but I had the same problem in a Windows Store app and found the following worked:

    private void PagesListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        pageListView.UpdateLayout();
        pageListView.ScrollIntoView(pageListView.SelectedItem);
    }

Cheers, Paul

Upvotes: 2

Alex
Alex

Reputation: 29

The problem I faced was similar. I was adding programatically to a ListBox and needed the last item added to be visible. After tying various methods I found the following to be the simplest and the best

lstbox.Items.MoveCurrentToLast();
lstbox.ScrollIntoView(lstbox.Items.CurrentItem);

Upvotes: -1

ck84vi
ck84vi

Reputation: 1576

Its an old post but since none of the answers here worked for my case I want to add my implementation. I found the solution here: [https://social.msdn.microsoft.com/Forums/vstudio/en-US/8a745550-4360-4b53-85f5-032f8f46e2cc/listview-scrollintoview-not-working-with-a-observablecollection?forum=wpf][1]

((INotifyCollectionChanged)lvLogs.Items).CollectionChanged += ListView_CollectionChanged;

private void ListView_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    ScrollToEnd(this.lvLogs);
}

private void ListView_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if ((e.Action == NotifyCollectionChangedAction.Add) && (e.NewItems != null))
            {
                try 
                {
                    ScrollToEnd(this.lvLogs);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString(), "Error updating list view", MessageBoxButton.OK, MessageBoxImage.Error);
                }

            }
        }

public void ScrollToEnd(ListView _ListView)
{
    ScrollViewer _ScrollViewer = GetDescendantByType(_ListView, typeof(ScrollViewer)) as ScrollViewer;
    _ScrollViewer.ScrollToEnd();
}

public Visual GetDescendantByType(Visual element, Type type)
{
    if (element == null) return null;
    if (element.GetType() == type) return element;
    Visual foundElement = null;
    if (element is FrameworkElement)
    {
        (element as FrameworkElement).ApplyTemplate();
    }
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
    {
        Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
        foundElement = GetDescendantByType(visual, type);
        if (foundElement != null)
            break;
    }
    return foundElement;
}

For me it works perfect.

Upvotes: 4

hug
hug

Reputation: 1218

The problem with ActivityList.ScrollIntoView(ActivityList.Items[ActivityList.Items.Count - 1]); and similiar "solutions", at least from what I'm experiencing, is that when the ListBox contains the same item more than once, it jumps to the first it finds, i.e. lowest index. So, it doesn't care about the index of the item you input, it just looks for that item and picks the first.

I haven't yet found a solution to this, but the approach I'm taking right now is to Insert instead of Add items. In the original posters case I think that would result in:

activities.Insert(0, new Activities()
        {
            Time = DateTime.Now,
            Message = message
        });

or possibly:

ActivityList.Items.Insert(0, new Activities()
        {
            Time = DateTime.Now,
            Message = message
        });

Which causes every new entry to be inserted in the top, and thus ScrollIntoView doesn't even need to be called.

Upvotes: 4

G&#225;bor
G&#225;bor

Reputation: 10214

It's got something to do with the internal list representation, namely, the items are not yet in place when you call the ScrollIntoView(). After many attempts, this is what I finally came up with and this seems to work: attach two handlers to every ListBox/ListView:

<ListBox x:Name="YourList" ... Loaded="YourList_Loaded" SelectionChanged="YourList_SelectionChanged">

and

void YourList_Loaded(object sender, RoutedEventArgs e) {
  if (YourList.SelectedItem != null)
    YourList.ScrollIntoView(YourList.SelectedItem);
}

void YourList_SelectionChanged(object sender, SelectionChangedEventArgs e) {
  if (YourList.SelectedItem != null)
    YourList.ScrollIntoView(YourList.SelectedItem);
}

The second handler you can't do without. You can't call ScrollIntoView() immediately after you set the selected item, it's not yet ready. You have to allow the list to inform you when it has actually changed the item. The first handler depends on the circumstances, if you have problems with the selection showing up after the initial loading of the list contents, you might need it, too.

Upvotes: 11

keyboardP
keyboardP

Reputation: 69362

You're passing in the index when the method expects the item object. Try this to scroll to the selected item.

ActivityList.ScrollIntoView(ActivityList.SelectedItem);

If you want to scroll to the last item, you can use this

ActivityList.ScrollIntoView(ActivityList.Items[ActivityList.Items.Count - 1]);

Upvotes: 21

SWeko
SWeko

Reputation: 30872

You are assigning the index of the item, and the list box expects the item itself. Try something like this:

 var activity = new Activities()
        {
            Time = DateTime.Now,
            Message = message
        };

 activities.Add(activity);

 ActivityList.ItemsSource = activities;
 ActivityList.ScrollIntoView(activity);

Upvotes: 1

Related Questions