如何处理 MVVM 中的“ScrollViewer.ScrollChanged”事件?

How to handle `ScrollViewer.ScrollChanged` event in MVVM?

我尝试以明显的方式处理 DataGrid 的路由事件 ScrollViewer.ScrollChanged

<i:Interaction.Triggers>
    <i:EventTrigger EventName="ScrollViewer.ScrollChanged">
       <ei:CallMethodAction MethodName="ScrollChangedHandler" TargetObject="{Binding}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

但是ScrollChangedHandler连开枪都没开

然后,我找到了 this article about handling events,但我无法弄清楚 xml 命名空间 (xmlns) 用于 mvvmjaco

<Image Width="360" Height="177" Source="Resources\PlayerArea.png">
   <i:Interaction.Triggers>
      <mvvmjoy:RoutedEventTrigger RoutedEvent="s:Contacts.ContactDown">
          <mvvmjaco:CommandAction Command="{Binding TouchCommand}" />
      </mvvmjoy:RoutedEventTrigger>
   </i:Interaction.Triggers>
</Image>

mvvmjoy 使用这个 class from the article:

public class RoutedEventTrigger :EventTriggerBase<DependencyObject>
{
    RoutedEvent _routedEvent;    
    //The code omitted for the brevity
}

基本上,我有两个问题:

  1. 我应该为 mvvmjaco xml 命名空间使用什么 class 或库?
  2. 如何处理我的 viewModel 中的 ScrollViewer.ScrollChanged 事件及其参数?

我不知道 mvvmjaco 但我对第二个问题有一些提示。您不能直接从 DataGrid 添加处理程序到 ScrollChanged。您可以扩展 DataGrid 并在那里添加自定义事件。例如:

public class ExtendedDataGrid : DataGrid
{
    public event ScrollChangedEventHandler ScrollChanged;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        var scrollViewer = (ScrollViewer)GetTemplateChild("DG_ScrollViewer");

        scrollViewer.ScrollChanged += OnScrollChanged;
    }

    protected virtual void OnScrollChanged(ScrollChangedEventArgs e)
    {
        ScrollChangedEventHandler handler = ScrollChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        OnScrollChanged(e);
    }
}

XAML:

<local:ExtendedDataGrid>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="ScrollChanged">
                <ei:CallMethodAction TargetObject="{Binding}"
                                     MethodName="OnScrollChanged" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </local:ExtendedDataGrid>

处理程序:

public void OnScrollChanged(object sender, ScrollChangedEventArgs e)
    {

    }
  1. 似乎 mvvmjaco:CommandAction 是从您的 ViewModel 调用命令的操作。您可以使用 i:InvokeCommandAction 作为替代。

  2. 您可以使用您链接的文章中的 RoutedEventTrigger 来处理滚动更改事件。

XAML:

<Window x:Class="ScrollChangedTest.MainWindow"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:local="clr-namespace:ScrollChangedTest"
                mc:Ignorable="d"
                xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
                Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <local:MainWindowViewModel />
</Window.DataContext>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>
    <DataGrid ItemsSource="{Binding DataItems}" AutoGenerateColumns="True">
        <i:Interaction.Triggers>
            <local:RoutedEventTrigger RoutedEvent="ScrollViewer.ScrollChanged">
                <local:CustomCommandAction Command="{Binding ScrollCommand}" />
            </local:RoutedEventTrigger>
        </i:Interaction.Triggers>
    </DataGrid>
    <TextBlock Grid.Row="1" Text="{Binding ScrollData}" />
</Grid>

</Window>

视图模型和内容:

public class MainWindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    ObservableCollection<DataItem> _dataItems = new ObservableCollection<DataItem>();
    public ObservableCollection<DataItem> DataItems { get { return _dataItems; } }

    private TestCommand _scrollCommand;
    public ICommand ScrollCommand { get { return _scrollCommand; } }

    public string ScrollData { get; set; }

    public MainWindowViewModel()
    {
        for (int i = 0; i < 100; i++)
        {
            _dataItems.Add(new DataItem() { Field1 = i.ToString(), Field2 = (i * 2).ToString(), Field3 = (i * 3).ToString() });
        }

        _scrollCommand = new TestCommand(OnScroll);
    }

    private void OnScroll(object param)
    {
        ScrollChangedEventArgs args = param as ScrollChangedEventArgs;

        if (args != null)
        {
            ScrollData = $"VerticalChange = {args.VerticalChange}; VerticalOffset = {args.VerticalOffset}";
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ScrollData)));
        }
    }
}

public class DataItem
{
    public string Field1 { get; set; }
    public string Field2 { get; set; }
    public string Field3 { get; set; }

}

public class TestCommand : ICommand
{
    private Action<object> _execute;

    public event EventHandler CanExecuteChanged;

    public TestCommand(Action<object> execute)
    {
        _execute = execute;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }
}

文章中的 RoutedEventTrigger:

public class RoutedEventTrigger : EventTriggerBase<DependencyObject>
{
    RoutedEvent _routedEvent;

    public RoutedEvent RoutedEvent
    {
        get { return _routedEvent; }
        set { _routedEvent = value; }
    }

    public RoutedEventTrigger()
    {
    }
    protected override void OnAttached()
    {
        Behavior behavior = base.AssociatedObject as Behavior;
        FrameworkElement associatedElement = base.AssociatedObject as FrameworkElement;

        if (behavior != null)
        {
            associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
        }
        if (associatedElement == null)
        {
            throw new ArgumentException("Routed Event trigger can only be associated to framework elements");
        }
        if (RoutedEvent != null)
        {
            associatedElement.AddHandler(RoutedEvent, new RoutedEventHandler(this.OnRoutedEvent));
        }
    }
    void OnRoutedEvent(object sender, RoutedEventArgs args)
    {
        base.OnEvent(args);
    }
    protected override string GetEventName()
    {
        return RoutedEvent.Name;
    }
}

