C# 将 WPF ListBox 滚动到视图模型中代码中设置的 SelectedItem
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8827489/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Scroll WPF ListBox to the SelectedItem set in code in a view model
提问by James Fassett
I have a XAML view with a list box:
我有一个带有列表框的 XAML 视图:
<control:ListBoxScroll ItemSource="{Binding Path=FooCollection}"
SelectedItem="{Binding SelectedFoo, Mode=TwoWay}"
ScrollSelectedItem="{Binding SelectedFoo}">
<!-- data templates, etc. -->
</control:ListBoxScroll>
The selected item is bound to a property in my view. When the user selects an item in the list box my SelectedFoo property in the view model gets updated. When I set the SelectedFoo property in my view model then the correct item is selected in the list box.
在我看来,所选项目绑定到一个属性。当用户在列表框中选择一个项目时,视图模型中的 SelectedFoo 属性会更新。当我在视图模型中设置 SelectedFoo 属性时,会在列表框中选择正确的项目。
The problem is that if the SelectedFoo that is set in code is not currently visible I need to additionally call ScrollIntoViewon the list box. Since my ListBox is inside a view and my logic is inside my view model ... I couldn't find a convenient way to do it. So I extended ListBoxScroll:
问题是,如果在代码中设置的 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);
}
}
It basically exposes a new dependency property ScrollSelectedItemwhich I bind to the SelectedFooproperty on my view model. I then hook into the property changed callback of the dependent property and scroll the newly selected item into view.
它基本上公开了一个新的依赖属性ScrollSelectedItem,我将其绑定到SelectedFoo我的视图模型上的属性。然后我挂钩到依赖属性的属性更改回调并将新选择的项目滚动到视图中。
Does anyone else know of an easier way to call functions on user controls on a XAML view that is backed by a view model? It's a bit of a run around to:
有没有其他人知道在由视图模型支持的 XAML 视图上调用用户控件上的函数的更简单方法?这有点绕:
- create a dependent property
- add a callback to the property changed callback
- handle function invocation inside the static callback
- 创建依赖属性
- 向属性更改的回调添加回调
- 处理静态回调中的函数调用
It would be nice to put the logic right in the ScrollSelectedItem { set {method but the dependency framework seems to sneak around and manages to work without actually calling it.
将逻辑放在ScrollSelectedItem { set {方法中会很好,但依赖框架似乎偷偷摸摸并设法在没有实际调用它的情况下工作。
采纳答案by James Fassett
After reviewing the answers a common theme came up: external classes listening to the SelectionChanged event of the ListBox. That made me realize that the dependant property approach was overkill and I could just have the sub-class listen to itself:
查看答案后,出现了一个共同的主题:外部类侦听 ListBox 的 SelectionChanged 事件。这让我意识到依赖属性方法是矫枉过正的,我可以让子类自己听:
class ListBoxScroll : ListBox
{
public ListBoxScroll() : base()
{
SelectionChanged += new SelectionChangedEventHandler(ListBoxScroll_SelectionChanged);
}
void ListBoxScroll_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ScrollIntoView(SelectedItem);
}
}
I feel this is the simplest solution that does what I want.
我觉得这是做我想要的最简单的解决方案。
Honourable mention goes to adcool2007 for bringing up Behaviours. Here are a couple of articles for those interested:
值得一提的是 adcool2007 提出了行为。这里有几篇文章供感兴趣的人使用:
http://blogs.msdn.com/b/johngossman/archive/2008/05/07/the-attached-behavior-pattern.aspx
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx
http://blogs.msdn.com/b/johngossman/archive/2008/05/07/the-attached-behavior-pattern.aspx
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx
I think for generic behaviours that will be added to several different user controls (e.g. click behaviours, drag behaviours, animation behaviours, etc.) then attached behaviours make a lot of sense. The reason I don't want to use them in this particular case is that the implementation of the behaviour (calling ScrollIntoView) isn't a generic action that can happen to any control other than a ListBox.
我认为对于将添加到几个不同用户控件(例如单击行为、拖动行为、动画行为等)的通用行为,附加行为很有意义。我不想在这种特殊情况下使用它们的原因是行为(调用ScrollIntoView)的实现不是一个通用操作,它可以发生在除 ListBox 之外的任何控件上。
回答by sellmeadog
Because this is strictly a View problem, there's no reason you can't have an event handler in the code behind of your view for this purpose. Listen for ListBox.SelectionChangedand use that to scroll the newly selected item into view.
因为这严格来说是一个视图问题,所以没有理由在视图后面的代码中不能有一个用于此目的的事件处理程序。聆听ListBox.SelectionChanged并使用它来滚动新选择的项目到视图中。
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
((ListBox)sender).ScrollIntoView(e.AddedItems[0]);
}
You also don't need a derived ListBoxto do this. Just use a standard control and when the ListBox.SelectedItemvalue changes (as described in your original question), the above handler will be executed and the item will be scrolled into view.
您也不需要派生ListBox来执行此操作。只需使用标准控件,当ListBox.SelectedItem值发生变化时(如您的原始问题中所述),将执行上述处理程序并将项目滚动到视图中。
<ListBox
ItemsSource="{Binding Path=FooCollection}"
SelectedItem="{Binding Path=SelectedFoo}"
SelectionChanged="ListBox_SelectionChanged"
/>
Another approach would be to write an attached property that listens for ICollectionView.CurrentChangedand then invokes ListBox.ScrollIntoViewfor the new current item. This is a more "reusable" approach if you need this functionality for several list boxes. You can find a good example here to get you started: http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/
另一种方法是编写一个附加属性来侦听ICollectionView.CurrentChanged然后调用ListBox.ScrollIntoView新的当前项。如果您需要为多个列表框提供此功能,这是一种更“可重用”的方法。你可以在这里找到一个很好的例子来帮助你入门:http: //michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/
回答by Ankesh
Have you tried using Behavior... Here is a ScrollInViewBehavior. I have used it for ListView and DataGrid..... I thinks it should work for ListBox......
您是否尝试过使用行为...这是一个 ScrollInViewBehavior。我已经将它用于 ListView 和 DataGrid ......我认为它应该适用于 ListBox......
You have to add a reference to System.Windows.Interactivityto use Behavior<T> class
您必须添加引用 System.Windows.Interactivity才能使用Behavior<T> class
Behavior
行为
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;
}
}
Usage
用法
Add alias to XAMLas xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
将别名添加到XAMLas xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
then in your Control
然后在你的 Control
<ListBox ItemsSource="{Binding Path=MyList}"
SelectedItem="{Binding Path=MyItem,
Mode=TwoWay}"
SelectionMode="Single">
<i:Interaction.Behaviors>
<Behaviors:ScrollIntoViewForListBox />
</i:Interaction.Behaviors>
</ListBox>
Now When ever "MyItem" property is set in ViewModelthe List will be scrolled when changes are refelected.
现在,当在ViewModel列表中设置“MyItem”属性时,将在反映更改时滚动。
回答by Dutts
I know this is an old question, but my recent search for the same problem has brought me to this. I wanted to use the behavior approach, but didn't want a dependency on the Blend SDK just to give me Behavior<T>so here's my solution without it:
我知道这是一个老问题,但我最近对同一问题的搜索使我想到了这一点。我想使用行为方法,但不想依赖 Blend SDK 只是为了给我,Behavior<T>所以这是我没有它的解决方案:
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);
}));
}
}
}
and then usage is just
然后用法只是
<ListBox ItemsSource="{Binding Path=MyList}"
SelectedItem="{Binding Path=MyItem, Mode=TwoWay}"
SelectionMode="Single"
behaviors:ListBoxBehavior.ScrollSelectedIntoView="True">
回答by user3511483
Try this:
尝试这个:
private void lstBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lstBox.ScrollIntoView(lstBox.SelectedItem);
}
回答by Alex
After tying various methods I found the following to be the simplest and the best
捆绑了各种方法后,我发现以下是最简单和最好的
lstbox.Items.MoveCurrentToLast();
lstbox.ScrollIntoView(lstbox.Items.CurrentItem);
回答by C. Tewalt
I took Ankesh's answer and made it not dependent on the blend sdk. The downside of my solution is that it will apply to all listboxes in your app. But the upside is no custom class needed.
我接受了 Ankesh 的回答,让它不依赖于混合 SDK。我的解决方案的缺点是它将适用于您应用程序中的所有列表框。但好处是不需要自定义类。
When your app is initializing...
当您的应用程序正在初始化时...
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);
});
}
}
}
This makes all of your listboxes scroll to selected (which I like as a default behavior).
这使您的所有列表框滚动到选定(我喜欢作为默认行为)。
回答by honzakuzel1989
I'm using this (in my opinion) clear and easy solution
我正在使用这个(在我看来)清晰而简单的解决方案
listView.SelectionChanged += (s, e) =>
listView.ScrollIntoView(listView.SelectedItem);
where listViewis name of ListViewcontrol in xaml, SelectedItemis affected from my MVVM and code is inserted in constructorin xaml.cs file.
xaml 中listView的ListView控件名称在哪里,SelectedItem受我的 MVVM 的影响,代码插入到 xaml.cs 文件的构造函数中。

