可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have got a big ListBox with vertical scrolling enabled, my MVVM has New and Edit ICommands.
I am adding new item to the end of the collection but I want the scrollbar also to auto position to the End when I call my MVVM-AddCommand.
I am also making an item editable(By calling EditCommand with a particular row item) from some other part of the application so that my ListBoxItem getting in to edit mode using DataTrigger, but how will I bring that particular row(ListBoxItem) to the view by adjusting the scroll position.
If I am doing it in the View side I can call listBox.ScrollInToView(lstBoxItem).
But what is the best way to solve this common Scroll issue from an MVVM perspective.
回答1:
I typically set IsSynchronizedWithCurrentItem="True"
on the ListBox
. Then I add a SelectionChanged
handler and always bring the selected item into view, with code like this:
private void BringSelectionIntoView(object sender, SelectionChangedEventArgs e)
{
Selector selector = sender as Selector;
if (selector is ListBox)
{
(selector as ListBox).ScrollIntoView(selector.SelectedItem);
}
}
From my VM I can get the default collection view and use one of the MoveCurrent*()
methods to ensure that the item being edited is the current item.
CollectionViewSource.GetDefaultView(_myCollection).MoveCurrentTo(thisItem);
NOTE: Edited to use ListBox.ScrollIntoView()
to accomodate virtualization
回答2:
Using this in MVVM can be easily accomplished via an attached behavior like so:
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace Jarloo.Sojurn.Behaviors
{
public sealed class ScrollIntoViewBehavior : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += ScrollIntoView;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= ScrollIntoView;
base.OnDetaching();
}
private void ScrollIntoView(object o, SelectionChangedEventArgs e)
{
ListBox b = (ListBox) o;
if (b == null)
return;
if (b.SelectedItem == null)
return;
ListBoxItem item = (ListBoxItem) ((ListBox) o).ItemContainerGenerator.ContainerFromItem(((ListBox) o).SelectedItem);
if (item != null) item.BringIntoView();
}
}
}
Then in the View ad this reference at the top:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And do this:
<ListBox ItemsSource="{Binding MyData}" SelectedItem="{Binding MySelectedItem}">
<i:Interaction.Behaviors>
<behaviors:ScrollIntoViewBehavior />
</i:Interaction.Behaviors>
</ListBox>
Now when the SelectedItem changes the behavior will do the BringIntoView() call for you.
回答3:
This is the attached property form of the accepted answer:
using System.Windows;
using System.Windows.Controls;
namespace CommonBehaviors
{
public static class ScrollCurrentItemIntoViewBehavior
{
public static readonly DependencyProperty AutoScrollToCurrentItemProperty =
DependencyProperty.RegisterAttached("AutoScrollToCurrentItem",
typeof(bool), typeof(ScrollCurrentItemIntoViewBehavior),
new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));
public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToCurrentItemProperty);
}
public static void OnAutoScrollToCurrentItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var listBox = obj as ListBox;
if (listBox == null) return;
var newValue = (bool)e.NewValue;
if (newValue)
listBox.SelectionChanged += listBoxSelectionChanged;
else
listBox.SelectionChanged -= listBoxSelectionChanged;
}
static void listBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
if (listBox == null || listBox.SelectedItem == null || listBox.Items == null) return;
listBox.Items.MoveCurrentTo(listBox.SelectedItem);
listBox.ScrollIntoView(listBox.SelectedItem);
}
public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToCurrentItemProperty, value);
}
}
}
Usage:
<ListBox ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
behaviors:ScrollCurrentItemIntoViewBehavior.AutoScrollToCurrentItem="True">
回答4:
If the above code doesn't work for you, give this a try
public class ListBoxExtenders : DependencyObject
{
public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached("AutoScrollToCurrentItem", typeof(bool), typeof(ListBoxExtenders), new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));
public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToSelectedItemProperty);
}
public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToSelectedItemProperty, value);
}
public static void OnAutoScrollToCurrentItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
var listBox = s as ListBox;
if (listBox != null)
{
var listBoxItems = listBox.Items;
if (listBoxItems != null)
{
var newValue = (bool)e.NewValue;
var autoScrollToCurrentItemWorker = new EventHandler((s1, e2) => OnAutoScrollToCurrentItem(listBox, listBox.Items.CurrentPosition));
if (newValue)
listBoxItems.CurrentChanged += autoScrollToCurrentItemWorker;
else
listBoxItems.CurrentChanged -= autoScrollToCurrentItemWorker;
}
}
}
public static void OnAutoScrollToCurrentItem(ListBox listBox, int index)
{
if (listBox != null && listBox.Items != null && listBox.Items.Count > index && index >= 0)
listBox.ScrollIntoView(listBox.Items[index]);
}
}
Usage in XAML
<ListBox IsSynchronizedWithCurrentItem="True" extenders:ListBoxExtenders.AutoScrollToCurrentItem="True" ..../>