CustomCommandAction class 用于将参数传递给命令

public sealed class CustomCommandAction : TriggerAction<DependencyObject>
{
    public static readonly DependencyProperty CommandParameterProperty =
        DependencyProperty.Register("CommandParameter", typeof(object), typeof(CustomCommandAction), null);

    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
        "Command", typeof(ICommand), typeof(CustomCommandAction), null);

    public ICommand Command
    {
        get
        {
            return (ICommand)this.GetValue(CommandProperty);
        }
        set
        {
            this.SetValue(CommandProperty, value);
        }
    }

    public object CommandParameter
    {
        get
        {
            return this.GetValue(CommandParameterProperty);
        }

        set
        {
            this.SetValue(CommandParameterProperty, value);
        }
    }

    protected override void Invoke(object parameter)
    {
        if (this.AssociatedObject != null)
        {
            ICommand command = this.Command;
            if (command != null)
            {
                if (this.CommandParameter != null)
                {
                    if (command.CanExecute(this.CommandParameter))
                    {
                        command.Execute(this.CommandParameter);
                    }
                }
                else
                {
                    if (command.CanExecute(parameter))
                    {
                        command.Execute(parameter);
                    }
                }
            }
        }
    }
}

我会用下面的附件解决它-属性:

using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication2
{
    public class DataGridExtensions
    {
        public static readonly DependencyProperty ScrollChangedCommandProperty = DependencyProperty.RegisterAttached(
            "ScrollChangedCommand", typeof(ICommand), typeof(DataGridExtensions),
            new PropertyMetadata(default(ICommand), OnScrollChangedCommandChanged));

        private static void OnScrollChangedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DataGrid dataGrid = d as DataGrid;
            if (dataGrid == null)
                return;
            if (e.NewValue != null)
            {
                dataGrid.Loaded += DataGridOnLoaded;
            }
            else if (e.OldValue != null)
            {
                dataGrid.Loaded -= DataGridOnLoaded;
            }
        }

        private static void DataGridOnLoaded(object sender, RoutedEventArgs routedEventArgs)
        {
            DataGrid dataGrid = sender as DataGrid;
            if (dataGrid == null)
                return;

            ScrollViewer scrollViewer = UIHelper.FindChildren<ScrollViewer>(dataGrid).FirstOrDefault();
            if (scrollViewer != null)
            {
                scrollViewer.ScrollChanged += ScrollViewerOnScrollChanged;
            }
        }

        private static void ScrollViewerOnScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            DataGrid dataGrid = UIHelper.FindParent<DataGrid>(sender as ScrollViewer);
            if (dataGrid != null)
            {
                ICommand command = GetScrollChangedCommand(dataGrid);
                command.Execute(e);
            }
        }

        public static void SetScrollChangedCommand(DependencyObject element, ICommand value)
        {
            element.SetValue(ScrollChangedCommandProperty, value);
        }

        public static ICommand GetScrollChangedCommand(DependencyObject element)
        {
            return (ICommand)element.GetValue(ScrollChangedCommandProperty);
        }
    }
}

class UIHelper 看起来像:

using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;

namespace WpfApplication2
{
    internal static class UIHelper
    {
        internal static IList<T> FindChildren<T>(DependencyObject element) where T : FrameworkElement
        {
            List<T> retval = new List<T>();
            for (int counter = 0; counter < VisualTreeHelper.GetChildrenCount(element); counter++)
            {
                FrameworkElement toadd = VisualTreeHelper.GetChild(element, counter) as FrameworkElement;
                if (toadd != null)
                {
                    T correctlyTyped = toadd as T;
                    if (correctlyTyped != null)
                    {
                        retval.Add(correctlyTyped);
                    }
                    else
                    {
                        retval.AddRange(FindChildren<T>(toadd));
                    }
                }
            }
            return retval;
        }

        internal static T FindParent<T>(DependencyObject element) where T : FrameworkElement
        {
            FrameworkElement parent = VisualTreeHelper.GetParent(element) as FrameworkElement;
            while (parent != null)
            {
                T correctlyTyped = parent as T;
                if (correctlyTyped != null)
                {
                    return correctlyTyped;
                }
                return FindParent<T>(parent);
            }
            return null;
        }
    }
}

然后你可以在你的DataGrid的定义中写:

<DataGrid ItemsSource="{Binding MySource}" extensionsNamespace:DataGridExtensions.ScrollChangedCommand="{Binding ScrollCommand}"/>

在您的 ViewModel 中,您有一个 ICommand 看起来像:

private ICommand scrollCommand;
public ICommand ScrollCommand
{
    get { return scrollCommand ?? (scrollCommand = new RelayCommand(Scroll)); }
}

private void Scroll(object parameter)
{
    ScrollChangedEventArgs scrollChangedEventArgs = parameter as ScrollChangedEventArgs;
    if (scrollChangedEventArgs != null)
    {

    }
}

第一个问题(特别感谢Andy ONeill and Magnus Montin):

MVVMJaco 是 xmlns:mvvmjaco="galasoft.ch/mvvmlight" 所需的库是:

  • GalaSoft.MVVmLight
  • GalaSoft.MVVmLight.额外内容
  • GalaSoft.MVVmLight.平台