2008-10-17 114 views
22

WPF,类似浏览器的应用程序。
我得到了一个包含ListView的页面。在调用PageFunction之后,我向ListView中添加一行,并且想要将新行滚动到视图中:将WPF Listview滚动到特定行

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

This works。只要新线路在视线中,线路就像它应该获得焦点一样。

问题是,当行不可见时,事情不起作用。
如果该行不可见,则生成的行没有ListViewItem,因此ItemContainerGenerator.ContainerFromIndex返回null。

但没有该项目,我该如何将视图滚动到视图中?有什么办法可以滚动到最后一行(或任何地方)而不需要ListViewItem?

+0

我已经使用ListBox和DataGrid的ScrollIntoView(),他们不会出现这个问题?这是一个愚蠢的问题,但你是否在3.5 SP1上运行?很多东西都在那里修好了。 – 2008-10-17 13:07:38

+0

是的,我对3.5SP1跑,发现这不是一个错误。 ListViewItem是虚拟化的,没关系,但是如何将它滚动到视图中呢? – Sam 2008-10-17 13:21:33

回答

10

我认为这里的问题是ListViewItem还没有创建,如果行不可见。 WPF按需创建可见。

所以在这种情况下,您可能会得到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); 
+0

尝试了代码,它的工作原理。不好,但一个答案。我想清理我的问题以反映真正的问题,如果您想包含代码(链接可能会更改),我会将您标记为答案。 – Sam 2008-10-17 13:30:52

+0

因为它的工作原理,我将你标记为答案 - 但我仍然认为如果在这里复制代码,万一链接发生变化,它会很好。 – Sam 2008-10-17 13:44:12

3

一个解决办法来,这是改变的ListView的ItemsPanel。默认面板是VirtualizingStackPanel,它只在第一次变为可见时创建ListBoxItem。如果您的列表中没有太多项目,则不会有问题。

<ListView> 
    ... 
    <ListView.ItemsPanel> 
     <ItemsPanelTemplate> 
     <StackPanel/> 
     </ItemsPanelTemplate> 
    </ListView.ItemsPanel> 
</ListView> 
38

有人告诉我一个更好的方法来滚动到一个特定的行,这很容易和像魅力一样工作。
总之:

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

+0

ScrollIntoView works - thx – Jeffrey 2009-07-18 18:52:51

+0

这对我来说唯一的问题是,它无情地覆盖了用户可能已经做出的任何选择。尽管如此,应该做一个简单的修改,以便为你+1,先生,谢谢。 – metao 2010-06-01 06:43:04

2

感谢您的最后一个提示山姆。我打开了一个对话框,这意味着每次关闭对话框时,我的网格都会失去焦点。我用这个:

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

我对Sam的答案做了一些修改。请注意,我想滚动到最后一行。不幸的是,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(); 
     } 
    } 

干杯

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

} 
1

我对ItemContainerGenerator.ContainerFromItem()和ItemContainerGenerator.ContainerFromIndex()有相同的问题,对于在列表框中清楚存在的项目返回null。 Decasteljau是对的,但我必须做一些挖掘才能弄清楚他的意思。下面的故事是为了挽救下一个家伙/加仑的一些工作。

长话短说,如果ListBoxItems不在视图内,它将被销毁。因此,由于ListBoxItems不存在,ContainerFromItem()和ContainerFromIndex()将返回null。这显然是内存/性能节省功能在这里详述: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> 
2

如果你只是想显示和创建新的数据项后集中的最后一个项目,这种方法可能会更好。与ScrollIntoView相比,ScrollViewer的ScrollToEnd在我的测试中更加可靠。 在一些使用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(); 
} 
0

为了解决虚拟化问题,但仍然使用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属性始终存在。在完成所有装订和装载之后,请务必调用此方法(使用正确的DispatcherPriority)。

使用ViewCommand模式,您的视图模型的代码看起来是这样的:

PersonVMs.ForEach(vm => vm.IsSelected = false); 
PersonVMs.Add(newPersonVM); 
newPersonVM.IsSelected = true; 
ViewCommandManager.InvokeLoaded("ScrollToSelectedPerson"); 
0

在我的项目,我需要从列表视图显示给用户所选择的指标线,所以我分配所选项目到ListView控件。该代码将滚动滚动条并显示所选项目。

BooleanListView.ScrollIntoView(BooleanListView.SelectedItem);

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

0

不知道这是否是要走的路,但目前这对我使用WPF,MVVM Light和.NET 3。5

我添加列表框称为SelectionChanged事件 “lbPossibleError_SelectionChangedI added the SelectionChanged event for ListBox

那么后面这个 “lbPossibleError_SelectionChanged” 事件,这里的代码 enter image description here

的作品,因为它应该给我。