Scroll WPF Listview to specific line

2019-01-08 14:23发布

WPF, Browserlike app.
I got one page containing a ListView. After calling a PageFunction I add a line to the ListView, and want to scroll the new line into view:

  ListViewItem item = ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
  if (item != null)
    ScrollIntoView(item);

This works. As long as the new line is in view the line gets the focus like it should.

Problem is, things don't work when the line is not visible.
If the line is not visible, there is no ListViewItem for the line generated, so ItemContainerGenerator.ContainerFromIndex returns null.

But without the item, how do I scroll the line into view? Is there any way to scroll to the last line (or anywhere) without needing an ListViewItem?

11条回答
狗以群分
2楼-- · 2019-01-08 14:29

Thanks for that last tip Sam. I had a dialog which opened, meaning my grid lost focus every time the dialog closed. I use this:

if(currentRow >= 0 && currentRow < lstGrid.Items.Count) {
    lstGrid.SelectedIndex = currentRow;
    lstGrid.ScrollIntoView(lstGrid.SelectedItem);
    if(shouldFocusGrid) {
        ListViewItem item = lstGrid.ItemContainerGenerator.ContainerFromItem(lstGrid.SelectedItem) as ListViewItem;
        item.Focus();
    }
} else if(shouldFocusGrid) {
    lstGrid.Focus();
}
查看更多
别忘想泡老子
3楼-- · 2019-01-08 14:32

I made some changes to Sam's answer. Note that I wanted to scroll to the last line. Unfortunately the ListView sometiems just displayed the last line (even when there were e.g. 100 lines above it), so this is how I fixed that:

    public void ScrollToLastItem()
    {
        if (_mainViewModel.DisplayedList.Count > 0)
        {
            var listView = myListView;
            listView.SelectedItem = listView.Items.GetItemAt(_mainViewModel.DisplayedList.Count - 1);
            listView.ScrollIntoView(listView.Items[0]);
            listView.ScrollIntoView(listView.SelectedItem);
            //item.Focus();
        }
    }

Cheers

查看更多
【Aperson】
4楼-- · 2019-01-08 14:37

To overcome the virtualisation issue but still use ScrollIntoView and not hacking around in the guts of the ListView, you could also use your ViewModel objects to determine what is selected. Assuming that you have ViewModel objects in your list that feature an IsSelected property. You'd link the items to the ListView in XAML like this:

<ListView Name="PersonsListView" ItemsSource="{Binding PersonVMs}">
  <ListView.ItemContainerStyle>
    <Style TargetType="{x:Type ListViewItem}">
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
    </Style>
  </ListView.ItemContainerStyle>
</ListView>

Then, the code-behind method can scroll to the first selected item with this:

var firstSelected = PersonsListView.Items
    .OfType<TreeViewItemViewModel>().FirstOrDefault(x => x.IsSelected);
if (firstSelected != null)
    CoObjectsListView.ScrollIntoView(firstSelected);

This also works if the selected item is well out of view. In my experiment, the PersonsListView.SelectedItem property was null, but of course your ViewModel IsSelected property is always there. Be sure to call this method after all binding and loading has completed (with the right DispatcherPriority).

Using the ViewCommand pattern, your ViewModel code could look like this:

PersonVMs.ForEach(vm => vm.IsSelected = false);
PersonVMs.Add(newPersonVM);
newPersonVM.IsSelected = true;
ViewCommandManager.InvokeLoaded("ScrollToSelectedPerson");
查看更多
叛逆
5楼-- · 2019-01-08 14:38

I think the problem here is that the ListViewItem is not created yet if the line is not visible. WPF creates the Visible on demand.

So in this case you probably get null for the item, do you? (According to your comment, you do)

I have found a link on MSDN forums that suggest accessing the Scrollviewer directly in order to scroll. To me the solution presented there looks very much like a hack, but you can decide for yourself.

Here is the code snippet from the link above:

VirtualizingStackPanel vsp =  
  (VirtualizingStackPanel)typeof(ItemsControl).InvokeMember("_itemsHost",
   BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic, null, 
   _listView, null);

double scrollHeight = vsp.ScrollOwner.ScrollableHeight;

// itemIndex_ is index of the item which we want to show in the middle of the view
double offset = scrollHeight * itemIndex_ / _listView.Items.Count;

vsp.SetVerticalOffset(offset);
查看更多
Bombasti
6楼-- · 2019-01-08 14:39

In my project i need to display the selected index line from the listview to the user so i assigned the selected item to ListView control. This code will scroll the scrollbar and display the selected item.

BooleanListView.ScrollIntoView(BooleanListView.SelectedItem);

OR

var listView = BooleanListView; listView.SelectedItem = listView.Items.GetItemAt(BooleanListView.SelectedIndex); listView.ScrollIntoView(listView.Items[0]); listView.ScrollIntoView(listView.SelectedItem);

查看更多
放我归山
7楼-- · 2019-01-08 14:44

If you just want to show and focus the last item after creating a new data item, this method is maybe better. Compare to ScrollIntoView, ScrollToEnd of ScrollViewer is in my tests more reliable. In some tests using ScrollIntoView method of ListView like above failed and i don't know reason. But using ScrollViewer to scroll to last one can work.

void FocusLastOne(ListView lsv)
{
   ObservableCollection<object> items= sender as ObservableCollection<object>;

   Decorator d = VisualTreeHelper.GetChild(lsv, 0) as Decorator;
   ScrollViewer v = d.Child as ScrollViewer;
   v.ScrollToEnd();

   lsv.SelectedItem = lsv.Items.GetItemAt(items.Count - 1);
   ListViewItem lvi = lsv.ItemContainerGenerator.ContainerFromIndex(items.Count - 1) as ListViewItem;
   lvi.Focus();
}
查看更多
登录 后发表回答