如何在MVVM中处理`ScrollViewer.ScrollChanged`事件?

时间:2015-12-14 16:04:15

标签: c# wpf mvvm event-handling

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

<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,但我无法弄清楚xmlns使用了什么xml命名空间(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使用此课程from the article

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

基本上,我有两个问题:

  1. 我应该为mvvmjaco xml命名空间使用哪个类或库?
  2. 如何使用参数在我的viewModel中处理ScrollViewer.ScrollChanged事件?

3 个答案:

答案 0 :(得分:3)

我会用以下的附属物来解决它:

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);
        }
    }
}

班级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 =&#34; galasoft.ch/mvvmlight" 所需的库是:

  • GalaSoft.MVVmLight
  • GalaSoft.MVVmLight.Extras
  • GalaSoft.MVVmLight.Platform

答案 1 :(得分:2)

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

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

  3. 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>
    

    ViewModel&amp;东西:

    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类,用于将args传递给命令

    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);
                        }
                    }
                }
            }
        }
    }
    

答案 2 :(得分:1)

我不知道mvvmjaco但我对第二个问题有一些暗示。您无法直接从ScrollChangedDataGrid添加处理程序。您可以扩展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)
    {

    }