2008-10-17 113 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

的作品,因爲它應該給我。