Прокрутите окно прокрутки до верха через viewmodel - PullRequest
8 голосов
/ 22 мая 2011

Я использую ScrollViewer с шаблоном MVVM, а список элементов обернут ScrollViewer, например

<ScrollViewer>
  <ListView>
    <ListView.View>
        <GridView>
            <GridViewColumn
                Header = "Name"
                DisplayMemberBinding="{Binding Path=Name}"
            />              
        </GridView>
    </ListView.View>
  </ListView>
</ScrollViewer>

Элементы списка связаны с коллекцией объектов в модели представления. Я хочу, чтобы средство прокрутки прокручивало вверх, когда элемент добавляется или удаляется из коллекции.
Мне нужна модель представления для запуска события, а не использование метода ScrollToTop() в выделенном фрагменте представления.

Ответы [ 4 ]

20 голосов
/ 19 июня 2015

ИМХО, самый ясный способ сделать это - использовать «Поведение» через AttachedProperty.AttachedProperty - это механизм для расширения функциональности существующих элементов управления.

Сначала создайте класс для хранения AtachedProperty, например:

public class ScrollViewerBehavior
{
    public static bool GetAutoScrollToTop(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoScrollToTopProperty);
    }

    public static void SetAutoScrollToTop(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoScrollToTopProperty, value);
    }

    public static readonly DependencyProperty AutoScrollToTopProperty =
        DependencyProperty.RegisterAttached("AutoScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, (o, e) =>
            {
                var scrollViewer = o as ScrollViewer;
                if (scrollViewer == null)
                {
                    return;
                }
                if ((bool)e.NewValue)
                {
                    scrollViewer.ScrollToTop();
                    SetAutoScrollToTop(o, false);
                }
            }));
}

Это присоединенное свойство позволяет ScrollViewer «магически» новое свойство типа Boolean, действующее как DependencyProperty в вашем XAML.Если вы связываете это свойство со стандартным свойством в вашей ViewModel, например:

private bool _reset;
public bool Reset
{
    get { return _reset; }
    set
    {
        _reset = value;
        if(PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs("Reset"));
    }
}

(опять же, ваше имя), а затем вы устанавливаете это свойство Reset на true, вашScrollViewer прокрутит вверх.Я назвал AtachedProperty AutoScrollToTop, но имя не имеет значения для этой цели.

XAML будет выглядеть примерно так:

<ScrollViewer my:ScrollViewerBehavior.AutoScrollToTop="{Binding Reset, Mode=TwoWay}">
    <ListView>
        <ListView.View>
            <GridView>
                <GridViewColumn
                    Header = "Name"
                    DisplayMemberBinding="{Binding Path=Name}"
                />
            </GridView>
        </ListView.View>
    </ListView>
</ScrollViewer>

Примечание: my - это пространство имен, в котором живет ваш класс ScrollViewerBehavior.Например: xmlns:my="clr-namespace:MyApp.Behaviors"

Наконец, единственное, что вам нужно сделать в ViewModel, это установить Reset = true, когда вам нравится, в вашем случае, когда вы добавляете или удаляете элемент из коллекции.

2 голосов
/ 18 июня 2015

Создайте новый элемент управления ListView, который расширяет Listview, и используйте этот новый вместо

public class ScrollListView : ListView
    {
        protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {

            if (e.OldItems.Count > 0)
                this.ScrollIntoView(e.OldItems[e.OldStartingIndex]);

            base.OnItemsChanged(e);
        }
    }
0 голосов
/ 18 июня 2015

Самый простой правильный способ сделать это в MVVM - создать событие в вашей модели представления и подписаться на него из вашего представления.А затем, в обработчике событий, вызовите ScrollToTop.

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

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

public interface IMyViewModel
{
    event EventHandler MyCollectionChanged;
}

public class MyViewModel : IMyViewModel
{
    public event EventHandler MyCollectionChanged;

    // More viewmodel related stuff

    protected virtual void OnMyCollectionChanged(EventArgs e)
    {
        if (MyCollectionChanged != null)
            MyCollectionChanged(this, e);
    }
}

public class MyWindow : Window
{
    public MyWindow(IMyViewModel viewModel)
    {
        this.DataContext = viewModel;
        InitializeComponent();
        (this.DataContext as IViewModel).MyCollectionChanged+= MyCollectionChangedEventHandler;
    }

    private void MyCollectionChangedEventHandler(object sender, EventArgs e)
    {
        // Do view related stuff
        scrollViewer.ScrollToTop();
    }

}

РЕДАКТИРОВАТЬ: Но это может быть уточнено гораздо больше, конечно.Если вы хотите избежать использования выделенного кода, ищите DataEventTriggers.Если вы не возражаете против выделения кода, но обеспокоены утечками памяти, поищите слабые события.

И, наконец, поскольку необходимая логика связана с просмотром на 100% (каждый раз прокручивайте ListView.элемент добавлен или удален к нему), вы также можете реализовать его в качестве свойства Поведение / присоединение или расширения ListView.Это может немного запутаться, но я призываю вас подумать над этими вариантами.

0 голосов
/ 26 июня 2011

Я также столкнулся с подобным сценарием, в котором мне нужно было назначить ScrollViewer's HorizontalOffset и VerticalOffset программно.Боюсь, что для этого нет прямого механизма связывания.То, что я делал, было обходным путем (поверьте, мне все еще не нравится подход, которому я следовал, но я не нашел другого варианта).Вот что я предлагаю:

Подключите событие Locked объекта ScrollViewer, приведите объект отправителя к ScrollViewer и назначьте его свойству в DataContext (означает, что вам нужно сохранить свойство ScrollViewer в DataContext, который будет содержать ссылку на ScrollViewerв пользовательском интерфейсе).Подключите события CollectionChanged ObservableCollection во ViewModel и, используя свойство ScrollViewer, вы можете вызывать такие методы, как ScrollToTop () и т. Д.

Это просто обходной путь.Я все еще ищу лучшее решение.

...