WPF_ Scroll WPF Listview to specific line

来源:互联网 发布:linux mint 字体 编辑:程序博客网 时间:2024/06/03 15:02

Scroll WPF Listview to specific line

up vote 4 down vote favorite
3

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:

  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.

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?

link|flag

 
 
I've used both ListBox and DataGrid's ScrollIntoView() and they do not exhibit this problem? It's a silly question, but are you running against 3.5 SP1? Alot of things got fixed there. – Bob King Oct 17 '08 at 13:07
Yep, I run against 3.5SP1, and found this not to be a bug. The ListViewItem is virtualized, which is ok, but how do I scroll it into view then? – Sam Oct 17 '08 at 13:21

6 Answers

active newest votes
up vote 2 down vote accepted

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.

So in this case you probably get null for the item, do you? (According to your comment, you do)

I have found a link on MSDN forums that suggest accessing the Scrollviewer directly in order to scroll. To me the solution presented there looks very much like a hack, but you can decide for yourself.

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; 
double offset = scrollHeight * itemIndex_ / _listView.Items.Count; // itemIndex_ is index of the item which we want to show in the middle of the view 
 
vsp
.SetVerticalOffset(offset); 

 

link|flag
Tried the code, it works. Not nice, but an answer. I'd like to clean up my question to reflect the real problem, if you want to include the code (links might change) I'd mark you as answer. – Sam Oct 17 '08 at 13:30
Since it works I marked you as answer anyway - but I still think it would be nice if the code is copied here, in case the link changes. – Sam Oct 17 '08 at 13:44
up vote 17 down vote

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:

link|flag
ScrollIntoView works - thx – Jeffrey Jul 18 '09 at 18:52
The only problem with this for me is that it callously overrides any selection the user might have made already. Still, should be an easy enough modification to make, so +1 for you, sir, thank you. – metao Jun 1 '10 at 6:43
Oh, yes - for me, changing the selection was exactly what I wanted, but you are right. – Sam Jun 1 '10 at 9:28
up vote 3 down vote

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:

    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

link|flag
 
  
up vote 1 down vote

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> 
   ... 
   
<ListView.ItemsPanel> 
     
<ItemsPanelTemplate> 
         
<StackPanel/> 
     
</ItemsPanelTemplate> 
   
</ListView.ItemsPanel> 
</ListView> 
link|flag
 
  
up vote 1 down vote

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(); 
} 
link|flag
 
  
up vote 0 down vote

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(lvi); contentPresenter.Focus(); contentPresenter.BringIntoView();

} `

原创粉丝点击