WPF, браузерное приложение.
У меня есть одна страница, содержащая ListView. После вызова PageFunction я добавляю строку в ListView и хочу прокрутить новую строку в поле зрения:
ListViewItem item = ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
if (item != null)
ScrollIntoView(item);
Это работает. Пока новая строка находится в поле зрения, линия получает фокус, как и должен.
Проблема в том, что ничего не работает, когда линия не видна. Если строка не отображается, для созданной строки нет ListViewItem, поэтому ItemContainerGenerator.ContainerFromIndex возвращает значение null.
Но без элемента, как мне прокрутить строку в поле зрения? Есть ли способ перейти к последней строке (или где угодно) без ListViewItem?
Да, я использовал 3.5SP1 и обнаружил, что это не ошибка. ListViewItem виртуализирован, и это нормально, но как мне тогда прокрутить его для просмотра?





Я думаю, проблема здесь в том, что 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);
Код пробовал, работает. Неприятно, но ответ. Я хотел бы очистить свой вопрос, чтобы отразить реальную проблему, если вы хотите включить код (ссылки могут измениться), я отмечу вас как ответ.
Поскольку это работает, я все равно пометил вас как ответ, но я все же думаю, что было бы неплохо, если бы код был скопирован сюда, на случай, если ссылка изменится.
Одним из способов решения этой проблемы является изменение ItemsPanel ListView. Панель по умолчанию - VirtualizingStackPanel, которая создает ListBoxItem только тогда, когда они становятся видимыми в первый раз. Если в вашем списке не слишком много пунктов, это не должно быть проблемой.
<ListView>
...
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
Кто-то сказал мне еще лучший способ прокрутки к определенной строке, который прост и работает как шарм. Суммируя:
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:
Единственная проблема с этим для меня заключается в том, что он грубо отменяет любой выбор, который пользователь, возможно, уже сделал. Тем не менее, модификация должна быть достаточно простой, так что +1 для вас, сэр, спасибо.
Ах да, для меня изменение выделения было именно тем, чего я хотел, но вы правы.
К сожалению, это не работает, если в ListBox есть VirtualizingStackPanel.IsVirtualizing = "True" = [. Однако принят-ответ делает. Если .IsVirtualizing = "False", который установлен по умолчанию, этот ответ работает нормально.
да. Работает как Charm Indeed !. Я много боролся с моим виртуализированным представлением списка с базой данных. Поскольку у меня были включены сортировка и поиск, я действительно изо всех сил пытался найти способы, которые не будут загружать все элементы из базы данных. Мне пришлось реализовать логику, которая сообщала бы мне, по какому индексу будет мой элемент в ListView после применения фильтров и в какой строке на самом деле является элемент в базе данных. Теперь работает :). Хороший!
Спасибо за последний совет, Сэм. У меня был диалог, который открывался, а это означало, что моя сетка теряла фокус каждый раз, когда диалог закрывался. Я использую это:
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();
}
Я внес некоторые изменения в ответ Сэма. Обратите внимание, что я хотел перейти к последней строке. К сожалению, 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();
}
}
Ваше здоровье
Попробуй это
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();
}
Это идеальный ответ, так как вы можете сохранить виртуализацию и прокрутить до определенной строки, не борясь с исходным выбором - даже для списков, которые могут иметь разную высоту элементов, как у меня (расширяемые элементы). Спасибо!
Привет, что такое сущность?
У меня была такая же проблема с 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>
Обратите внимание, что ответ Рэя отключает виртуализацию!
Если вы просто хотите показать и сфокусировать последний элемент после создания нового элемента данных, этот метод, возможно, лучше. Сравните с 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();
}
извините, первый клаус должен быть: ObservableCollection <object> items = lsv.ItemsSource as ObservableCollection <object>;
Отредактируйте свой ответ, а не комментируйте его, пожалуйста. Ваше здоровье,
ScrollToEnd работает лучше, чем ScrollIntoView, который может отображать только частично видимый последний элемент. Спасибо, что поделились этим решением.
привет, Это не работает, если ListBox имеет VirtualizingStackPanel.IsVirtualizing = "True"
Чтобы преодолеть проблему виртуализации, но по-прежнему использовать 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>
Затем метод кода программной части может перейти к первому выбранному элементу следующим образом:
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");
В моем проекте мне нужно отобразить выбранную строку индекса из списка для пользователя, поэтому я назначил выбранный элемент элементу управления ListView. Этот код будет прокручивать полосу прокрутки и отображать выбранный элемент.
BooleanListView.ScrollIntoView (BooleanListView.SelectedItem);
ИЛИ ЖЕ
var listView = BooleanListView; listView.SelectedItem = listView.Items.GetItemAt (BooleanListView.SelectedIndex); listView.ScrollIntoView (listView.Items [0]); listView.ScrollIntoView (listView.SelectedItem);
не уверен, что это путь, но в настоящее время это работает для меня, используя WPF, MVVM Light и .NET 3.5
Я добавил событие SelectionChanged для ListBox под названием "lbPossibleError_SelectionChanged"

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

работает как надо для меня.
Я использовал как ListBox, так и DataGrid ScrollIntoView (), и у них нет этой проблемы? Глупый вопрос, а вы работаете с 3.5 SP1? Там много чего починили.