c# - mvvm框架 - wpf mvvm framework



将WPF ListBox滚动到视图模型中的代码中设置的SelectedItem (6)

我有一个带有列表框的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视图上调用用户控件上的函数的更简单方法? 这有点像:

  1. 创建一个依赖属性
  2. 添加回调到属性更改回调
  3. 处理静态回调内的函数调用

将逻辑放在ScrollSelectedItem { set {方法中,这很好,但依赖框架似乎潜行并设法工作而不实际调用它。


Answer #1

因为这严格来说是一个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/http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/


Answer #2

在查看答案后,出现了一个共同的主题:外部类侦听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以外的任何控件上的通用操作。


Answer #3

尝试这个:

private void lstBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    lstBox.ScrollIntoView(lstBox.SelectedItem);
}

Answer #4

您是否尝试过使用行为......这是一个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”属性时,将在重新选择更改时滚动List。


Answer #5

我正在使用这个(在我看来)明确而简单的解决方案

listView.SelectionChanged += (s, e) => 
    listView.ScrollIntoView(listView.SelectedItem);

其中listView是xaml中ListView控件的名称, SelectedItem受到我的MVVM的影响,代码被插入到xaml.cs文件的构造函数中。


Answer #6

我知道这是一个古老的问题,但是我最近对同样问题的搜索引起了我的注意。 我想使用行为方法,但不希望依赖于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">




mvvm