我有一个启用垂直滚动的大列表框,我的 MVVM 有新建和编辑 ICommand。 我正在将新项目添加到集合的末尾,但我希望当我调用 MVVM-AddCommand 时滚动条也自动定位到末尾。 我还从应用程序的其他部分使项目可编辑(通过使用特定行项目调用 EditCommand),以便我的 ListBoxItem 使用 DataTrigger 进入编辑模式,但是我将如何将该特定行(ListBoxItem)带到视图通过调整滚动位置。
如果我在视图端执行此操作,我可以调用 listBox.ScrollInToView(lstBoxItem)。 但从 MVVM 角度来看,解决这个常见滚动问题的最佳方法是什么?
我通常将
IsSynchronizedWithCurrentItem="True"
设置为 ListBox
。 然后我添加一个 SelectionChanged
处理程序并始终将所选项目带入视图,代码如下:
private void BringSelectionIntoView(object sender, SelectionChangedEventArgs e)
{
Selector selector = sender as Selector;
if (selector is ListBox)
{
(selector as ListBox).ScrollIntoView(selector.SelectedItem);
}
}
从我的虚拟机中,我可以获取默认集合视图并使用
MoveCurrent*()
方法之一来确保正在编辑的项目是当前项目。
CollectionViewSource.GetDefaultView(_myCollection).MoveCurrentTo(thisItem);
注意:编辑为使用
ListBox.ScrollIntoView()
来适应虚拟化
在 MVVM 中使用它可以通过附加行为轻松完成,如下所示:
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();
}
}
}
然后在查看广告顶部引用此内容:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
然后这样做:
<ListBox ItemsSource="{Binding MyData}" SelectedItem="{Binding MySelectedItem}">
<i:Interaction.Behaviors>
<behaviors:ScrollIntoViewBehavior />
</i:Interaction.Behaviors>
</ListBox>
现在,当 SelectedItem 更改时,行为将为您执行 BringIntoView() 调用。
这是已接受答案的附加属性形式:
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);
}
}
}
用途:
<ListBox ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
behaviors:ScrollCurrentItemIntoViewBehavior.AutoScrollToCurrentItem="True">
如果上面的代码不适合您,请尝试一下
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]);
}
}
XAML 中的用法
<ListBox IsSynchronizedWithCurrentItem="True" extenders:ListBoxExtenders.AutoScrollToCurrentItem="True" ..../>
下面是我对 @VahidN 建议的解决方案的 ListView 改编。我希望这可以帮助正在寻找类似解决方案的人。
namespace YourSolutionName.YourProjectName.Utilities
{
public class ListViewExtension
{
public static readonly DependencyProperty AutoScrollToCurrentItemProperty =
DependencyProperty.RegisterAttached("AutoScrollToCurrentItem",
typeof(bool), typeof(ListViewExtension),
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 listView = (ListView)obj;
var newValue = (bool)e.NewValue;
if (listView == null) return;
if (newValue)
listView.SelectionChanged += listViewSelectionChanged;
else
listView.SelectionChanged -= listViewSelectionChanged;
}
static void listViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = (ListView)sender;
if (listView == null || listView.SelectedItem == null || listView.Items == null) return;
listView.Items.MoveCurrentTo(listView.SelectedItem);
listView.ScrollIntoView(listView.SelectedItem);
}
public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToCurrentItemProperty, value);
}
}
}
将命名空间添加到您的 Xaml 文件中:
xmlns:ut="clr-namespace:YourSolutionName.YourProjectName.Utilities"
Xaml 标记中的用法:
<ListView
x:Name="CollectioListView"
ut:ListViewExtension.AutoScrollToCurrentItem="True"
ItemsSource="{Binding YourListViewCollection}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
SelectedItem="{Binding Path=CurrentSelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
请注意,以下属性和值是可选的:
a) x:Name="CollectioListView"
b) ScrollViewer.HorizontalScrollBarVisibility="Auto"
c) ScrollViewer.VerticalScrollBarVisibility="Auto"
d) SelectedItem="{Binding Path=CurrentSelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"