Как установить положение прокрутки из модели представления с помощью caliburn.micro?

У меня есть ListBox, на мой взгляд, привязанный к коллекции, которая динамически растет. Я хотел бы, чтобы позиция прокрутки следовала за последним добавленным элементом (который добавляется в конец списка). Как я могу добиться этого с помощью Caliburn.Micro?


person Michael Teper    schedule 07.03.2013    source источник


Ответы (3)


Альтернативой может быть использование агрегатора событий для публикации сообщения в представлении.

Что-то типа:

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

и в представлении:

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

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

Это зависит от ваших требований, но, по крайней мере, представление отвечает за проблемы с отображением, и вы все равно можете протестировать виртуальную машину.

Кроме того, вы можете просто реализовать код исключительно в представлении, поскольку он, по-видимому, связан с представлением (например, с использованием событий, которые предоставляет список)

Поведение также было бы полезно, но, возможно, оно немного меньше связано с вашими типами, например. общее поведение 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.

Также вы можете назвать его SeekLastAddedItemBehaviour или SeekLastAddedItemBehavior — я предпочитаю использовать американское правописание, поскольку оно совпадает с правописанием Microsoft. Я думаю, что SeekLastItem звучит так, будто он будет прокручиваться до последнего элемента в списке, а не до последнего добавленного элемента.

person Charleh    schedule 08.03.2013
comment
Просто взгляните, и не похоже, что ListBox знает об изменении ItemsSource (по крайней мере, в Silverlight), поэтому может быть невозможно написать общее поведение. Существует метод ScrollIntoView, который принимает объект - установка SelectedItem и вызов ScrollIntoView, вероятно, сделают это, но перехватывать события изменения списка, не зная, когда изменения ItemsSource будут трудными! - person Charleh; 08.03.2013
comment
Что ж, кому-то удалось обойти это, создав привязку для наблюдения за изменениями свойства - я вставил туда реализацию поведения. Это должно работать, но я не проверял это. - person Charleh; 08.03.2013

Вы можете ссылаться на представление в модели представления, используя GetView(). Это также связывает представление и модель представления.

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

Другой вариант — создать поведение. Это пример того, как используйте поведение, чтобы расширить TreeView из модели представления. То же самое можно применить к ListBox.

person Derek Beattie    schedule 07.03.2013
comment
Не часто бывает хорошей идеей, чтобы ViewModel манипулировала View — как бы вы отделили модульное тестирование только для ViewModel? Лучший ответ — создать поведение, как указывает Дерек. - Поведение поддерживается в представлении, что сохраняет проблемы отображения на правильном уровне. - person EtherDragon; 08.03.2013
comment
Я согласен, поэтому я обычно использую поведение или реализую IResult, но, тем не менее, можно ссылаться на представление из виртуальной машины. Это удобный быстрый и грязный способ сделать доказательство концепции, которое позже может быть перемещено в более несвязанный вариант. - person Derek Beattie; 08.03.2013
comment
Я упомянул IResult в своем предыдущем комментарии, в данном конкретном случае он не сработает, поэтому я не включил его в ответ. В некоторых случаях IResult удобен из-за переданного ActionExecutionContext. - person Derek Beattie; 08.03.2013

На самом деле, есть более простой способ добиться этого, без всего вышеперечисленного.

Просто дополните свой Listbox следующим:

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>
person Dave    schedule 07.03.2014