将 WPF Listview 滚动到特定行
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/211971/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Scroll WPF Listview to specific line
提问by Sam
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:
WPF,类似浏览器的应用程序。
我有一个包含 ListView 的页面。调用 PageFunction 后,我将一行添加到 ListView,并希望将新行滚动到视图中:
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.
问题是,当线条不可见时,事情就不起作用了。
如果该行不可见,则生成的行没有 ListViewItem,因此 ItemContainerGenerator.ContainerFromIndex 返回 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?
但是没有该项目,我如何将行滚动到视图中?有没有办法在不需要 ListViewItem 的情况下滚动到最后一行(或任何地方)?
采纳答案by EFrank
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.
我认为这里的问题是如果该行不可见,则尚未创建 ListViewItem。WPF 按需创建 Visible。
So in this case you probably get null
for the item, do you?
(According to your comment, you do)
因此,在这种情况下,您可能会null
购买该物品,对吗?(根据你的评论,你这样做)
I have found a link on MSDN forums that suggest accessing the Scrollviewer directlyin order to scroll. To me the solution presented there looks very much like a hack, but you can decide for yourself.
我在 MSDN 论坛上找到了一个链接,建议直接访问 Scrollviewer以进行滚动。对我来说,那里提出的解决方案看起来非常像一个黑客,但你可以自己决定。
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);
回答by Sam
Someone told me an even better way to scroll to a specific line, which is easy and works like charm.
In short:
有人告诉我一种更好的滚动到特定行的方法,这很容易,而且很有魅力。
简而言之:
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();
}
The longer version in MSDN forums:
MSDN 论坛中的较长版本:
回答by Joseph jun. Melettukunnel
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:
我对山姆的回答做了一些改动。请注意,我想滚动到最后一行。不幸的是,ListView 有时只显示最后一行(即使上面有 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();
}
}
Cheers
干杯
回答by decasteljau
One workaround to this is to change the ItemsPanel of the ListView. The default panel is the VirtualizingStackPanel which only creates the ListBoxItem the first time they become visible. If you don't have too many items in your list, it should not be a problem.
一种解决方法是更改 ListView 的 ItemsPanel。默认面板是 VirtualizingStackPanel,它仅在 ListBoxItem 第一次可见时创建。如果您的列表中没有太多项目,那应该不是问题。
<ListView>
...
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
回答by Echilon
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();
}
回答by Welson Zhong
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.
如果您只想在创建新数据项后显示并聚焦最后一项,这种方法可能更好。与 ScrollIntoView 相比,ScrollViewer 的 ScrollToEnd 在我的测试中更可靠。在某些使用 ListView 的 ScrollIntoView 方法的测试中,如上所示失败了,我不知道原因。但是使用 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();
}
回答by Murali.S
Try this
尝试这个
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();
}
回答by Ray Ackley
I just had the same issue with ItemContainerGenerator.ContainerFromItem() and ItemContainerGenerator.ContainerFromIndex() returning null for items that clearly existed in the listbox. Decasteljau was right but I had to do some digging to figure out exactly what he meant. Here is the breakdown to save the next guy/gal some legwork.
我只是遇到了同样的问题,ItemContainerGenerator.ContainerFromItem() 和 ItemContainerGenerator.ContainerFromIndex() 为列表框中明显存在的项目返回 null。Decasteljau 是对的,但我不得不做一些挖掘才能弄清楚他的意思。这是为下一个男人/女孩节省一些跑腿工作的细分。
Long story short, ListBoxItems are destroyed if they are not within view. Consequently ContainerFromItem() and ContainerFromIndex() return null since the ListBoxItems do not exist. This is apparently a memory/performance saving feature detailed here: http://blogs.msdn.com/b/oren/archive/2010/11/08/wp7-silverlight-perf-demo-1-virtualizingstackpanel-vs-stackpanel-as-a-listbox-itemspanel.aspx
长话短说,如果 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
The empty <ListBox.ItemsPanel>
code is what turns off the virtualization. Sample code that fixed the issue for me:
空<ListBox.ItemsPanel>
代码是关闭虚拟化的原因。为我解决问题的示例代码:
Data template:
数据模板:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="StoryViewModelTemplate">
<StackPanel>
<your datatemplated stuff here/>
</StackPanel>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
Main body:
主体:
<Grid x:Name="ContentPanel">
<ListBox Name="lbResults" ItemsSource="{Binding SearchResults}" ItemTemplate="{StaticResource StoryViewModelTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel>
</StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
回答by ygoe
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:
为了克服虚拟化问题但仍然使用ScrollIntoView
而不是在 ListView 的内部乱搞,您还可以使用您的 ViewModel 对象来确定选择了什么。假设您的列表中有具有IsSelected
属性的ViewModel 对象。您可以像这样将项目链接到 XAML 中的 ListView:
<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
).
如果所选项目不在视野范围内,这也适用。在我的实验中,该PersonsListView.SelectedItem
属性为null
,但当然您的 ViewModelIsSelected
属性始终存在。确保在所有绑定和加载完成后调用此方法(使用正确的DispatcherPriority
)。
Using the ViewCommandpattern, your ViewModel code could look like this:
使用ViewCommand模式,您的 ViewModel 代码可能如下所示:
PersonVMs.ForEach(vm => vm.IsSelected = false);
PersonVMs.Add(newPersonVM);
newPersonVM.IsSelected = true;
ViewCommandManager.InvokeLoaded("ScrollToSelectedPerson");
回答by Jeyavel
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.
在我的项目中,我需要向用户显示从列表视图中选择的索引行,因此我将所选项目分配给了 ListView 控件。此代码将滚动滚动条并显示所选项目。
BooleanListView.ScrollIntoView(BooleanListView.SelectedItem);
BooleanListView.ScrollIntoView(BooleanListView.SelectedItem);
OR
或者
var listView = BooleanListView; listView.SelectedItem = listView.Items.GetItemAt(BooleanListView.SelectedIndex); listView.ScrollIntoView(listView.Items[0]); listView.ScrollIntoView(listView.SelectedItem);
var listView = BooleanListView; listView.SelectedItem = listView.Items.GetItemAt(BooleanListView.SelectedIndex); listView.ScrollIntoView(listView.Items[0]); listView.ScrollIntoView(listView.SelectedItem);