我有一个带有列表框的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 {
方法中会很好,但是依赖框架似乎潜行并且设法工作而不实际调用它。
答案 0 :(得分:48)
您是否尝试过使用行为......这是一个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>
现在,当ViewModel
中设置了“MyItem”属性时,将在重新选择更改时滚动列表。
答案 1 :(得分:34)
在查看答案后,出现了一个共同的主题:外部类侦听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之外的任何控件都可能发生的泛型操作。
答案 2 :(得分:16)
因为这严格来说是一个View问题,所以没有理由为此目的在视图后面的代码中没有事件处理程序。听取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/
答案 3 :(得分:11)
我知道这是一个老问题,但是我最近对同样问题的搜索引起了我的注意。我想使用行为方法,但不希望依赖于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);
}));
}
}
}
然后使用只是
<ListBox ItemsSource="{Binding Path=MyList}"
SelectedItem="{Binding Path=MyItem, Mode=TwoWay}"
SelectionMode="Single"
behaviors:ListBoxBehavior.ScrollSelectedIntoView="True">
答案 4 :(得分:7)
试试这个:
private void lstBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lstBox.ScrollIntoView(lstBox.SelectedItem);
}
答案 5 :(得分:6)
我正在使用这个(在我看来)清晰易用的解决方案
listView.SelectionChanged += (s, e) =>
listView.ScrollIntoView(listView.SelectedItem);
其中listView
是xaml中ListView
控件的名称,SelectedItem
受我的MVVM影响,代码插入xaml.cs文件中的构造函数。
答案 6 :(得分:3)
在绑定各种方法后,我发现以下是最简单和最好的
lstbox.Items.MoveCurrentToLast();
lstbox.ScrollIntoView(lstbox.Items.CurrentItem);
答案 7 :(得分:1)
我接受了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);
});
}
}
}
这使得所有列表框都滚动到选中状态(我喜欢它作为默认行为)。