Mvvm, как сделать вид списка автоматически прокручивается до нового элемента в виде списка

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

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

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

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

Приветствия

Ответы

Ответ 1

Это решение для 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 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}"/>

Ответ 2

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


С#

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 items collection 
        //changed handler by default if AutoScroll is enabled by default.
        SubscribeToAutoScroll_ItemsCollectionChanged(
            this, (bool)AutoScrollProperty.DefaultMetadata.DefaultValue);
    }
}

XAML

Вот как вы используете элемент управления в XAML:

<tools:LoggingListBox/> <!-- AutoScroll="true" by default. -->

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

xmlns:tools="clr-namespace:MyCustomControls;assembly=MyCustomControls"

Как это работает

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

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

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

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

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

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

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

Недостатки

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

Наслаждайтесь!: -)

Ответ 3

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

Ответ 4

Я только что протестировал решение Ника Миллера, оно отлично работает, но хотелось бы, чтобы пользователь также мог остановить автоматическую прокрутку. Я только что добавил флажок, чтобы активировать щелчок активным или нет private void ChkAutoScroll_Click (object sender, RoutedEventArgs e) { if (ChkAutoScroll.IsChecked == true) { Listbox1.AutoScroll = true; } else { Listbox1.AutoScroll = false; } }: private void ChkAutoScroll_Click (object sender, RoutedEventArgs e) { if (ChkAutoScroll.IsChecked == true) { Listbox1.AutoScroll = true; } else { Listbox1.AutoScroll = false; } } private void ChkAutoScroll_Click (object sender, RoutedEventArgs e) { if (ChkAutoScroll.IsChecked == true) { Listbox1.AutoScroll = true; } else { Listbox1.AutoScroll = false; } }

Но это не работает. Когда я добавляю точки останова, все идет хорошо:

'public bool AutoScroll
 {
   get {return (bool) GetValue (AutoScrollProperty); }
   set {SetValue (AutoScrollProperty, value); }
 }'

Но это не имеет никакого влияния.

Спасибо.

Ответ 5

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

Ответ 6

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

для списка просто вбить:

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

И для Listbox просто нажмите в:

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

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

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