WPF_ Scroll WPF Listview to specific line

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

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

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

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
 
  

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
 
  

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
 
  

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

} `

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值