如何使用caliburn.micro从视图模型设置滚动位置?

时间:2013-03-07 21:10:14

标签: wpf mvvm caliburn.micro

我的视图中有一个ListBox,绑定到一个动态增长的集合。我希望滚动位置跟随最后添加的项目(附加到列表的底部)。我怎样才能用Caliburn.Micro实现这个目标?

3 个答案:

答案 0 :(得分:1)

您可以使用GetView()在视图模型中引用该视图。这也结合了视图和视图模型。

var myView = GetView() as MyView;
myView.MyListBox.DoStuff

另一种选择是创建一种行为。 This是如何使用行为从视图模型中展开TreeView的示例。同样可以应用于ListBox

答案 1 :(得分:1)

另一种方法是使用事件聚合器将消息发布到视图。

类似的东西:

Aggregator.Publish(ItemAddedMessage<SomeItemType>(itemThatWasJustAdded));

并在视图中:

public class SomeView : IHandle<ItemAddedMessage<SomeItemType>>
{

   public void Handle(ItemAddedMessage<SomeItemType> message)
   {
       // Implement view specific behaviour here
   }
}

这取决于您的要求,但至少视图负责显示问题,您仍然可以测试VM

此外,您只能在视图中实现代码 - 因为它似乎是一个视图问题(例如,使用列表框提供的事件)

一种行为也很有用,但也许与你的类型相关的一点 - 例如通用行为SeekAddedItemBehaviour,用于挂钩列表框事件以查找最后一项。不确定列表框是否公开了所需的事件,但值得一看

编辑:

好的,这可能会完全停止 - 你应该能够将这种行为附加到列表框中,它应该处理其余的事情:

public class ListBoxSeekLastItemBehaviour : System.Windows.Interactivity.Behavior<ListBox>
{
    private static readonly DependencyProperty ItemsSourceWatcherProperty = DependencyProperty.Register("ItemsSourceWatcher", typeof(object), typeof(ListBoxSeekLastItemBehaviour), new PropertyMetadata(null, OnItemsSourceWatcherPropertyChanged));

    private ListBox _listBox = null;

    private static void OnItemsSourceWatcherPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ListBoxSeekLastItemBehaviour source = d as ListBoxSeekLastItemBehaviour;

        if (source != null)
            source.OnItemsSourceWatcherPropertyChanged();
    }

    private void OnItemsSourceWatcherPropertyChanged()
    {
        // The itemssource has changed, check if it raises collection changed notifications
        if (_listBox.ItemsSource is INotifyCollectionChanged)
        {
            // if it does, hook the CollectionChanged event so we can respond to items being added
            (_listBox.ItemsSource as INotifyCollectionChanged).CollectionChanged += new NotifyCollectionChangedEventHandler(ListBoxSeekLastItemBehaviour_CollectionChanged);
        }
    }

    void ListBoxSeekLastItemBehaviour_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count > 0)
        {
            // If an item was added seek it
            ScrollIntoView(e.NewItems[0]);
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();

        // We've been attached - get the associated listbox
        var box = this.AssociatedObject as ListBox;

        if (box != null)
        {
            // Hold a ref
            _listBox = box;

            // Set a binding to watch for property changes
            System.Windows.Data.Binding binding = new System.Windows.Data.Binding("ItemsSource") { Source = _listBox; }  

            // EDIT: Potential bugfix - you probably want to check the itemssource here just 
            // in case the behaviour is applied after the original ItemsSource binding has been evaluated - otherwise you might miss the change
            OnItemsSourceWatcherPropertyChanged();
        }
    }

    private void ScrollIntoView(object target)
    {
        // Set selected item and try and scroll it into view
        _listBox.SelectedItem = target;
        _listBox.ScrollIntoView(target);
    }
}

您可能希望稍微整理一下,并确保在CollectionChanged更改时删除ItemsSource的事件处理程序。

您也可以将其称为SeekLastAddedItemBehaviourSeekLastAddedItemBehavior - 我倾向于保留美国拼写,因为它与Microsoft的拼写相符。我认为SeekLastItem听起来会滚动到列表中的最后一项而不是最后添加的项

答案 2 :(得分:1)

实际上,有一种更简单的方法可以实现这一点,而不需要上述任何方法。

只需使用以下内容扩展您的列表框:

namespace Extensions.Examples {
    public class ScrollingListBox : ListBox
        {
            protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                if (e.NewItems != null)
                {
                    int newItemCount = e.NewItems.Count;
                    if (newItemCount > 0)
                        this.ScrollIntoView(e.NewItems[newItemCount - 1]);

                    base.OnItemsChanged(e);
                }
            }
        }
}

然后在Xaml中,声明扩展类的位置如下:

xmlns:Extensions="clr-namespace:Extensions.Examples"

当您创建列表框时,而不是使用

<Listbox></Listbox>

只需使用扩展类

<Extensions:ScrollingListBox></Extensions:ScrollingListBox>