Прокрутите список в WPF в определенную строку
WPF, браузерное приложение.
У меня есть одна страница, содержащая ListView. После вызова функции PageFunction я добавляю строку в ListView и хочу прокручивать новую строку в виде:
ListViewItem item = ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
if (item != null)
ScrollIntoView(item);
Это работает. Пока новая строка находится в поле зрения, линия получает фокус так, как должна.
Проблема в том, что вещи не работают, когда линия не видна.
Если строка не отображается, для создаваемой строки не существует ListViewItem, поэтому ItemContainerGenerator.ContainerFromIndex возвращает null.
Но без элемента, как прокручивать строку в виде? Есть ли способ прокрутить до последней строки (или где угодно), не требуя ListViewItem?
Ответы
Ответ 1
Я думаю, проблема здесь в том, что ListViewItem еще не создан, если строка не видна. WPF создает видимый по требованию.
Итак, в этом случае вы, вероятно, получаете 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);
Ответ 2
Кто-то сказал мне еще лучший способ прокрутить к определенной строке, что легко и работает как шарм.
Короче говоря:
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:
Ответ 3
Я внесла некоторые изменения в ответ Сэма. Обратите внимание, что мне нужно прокрутить до последней строки. К сожалению, 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();
}
}
Приветствия
Ответ 4
Одним из способов решения этой проблемы является изменение ItemsPanel в ListView. Панель по умолчанию - VirtualizingStackPanel, которая только создает ListBoxItem в первый раз, когда они становятся видимыми. Если у вас слишком много элементов в списке, это не должно быть проблемой.
<ListView>
...
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
Ответ 5
Спасибо за последний отзыв Sam. У меня появилось диалоговое окно, в результате моя сетка потеряла фокус каждый раз, когда диалог закрылся. Я использую это:
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();
}
Ответ 6
Попробуйте это
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();
}
Ответ 7
Если вы просто хотите показать и сфокусировать последний элемент после создания нового элемента данных, этот метод может быть лучше. Сравнение с 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();
}
Ответ 8
У меня была одна и та же проблема с ItemContainerGenerator.ContainerFromItem() и ItemContainerGenerator.ContainerFromIndex(), возвращающая значение null для элементов, которые явно существовали в списке. Декастеля был прав, но мне нужно было кое-что сделать, чтобы понять, что он имел в виду. Вот разбивка, чтобы спасти следующего парня/галлу некоторую работу.
Короче говоря, 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>
Ответ 9
Чтобы преодолеть проблему виртуализации, но по-прежнему использовать ScrollIntoView
, а не взламывать кишки ListView, вы также можете использовать объекты ViewModel для определения того, что выбрано. Предполагая, что у вас есть объекты ViewModel в вашем списке, которые имеют свойство IsSelected
. Вы бы связали элементы с ListView в XAML следующим образом:
<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>
Затем метод code-behind может прокручиваться до первого выбранного элемента следующим образом:
var firstSelected = PersonsListView.Items
.OfType<TreeViewItemViewModel>().FirstOrDefault(x => x.IsSelected);
if (firstSelected != null)
CoObjectsListView.ScrollIntoView(firstSelected);
Это также работает, если выбранный элемент находится вне поля зрения. В моем эксперименте свойство PersonsListView.SelectedItem
было null
, но, конечно, свойство ViewModel IsSelected
всегда присутствует. Не забудьте вызвать этот метод после завершения привязки и загрузки (с правом DispatcherPriority
).
Используя шаблон ViewCommand, ваш код ViewModel может выглядеть следующим образом:
PersonVMs.ForEach(vm => vm.IsSelected = false);
PersonVMs.Add(newPersonVM);
newPersonVM.IsSelected = true;
ViewCommandManager.InvokeLoaded("ScrollToSelectedPerson");
Ответ 10
В моем проекте мне нужно отобразить выбранную строку индекса из списка для пользователя, чтобы я назначил выбранный элемент элементу управления ListView. Этот код будет прокручивать полосу прокрутки и отображать выбранный элемент.
BooleanListView.ScrollIntoView(BooleanListView.SelectedItem);
ИЛИ
var listView = BooleanListView; listView.SelectedItem = listView.Items.GetItemAt(BooleanListView.SelectedIndex); listView.ScrollIntoView(listView.Items [0]); listView.ScrollIntoView(listView.SelectedItem);
Ответ 11
не уверен, что это путь, но в настоящее время это работает для меня с использованием WPF, MVVM Light и .NET 3.5
Я добавил событие SelectionChanged для ListBox, называемое lbPossibleError_SelectionChanged
![I added the SelectionChanged event for ListBox]()
то за этим " lbPossibleError_SelectionChanged", здесь код
![enter image description here]()
работает так, как должно для меня.