mvvm как сделать автоматический просмотр списка в новом элементе в виде списка - PullRequest
17 голосов
/ 23 июля 2010

Я использую шаблон MVVM, у меня есть представление, которое создает новый ViewModel, после того, как пользователь нажимает сохранить, это представление закрывается, и открывается отдельное представление, которое отображает коллекцию моделей представлений в ListView.

Этот ListView отсортирован в алфавитном порядке, поэтому новый ViewModel может появиться в нижней части ListBox, который не сразу виден пользователю.

MyВопрос в том, как мне получить представление для автоматической прокрутки к вновь добавленному элементу?

Я предполагаю, что он будет использовать прикрепленное поведение, и событие ScrollIntoView в ListView, однако, какое событие этоМне нужно захватить из GridView, в котором я не уверен ..

Приветствия

Ответы [ 5 ]

15 голосов
/ 24 июля 2010

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

Класс:

/// <summary>
/// ListBoxItem Behavior class
/// </summary>
public static class ListBoxItemBehavior
{
    #region IsBroughtIntoViewWhenSelected

    /// <summary>
    /// Gets the IsBroughtIntoViewWhenSelected value
    /// </summary>
    /// <param name="listBoxItem"></param>
    /// <returns></returns>
    public static bool GetIsBroughtIntoViewWhenSelected(ListBoxItem listBoxItem)
    {
        return (bool)listBoxItem.GetValue(IsBroughtIntoViewWhenSelectedProperty);
    }

    /// <summary>
    /// Sets the IsBroughtIntoViewWhenSelected value
    /// </summary>
    /// <param name="listBoxItem"></param>
    /// <param name="value"></param>
    public static void SetIsBroughtIntoViewWhenSelected(
      ListBoxItem listBoxItem, bool value)
    {
        listBoxItem.SetValue(IsBroughtIntoViewWhenSelectedProperty, value);
    }

    /// <summary>
    /// Determins if the ListBoxItem is bought into view when enabled
    /// </summary>
    public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty =
        DependencyProperty.RegisterAttached(
        "IsBroughtIntoViewWhenSelected",
        typeof(bool),
        typeof(ListBoxItemBehavior),
        new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged));

    /// <summary>
    /// Action to take when item is brought into view
    /// </summary>
    /// <param name="depObj"></param>
    /// <param name="e"></param>
    static void OnIsBroughtIntoViewWhenSelectedChanged(
      DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        ListBoxItem item = depObj as ListBoxItem;
        if (item == null)
            return;

        if (e.NewValue is bool == false)
            return;

        if ((bool)e.NewValue)
            item.Selected += OnListBoxItemSelected;
        else
            item.Selected -= OnListBoxItemSelected;
    }

    static void OnListBoxItemSelected(object sender, RoutedEventArgs e)
    {
        // Only react to the Selected event raised by the ListBoxItem 
        // whose IsSelected property was modified.  Ignore all ancestors 
        // who are merely reporting that a descendant's Selected fired. 
        if (!Object.ReferenceEquals(sender, e.OriginalSource))
            return;

        ListBoxItem item = e.OriginalSource as ListBoxItem;
        if (item != null)
            item.BringIntoView();
    }

    #endregion // IsBroughtIntoViewWhenSelected
}

Добавьте xmlns к вашему представлению:

xmlns:util="clr-namespace:YourNamespaceHere.Classes"

Добавить стиль к ресурсам Window / UserControl:

<Window.Resources>
    <Style x:Key="ListBoxItemContainerStyle" TargetType="{x:Type ListBoxItem}"
        BasedOn="{StaticResource {x:Type ListBoxItem}}">
        <Setter Property="util:ListBoxItemBehavior.IsBroughtIntoViewWhenSelected" Value="true"/>
    </Style>
</Window.Resources>

Реализация списка:

<ListBox ItemsSource="{Binding MyView}"
         DisplayMemberPath="Title"
         SelectedItem="{Binding SelectedItem}" 
         ItemContainerStyle="{StaticResource ListBoxItemContainerStyle}"/>
3 голосов
/ 15 октября 2015

Другое решение, использующее ListBox. Для реализации автоматической прокрутки вы можете создать собственный элемент управления!


C #

public class LoggingListBox : ListBox
{
    ///<summary>
    ///Define the AutoScroll property. If enabled, causes the ListBox to scroll to 
    ///the last item whenever a new item is added.
    ///</summary>
    public static readonly DependencyProperty AutoScrollProperty = 
        DependencyProperty.Register(
            "AutoScroll", 
            typeof(Boolean), 
            typeof(LoggingListBox), 
            new FrameworkPropertyMetadata(
                true, //Default value.
                FrameworkPropertyMetadataOptions.AffectsArrange | 
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 
                AutoScroll_PropertyChanged));

    /// <summary>
    /// Gets or sets whether or not the list should scroll to the last item 
    /// when a new item is added.
    /// </summary>
    [Category("Common")] //Indicate where the property is located in VS designer.
    public bool AutoScroll
    {
        get { return (bool)GetValue(AutoScrollProperty); }
        set { SetValue(AutoScrollProperty, value); }
    }

    /// <summary>
    /// Event handler for when the AutoScroll property is changed.
    /// This delegates the call to SubscribeToAutoScroll_ItemsCollectionChanged().
    /// </summary>
    /// <param name="d">The DependencyObject whose property was changed.</param>
    /// <param name="e">Change event args.</param>
    private static void AutoScroll_PropertyChanged(
        DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        SubscribeToAutoScroll_ItemsCollectionChanged(
            (LoggingListBox)d,
            (bool)e.NewValue);
    }

