Прокрутите список просмотра WPF до определенной строки. - PullRequest
22 голосов
/ 17 октября 2008

WPF, браузероподобное приложение.
Я получил одну страницу, содержащую ListView. После вызова функции PageFunction я добавляю строку в ListView и хочу прокрутить новую строку в представлении:

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

Это работает. Пока новая линия видна, она получает фокус, как и должна.

Проблема в том, что вещи не работают, когда строка не видна.
Если строка не видна, для сгенерированной строки нет ListViewItem, поэтому ItemContainerGenerator.ContainerFromIndex возвращает ноль.

Но без элемента, как прокрутить строку в поле зрения? Есть ли способ прокрутки до последней строки (или в любом месте) без необходимости ListViewItem?

Ответы [ 11 ]

39 голосов
/ 23 октября 2008

Кто-то сказал мне еще лучший способ прокрутки до определенной строки, который прост и работает как шарм.
Короче говоря:

public void ScrollToLastItem()
{
  lv.SelectedItem = lv.Items.GetItemAt(rows.Count - 1);
  lv.ScrollIntoView(lv.SelectedItem);
  ListViewItem item = lv.ItemContainerGenerator.ContainerFromItem(lv.SelectedItem) as ListViewItem;
  item.Focus();
}

Более длинная версия на форумах MSDN :

10 голосов
/ 17 октября 2008

Я думаю, что проблема здесь в том, что ListViewItem еще не создан, если строка не видна. WPF создает Visible по требованию.

Так что в этом случае вы, вероятно, получите null за предмет, не так ли? (Согласно вашему комментарию, вы делаете)

Я нашел ссылку на форумах MSDN, которая предлагает прямой доступ к Scrollviewer для прокрутки. Для меня представленное решение выглядит очень похоже на взлом, но вы можете решить для себя.

Вот фрагмент кода по ссылке выше :

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);
5 голосов
/ 20 августа 2009

Я внес некоторые изменения в ответ Сэма. Обратите внимание, что я хотел прокрутить до последней строки. К сожалению, ListView sometiems только что отображал последнюю строку (даже когда над ней было, например, 100 строк), поэтому я исправил это так:

    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();
        }
    }

Приветствия

3 голосов
/ 17 октября 2008

Одним из способов решения этой проблемы является изменение ItemsPanel ListView. Панель по умолчанию - VirtualizingStackPanel, которая создает ListBoxItem только в первый раз, когда они становятся видимыми. Если в вашем списке слишком мало предметов, это не должно быть проблемой.

<ListView>
   ...
   <ListView.ItemsPanel>
      <ItemsPanelTemplate>
         <StackPanel/>
      </ItemsPanelTemplate>
   </ListView.ItemsPanel>
</ListView>
2 голосов
/ 13 ноября 2012

Если вы просто хотите показать и сфокусировать последний элемент после создания нового элемента данных, этот метод может быть лучше. По сравнению с ScrollIntoView, ScrollToEnd из ScrollViewer в моих тестах более надежен. В некоторых тестах с использованием метода ScrollIntoView ListView, как указано выше, не удалось, и я не знаю причину. Но использование ScrollViewer для прокрутки до последнего может работать.

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();
}
2 голосов
/ 10 апреля 2010

Попробуйте это

private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ScrollViewer scrollViewer = GetScrollViewer(lstVw) as ScrollViewer;
            scrollViewer.ScrollToHorizontalOffset(dataRowToFocus.RowIndex);
            if (dataRowToFocus.RowIndex < 2)
                lstVw.ScrollIntoView((Entity)lstVw.Items[0]);
            else
                lstVw.ScrollIntoView(e.AddedItems[0]);
        } 

 public static DependencyObject GetScrollViewer(DependencyObject o)
        {
            if (o is ScrollViewer)
            { return o; }

            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
            {
                var child = VisualTreeHelper.GetChild(o, i);

                var result = GetScrollViewer(child);
                if (result == null)
                {
                    continue;
                }
                else
                {
                    return result;
                }
            }
            return null;
        } 

private void Focus()
{
 lstVw.SelectedIndex = dataRowToFocus.RowIndex;
 lstVw.SelectedItem = (Entity)dataRowToFocus.Row;

 ListViewItem lvi = (ListViewItem)lstVw.ItemContainerGenerator.ContainerFromItem(lstVw.SelectedItem);
ContentPresenter contentPresenter = FindVisualChild<ContentPresenter>(lvi);
contentPresenter.Focus();
contentPresenter.BringIntoView();

}
2 голосов
/ 26 марта 2009

Спасибо за последний совет, Сэм. У меня был диалог, который открывался, то есть моя сетка теряла фокус каждый раз, когда диалог закрывался. Я использую это:

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();
}
1 голос
/ 07 декабря 2011

У меня только что была та же проблема с ItemContainerGenerator.ContainerFromItem () и ItemContainerGenerator.ContainerFromIndex (), возвращая ноль для элементов, которые явно присутствовали в списке. Декартель был прав, но мне пришлось немного покопаться, чтобы понять, что именно он имел в виду. Вот разбивка, чтобы спасти следующего парня / девчонку.

Короче говоря, ListBoxItems уничтожаются, если они не видны. Следовательно, ContainerFromItem () и ContainerFromIndex () возвращают null, поскольку ListBoxItems не существует. Это, очевидно, функция экономии памяти / производительности, подробно описанная здесь: http://blogs.msdn.com/b/oren/archive/2010/11/08/wp7-silverlight-perf-demo-1-virtualizingstackpanel-vs-stackpanel-as-a-listbox-itemspanel.aspx

Пустой код <ListBox.ItemsPanel> - это то, что отключает виртуализацию. Пример кода, который исправил проблему для меня:

Шаблон данных:

<phone:PhoneApplicationPage.Resources>
    <DataTemplate x:Key="StoryViewModelTemplate">
        <StackPanel>
          <your datatemplated stuff here/>
        </StackPanel>
    </DataTemplate>
</phone:PhoneApplicationPage.Resources>

Основной корпус:

<Grid x:Name="ContentPanel">
    <ListBox Name="lbResults" ItemsSource="{Binding SearchResults}" ItemTemplate="{StaticResource StoryViewModelTemplate}">
        <ListBox.ItemsPanel>
             <ItemsPanelTemplate>
                 <StackPanel>
                 </StackPanel>
             </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
</Grid>
0 голосов
/ 16 марта 2015

не уверен, если это так, но в настоящее время это работает для меня, используя WPF, MVVM Light и .NET 3.5

Я добавил событие SelectionChanged для ListBox, которое называется " lbPossibleError_SelectionChanged " I added the SelectionChanged event for ListBox

затем за этим событием " lbPossibleError_SelectionChanged " вот код enter image description here

работает как надо для меня.

0 голосов
/ 21 января 2015

В моем проекте мне нужно отобразить выбранную индексную строку из списка для просмотра пользователю, поэтому я назначил выбранный элемент элементу управления ListView. Этот код будет прокручивать полосу прокрутки и отображать выбранный элемент.

BooleanListView.ScrollIntoView (BooleanListView.SelectedItem);

OR

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

...