将WPF ListBox滚动到视图模型中代码中的SelectedItem集合
本文关键字:代码 SelectedItem 集合 模型 视图 WPF ListBox 滚动 | 更新日期: 2023-09-27 18:19:59
我有一个带有列表框的XAML视图:
<control:ListBoxScroll ItemSource="{Binding Path=FooCollection}"
SelectedItem="{Binding SelectedFoo, Mode=TwoWay}"
ScrollSelectedItem="{Binding SelectedFoo}">
<!-- data templates, etc. -->
</control:ListBoxScroll>
所选项目已绑定到我视图中的某个属性。当用户在列表框中选择一个项目时,视图模型中的SelectedFoo属性将更新。当我在视图模型中设置SelectedFoo属性时,就会在列表框中选择正确的项目。
问题是,如果代码中设置的SelectedFoo当前不可见,我需要额外调用列表框上的ScrollIntoView
。由于我的ListBox在视图中,而我的逻辑在视图模型中。。。我找不到方便的方法。所以我扩展了ListBoxScroll:
class ListBoxScroll : ListBox
{
public static readonly DependencyProperty ScrollSelectedItemProperty = DependencyProperty.Register(
"ScrollSelectedItem",
typeof(object),
typeof(ListBoxScroll),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(onScrollSelectedChanged)));
public object ScrollSelectedItem
{
get { return (object)GetValue(ScrollSelectedItemProperty); }
set { SetValue(ScrollSelectedItemProperty, value); }
}
private static void onScrollSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listbox = d as ListBoxScroll;
listbox.ScrollIntoView(e.NewValue);
}
}
它基本上公开了一个新的依赖属性ScrollSelectedItem
,我将其绑定到视图模型上的SelectedFoo
属性。然后,我挂接到依赖属性的属性更改回调,并将新选择的项滚动到视图中。
其他人知道在有视图模型支持的XAML视图上,对用户控件调用函数的更简单方法吗?这有点像是在四处奔波:
- 创建从属特性
- 向属性更改回调添加回调
- 在静态回调中处理函数调用
将逻辑正确地放在ScrollSelectedItem { set {
方法中是很好的,但依赖框架似乎会偷偷溜走,并在没有实际调用它的情况下设法工作。
您是否尝试过使用Behavior。。。这是一个ScrollInViewBehavior。我已经将它用于ListView和DataGrid。。。。。我认为它应该适用于ListBox。。。。。。
您必须添加对System.Windows.Interactivity
的引用才能使用Behavior<T> class
行为
public class ScrollIntoViewForListBox : Behavior<ListBox>
{
/// <summary>
/// When Beahvior is attached
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
/// <summary>
/// On Selection Changed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void AssociatedObject_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if (sender is ListBox)
{
ListBox listBox = (sender as ListBox);
if (listBox .SelectedItem != null)
{
listBox.Dispatcher.BeginInvoke(
(Action) (() =>
{
listBox.UpdateLayout();
if (listBox.SelectedItem !=
null)
listBox.ScrollIntoView(
listBox.SelectedItem);
}));
}
}
}
/// <summary>
/// When behavior is detached
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.SelectionChanged -=
AssociatedObject_SelectionChanged;
}
}
用法
将别名添加到XAML
作为xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
然后在您的Control
中
<ListBox ItemsSource="{Binding Path=MyList}"
SelectedItem="{Binding Path=MyItem,
Mode=TwoWay}"
SelectionMode="Single">
<i:Interaction.Behaviors>
<Behaviors:ScrollIntoViewForListBox />
</i:Interaction.Behaviors>
</ListBox>
现在当曾经";MyItem";属性设置在ViewModel
中时,重新选择更改时将滚动列表。
查看答案后,出现了一个常见的主题:外部类侦听ListBox的SelectionChanged事件。这让我意识到依赖属性的方法有些过头了,我可以让子类自己听:
class ListBoxScroll : ListBox
{
public ListBoxScroll() : base()
{
SelectionChanged += new SelectionChangedEventHandler(ListBoxScroll_SelectionChanged);
}
void ListBoxScroll_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ScrollIntoView(SelectedItem);
}
}
我觉得这是最简单的解决方案,可以满足我的需求。
值得一提的是adcool2007提出了行为学。以下是一些感兴趣的文章:
http://blogs.msdn.com/b/johngossman/archive/2008/05/07/the-attached-behavior-pattern.aspx
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx
我认为,对于将添加到几个不同用户控件中的通用行为(例如,点击行为、拖动行为、动画行为等),附加行为非常有意义。我不想在这种特殊情况下使用它们的原因是,行为的实现(调用ScrollIntoView
)不是一个可以发生在ListBox以外的任何控件上的通用操作。
因为这是一个严格意义上的视图问题,所以没有理由不能在视图的代码后面设置事件处理程序。收听ListBox.SelectionChanged
,并使用它将新选择的项目滚动到视图中。
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
((ListBox)sender).ScrollIntoView(e.AddedItems[0]);
}
您也不需要派生的ListBox
来执行此操作。只需使用一个标准控件,当ListBox.SelectedItem
值发生变化时(如您最初的问题中所述),将执行上述处理程序,并将项目滚动到视图中。
<ListBox
ItemsSource="{Binding Path=FooCollection}"
SelectedItem="{Binding Path=SelectedFoo}"
SelectionChanged="ListBox_SelectionChanged"
/>
另一种方法是编写一个附加属性,侦听ICollectionView.CurrentChanged
,然后为新的当前项调用ListBox.ScrollIntoView
。如果您需要为多个列表框提供此功能,那么这是一种更"可重用"的方法。你可以在这里找到一个很好的例子来开始:http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/
我正在使用这个(在我看来)清晰简单的解决方案
listView.SelectionChanged += (s, e) =>
listView.ScrollIntoView(listView.SelectedItem);
其中listView
是xaml中ListView
控件的名称,SelectedItem
受我的MVVM影响,代码被插入到xaml.cs文件中的构造函数中。
我知道这是一个老问题,但我最近对同一问题的搜索让我想到了这个问题。我想使用行为方法,但不想仅仅为了给我Behavior<T>
而依赖Blend SDK,所以这里是我没有它的解决方案:
public static class ListBoxBehavior
{
public static bool GetScrollSelectedIntoView(ListBox listBox)
{
return (bool)listBox.GetValue(ScrollSelectedIntoViewProperty);
}
public static void SetScrollSelectedIntoView(ListBox listBox, bool value)
{
listBox.SetValue(ScrollSelectedIntoViewProperty, value);
}
public static readonly DependencyProperty ScrollSelectedIntoViewProperty =
DependencyProperty.RegisterAttached("ScrollSelectedIntoView", typeof (bool), typeof (ListBoxBehavior),
new UIPropertyMetadata(false, OnScrollSelectedIntoViewChanged));
private static void OnScrollSelectedIntoViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as Selector;
if (selector == null) return;
if (e.NewValue is bool == false)
return;
if ((bool) e.NewValue)
{
selector.AddHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler));
}
else
{
selector.RemoveHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler));
}
}
private static void ListBoxSelectionChangedHandler(object sender, RoutedEventArgs e)
{
if (!(sender is ListBox)) return;
var listBox = (sender as ListBox);
if (listBox.SelectedItem != null)
{
listBox.Dispatcher.BeginInvoke(
(Action)(() =>
{
listBox.UpdateLayout();
if (listBox.SelectedItem !=null)
listBox.ScrollIntoView(listBox.SelectedItem);
}));
}
}
}
然后使用就是
<ListBox ItemsSource="{Binding Path=MyList}"
SelectedItem="{Binding Path=MyItem, Mode=TwoWay}"
SelectionMode="Single"
behaviors:ListBoxBehavior.ScrollSelectedIntoView="True">
试试这个:
private void lstBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lstBox.ScrollIntoView(lstBox.SelectedItem);
}
在结合各种方法后,我发现以下是最简单、最好的
lstbox.Items.MoveCurrentToLast();
lstbox.ScrollIntoView(lstbox.Items.CurrentItem);
我接受了Ankesh的答案,并使其不依赖于混合sdk。我的解决方案的缺点是,它将适用于应用程序中的所有列表框。但好处是不需要自定义类。
当你的应用程序正在初始化时。。。
internal static void RegisterFrameworkExtensionEvents()
{
EventManager.RegisterClassHandler(typeof(ListBox), ListBox.SelectionChangedEvent, new RoutedEventHandler(ScrollToSelectedItem));
}
//avoid "async void" unless used in event handlers (or logical equivalent)
private static async void ScrollToSelectedItem(object sender, RoutedEventArgs e)
{
if (sender is ListBox)
{
var lb = sender as ListBox;
if (lb.SelectedItem != null)
{
await lb.Dispatcher.BeginInvoke((Action)delegate
{
lb.UpdateLayout();
if (lb.SelectedItem != null)
lb.ScrollIntoView(lb.SelectedItem);
});
}
}
}
这会使您的所有列表框滚动到选定的(我喜欢这是一种默认行为)。