    /// <summary>
    /// Subscribes to the list items' collection changed event if AutoScroll is enabled.
    /// Otherwise, it unsubscribes from that event.
    /// For this to work, the underlying list must implement INotifyCollectionChanged.
    ///
    /// (This function was only creative for brevity)
    /// </summary>
    /// <param name="listBox">The list box containing the items collection.</param>
    /// <param name="subscribe">Subscribe to the collection changed event?</param>
    private static void SubscribeToAutoScroll_ItemsCollectionChanged(
        LoggingListBox listBox, bool subscribe)
    {
        INotifyCollectionChanged notifyCollection =
            listBox.Items.SourceCollection as INotifyCollectionChanged;
        if (notifyCollection != null)
        {
            if (subscribe)
            {
                //AutoScroll is turned on, subscribe to collection changed events.
                notifyCollection.CollectionChanged += 
                    listBox.AutoScroll_ItemsCollectionChanged;
            }
            else
            {
                //AutoScroll is turned off, unsubscribe from collection changed events.
                notifyCollection.CollectionChanged -= 
                    listBox.AutoScroll_ItemsCollectionChanged;
            }
        }
    }

    /// <summary>
    /// Event handler called only when the ItemCollection changes
    /// and if AutoScroll is enabled.
    /// </summary>
    /// <param name="sender">The ItemCollection.</param>
    /// <param name="e">Change event args.</param>
    private void AutoScroll_ItemsCollectionChanged(
        object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            int count = Items.Count;
            ScrollIntoView(Items[count - 1]);
        }
    }

    /// <summary>
    /// Constructor a new LoggingListBox.
    /// </summary>
    public LoggingListBox()
    {
        //Subscribe to the AutoScroll property's items collection 
        //changed handler by default if AutoScroll is enabled by default.
        SubscribeToAutoScroll_ItemsCollectionChanged(
            this, (bool)AutoScrollProperty.DefaultMetadata.DefaultValue);
    }
}

1010 * XAML * Вот как вы используете элемент управления в XAML: <tools:LoggingListBox/> <!-- AutoScroll="true" by default. --> Где-то вам нужно указать, как вы получаете доступ к этому элементу управления. Это полностью зависит от настроек вашего проекта. xmlns:tools="clr-namespace:MyCustomControls;assembly=MyCustomControls" Как это работает

Для создания пользовательского элемента управления вам нужен только код C #. Мы делаем это, расширяя ListBox и добавляя только одно свойство, AutoScroll . Поскольку это свойство зависимости, оно будет участвовать в системе привязки WPF, что также делает его доступным в конструкторе Visual Studio.
Охват свойств зависимостей - довольно большая тема, но она является неотъемлемой частью создания пользовательских элементов управления. Вы можете узнать больше о Обзор авторизации элементов управления или Обзор свойств зависимостей .

Цель состоит в том, чтобы подписаться на событие изменения коллекции базовой коллекции элементов, чтобы мы могли реагировать прокруткой вниз при каждом добавлении нового элемента. Мы должны подписаться на это событие в двух местах.

  1. Всякий раз, когда AutoScroll имеет значение true , нам необходимо подписаться. Значение AutoScroll может измениться в любое время, и мы сможем ответить соответствующим образом. Если установлено значение false , мы должны дать команду элементу управления прекратить прокрутку вниз, отменив подписку.
  2. Предположим, AutoScroll нужно установить только во время компиляции, нам нужен метод подписки при запуске. Это делается с помощью конструктора элемента управления.

Зачем создавать пользовательский элемент управления

Прежде всего, мы максимально упростили XAML. Нам нужен только доступ к элементу управления и, при необходимости, указание или привязка к свойству AutoScroll.

Это соответствует MVVM. Нашей модели представления не нужно беспокоиться о функциональности AutoScroll, поскольку она содержится в элементе управления. В то же время модель представления может предоставлять свойство, с которым связано свойство AutoScroll, что дает нам желаемое разделение представления и модели представления.

Кроме того, мы избегали использования поведения. Это означает, что мы удалили две зависимости из нашего проекта (учитывая, что это была единственная причина, по которой эти зависимости были включены в первую очередь). Мы можем безопасно опустить System.Windows.Interactivity и Microsoft.Expressions.Interactions из ссылок проекта.

1062 * Недостатки * У этого подхода есть только один недостаток. Основная коллекция элементов должна реализовывать INotifyCollectionChanged. В большинстве случаев это не проблема. Если вы используете MVVM, вы, вероятно, уже завернули свои элементы в ObservableCollection, который уже реализует наш требуемый интерфейс. Наслаждайтесь! : -)

2 голосов
/ 23 июля 2010

Добавить выбранный элемент DependecyProperty в класс, который содержит коллекцию. Привязать к нему выбранный элемент списка. После добавления новой модели в коллекцию установите выбранный элемент DependencyProperty.

0 голосов
/ 27 июля 2012

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

для просмотра списка просто введите:

listView1.EnsureVisible(listView1.Items.Count - 1);

А для Listbox просто введите:

listBox1.SelectedIndex = listBox1.Items.Count - 1; 
listBox1.SelectedIndex = -1;

К вашему элементу списка добавить метод (..etc) ... .. или ударить по таймеру.

Вышеуказанный способ работы с OP, кажется, много делает для меня, я ленивый ... Весь код объясняет себя.

0 голосов
/ 27 июня 2012

Это может не относиться к WPF, но в WinForms код похож на lstData.EnsureVisible(itemIndex);

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...