WPF,类似浏览器的应用。
我有一个包含ListView的页面。在调用PageFunction后,我向ListView添加一行,并希望将新行滚动到视图中:
ListViewItem item = ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
if (item != null)
ScrollIntoView(item);
这很有效。只要新线在视野中,线就会得到它应该的焦点。
问题是,当线条不可见时,事情不起作用 如果该行不可见,则生成的行没有ListViewItem,因此ItemContainerGenerator.ContainerFromIndex返回null。
但如果没有该项目,如何将该行滚动到视图中?有没有办法滚动到最后一行(或任何地方)而不需要ListViewItem?
答案 0 :(得分:39)
有人告诉我一个更好的方法来滚动到一个特定的线,这很容易,就像魅力一样 简而言之:
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 forums中的较长版本:
答案 1 :(得分:10)
我认为这里的问题是如果该行不可见,则尚未创建ListViewItem。 WPF按需创建Visible。
所以在这种情况下你可能会得到null
这个项目,对吗?
(根据你的评论,你这样做)
我找到了link on MSDN forums that suggest accessing the Scrollviewer directly才能滚动。对我来说,那里提出的解决方案看起来非常像黑客,但你可以自己决定。
以下是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);
答案 2 :(得分:5)
我对Sam的回答做了一些修改。请注意,我想滚动到最后一行。不幸的是,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();
}
}
干杯
答案 3 :(得分:3)
解决此问题的一种方法是更改ListView的ItemsPanel。默认面板是VirtualizingStackPanel,它仅在第一次可见时创建ListBoxItem。如果列表中没有太多项目,那应该不是问题。
<ListView>
...
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
答案 4 :(得分:2)
感谢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();
}
答案 5 :(得分:2)
试试这个
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();
}
答案 6 :(得分:2)
如果您只想在创建新数据项后显示和聚焦最后一项,则此方法可能更好。与ScrollIntoView相比,ScrollView的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();
}
答案 7 :(得分:1)
我在ItemContainerGenerator.ContainerFromItem()和ItemContainerGenerator.ContainerFromIndex()中遇到了同样的问题,对列表框中明显存在的项目返回null。 Decasteljau是对的,但我不得不做一些挖掘来弄清楚他的意思。这是为了挽救下一个人/ gal一些腿部工作的细分。
长话短说,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>
答案 8 :(得分:0)
要克服虚拟化问题,但仍然使用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>
然后,代码隐藏方法可以滚动到第一个选定的项目:
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");
答案 9 :(得分:0)
在我的项目中,我需要将列表视图中选定的索引行显示给用户,因此我将所选项目分配给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);
答案 10 :(得分:0)
不确定这是否可行,但目前适用于我使用WPF,MVVM Light和.NET 3.5
我为ListBox添加了SelectionChanged事件,名为&#34; lbPossibleError_SelectionChanged &#34;
然后在这&#34; lbPossibleError_SelectionChanged &#34;事件,这里是代码
应该为我工作。