У меня есть ListBox
, на мой взгляд, привязанный к коллекции, которая динамически растет. Я хотел бы, чтобы позиция прокрутки следовала за последним добавленным элементом (который добавляется в конец списка). Как я могу добиться этого с помощью Caliburn.Micro?
Как установить положение прокрутки из модели представления с помощью caliburn.micro?
Ответы (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
звучит так, будто он будет прокручиваться до последнего элемента в списке, а не до последнего добавленного элемента.
ListBox
знает об изменении ItemsSource
(по крайней мере, в Silverlight), поэтому может быть невозможно написать общее поведение. Существует метод ScrollIntoView
, который принимает объект - установка SelectedItem
и вызов ScrollIntoView
, вероятно, сделают это, но перехватывать события изменения списка, не зная, когда изменения ItemsSource
будут трудными!
- person Charleh; 08.03.2013
Вы можете ссылаться на представление в модели представления, используя GetView(). Это также связывает представление и модель представления.
var myView = GetView() as MyView;
myView.MyListBox.DoStuff
Другой вариант — создать поведение. Это пример того, как используйте поведение, чтобы расширить TreeView
из модели представления. То же самое можно применить к ListBox
.
На самом деле, есть более простой способ добиться этого, без всего вышеперечисленного.
Просто дополните свой 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>