[ACCEPTED]-Scroll WPF Listview to specific line-scroll
Someone told me an even better way to scroll 3 to a specific line, which is easy and works 2 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 1 in MSDN forums:
I think the problem here is that the ListViewItem 9 is not created yet if the line is not visible. WPF 8 creates the Visible on demand.
So in this 7 case you probably get null
for the item, do 6 you?
(According to your comment, you do)
I 5 have found a link on MSDN forums that suggest accessing the Scrollviewer directly in order to scroll. To me 4 the solution presented there looks very 3 much like a hack, but you can decide for 2 yourself.
Here is the code snippet from the 1 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);
I made some changes to Sam's answer. Note 5 that I wanted to scroll to the last line. Unfortunately 4 the ListView sometiems just displayed the 3 last line (even when there were e.g. 100 2 lines above it), so this is how I fixed 1 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
One workaround to this is to change the 6 ItemsPanel of the ListView. The default 5 panel is the VirtualizingStackPanel which 4 only creates the ListBoxItem the first time 3 they become visible. If you don't have 2 too many items in your list, it should not 1 be a problem.
<ListView>
...
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
Thanks for that last tip Sam. I had a dialog 2 which opened, meaning my grid lost focus 1 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();
}
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<ContentPresenter>(lvi);
contentPresenter.Focus();
contentPresenter.BringIntoView();
}
0
If you just want to show and focus the last 7 item after creating a new data item, this 6 method is maybe better. Compare to ScrollIntoView, ScrollToEnd 5 of ScrollViewer is in my tests more reliable. In 4 some tests using ScrollIntoView method of 3 ListView like above failed and i don't know 2 reason. But using ScrollViewer to scroll 1 to last one can work.
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();
}
I just had the same issue with ItemContainerGenerator.ContainerFromItem() and 15 ItemContainerGenerator.ContainerFromIndex() returning 14 null for items that clearly existed in the 13 listbox. Decasteljau was right but I had 12 to do some digging to figure out exactly 11 what he meant. Here is the breakdown to 10 save the next guy/gal some legwork.
Long 9 story short, ListBoxItems are destroyed 8 if they are not within view. Consequently 7 ContainerFromItem() and ContainerFromIndex() return 6 null since the ListBoxItems do not exist. This 5 is apparently a memory/performance saving 4 feature detailed here: http://blogs.msdn.com/b/oren/archive/2010/11/08/wp7-silverlight-perf-demo-1-virtualizingstackpanel-vs-stackpanel-as-a-listbox-itemspanel.aspx
The empty <ListBox.ItemsPanel>
code 3 is what turns off the virtualization. Sample 2 code that fixed the issue for me:
Data template:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="StoryViewModelTemplate">
<StackPanel>
<your datatemplated stuff here/>
</StackPanel>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
Main 1 body:
<Grid x:Name="ContentPanel">
<ListBox Name="lbResults" ItemsSource="{Binding SearchResults}" ItemTemplate="{StaticResource StoryViewModelTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel>
</StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
To overcome the virtualisation issue but 15 still use ScrollIntoView
and not hacking around in the 14 guts of the ListView, you could also use 13 your ViewModel objects to determine what 12 is selected. Assuming that you have ViewModel 11 objects in your list that feature an IsSelected
property. You'd 10 link the items to the ListView in XAML like 9 this:
<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>
Then, the code-behind method can scroll 8 to the first selected item with this:
var firstSelected = PersonsListView.Items
.OfType<TreeViewItemViewModel>().FirstOrDefault(x => x.IsSelected);
if (firstSelected != null)
CoObjectsListView.ScrollIntoView(firstSelected);
This 7 also works if the selected item is well 6 out of view. In my experiment, the PersonsListView.SelectedItem
property 5 was null
, but of course your ViewModel IsSelected
property 4 is always there. Be sure to call this method 3 after all binding and loading has completed 2 (with the right DispatcherPriority
).
Using the ViewCommand pattern, your 1 ViewModel code could look like this:
PersonVMs.ForEach(vm => vm.IsSelected = false);
PersonVMs.Add(newPersonVM);
newPersonVM.IsSelected = true;
ViewCommandManager.InvokeLoaded("ScrollToSelectedPerson");
In my project i need to display the selected 6 index line from the listview to the user 5 so i assigned the selected item to ListView 4 control. This code will scroll the scrollbar 3 and display the selected item.
BooleanListView.ScrollIntoView(BooleanListView.SelectedItem);
OR
var 2 listView = BooleanListView; listView.SelectedItem 1 = listView.Items.GetItemAt(BooleanListView.SelectedIndex); listView.ScrollIntoView(listView.Items[0]); listView.ScrollIntoView(listView.SelectedItem);
not sure if this is the way to go but this 4 currently works for me using WPF, MVVM Light, and 3 .NET 3.5
I added the SelectionChanged event 2 for ListBox called "lbPossibleError_SelectionChanged"
then behind this "lbPossibleError_SelectionChanged" event, here's 1 the code
works as it should for me.
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.