WPF将UI事件绑定到ViewModel中的命令

时间:2011-02-04 12:07:10

标签: c# wpf mvvm

我正在对一个简单的应用程序进行一些重构以跟踪MVVM,我的问题是如何将SelectionChanged事件从我的代码中移出到viewModel?我已经看了一些绑定元素到命令的例子,但是并没有完全掌握它。谁能帮忙解决这个问题。谢谢!

任何人都可以使用以下代码提供解决方案吗?非常感谢!

public partial class MyAppView : Window 
{
    public MyAppView()
    {
        InitializeComponent();

        this.DataContext = new MyAppViewModel ();

        // Insert code required on object creation below this point.
    }

    private void contactsList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
    {
        //TODO: Add event handler implementation here.           
        //for each selected contact get the labels and put in collection 

        ObservableCollection<AggregatedLabelModel> contactListLabels = new ObservableCollection<AggregatedLabelModel>();

        foreach (ContactListModel contactList in contactsList.SelectedItems)
        {
            foreach (AggregatedLabelModel aggLabel in contactList.AggLabels)
            {
                contactListLabels.Add(aggLabel);
            }
        }
        //aggregate the contactListLabels by name
        ListCollectionView selectedLabelsView = new ListCollectionView(contactListLabels);

        selectedLabelsView.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
        tagsList.ItemsSource = selectedLabelsView.Groups;
    }
}

11 个答案:

答案 0 :(得分:95)

您应该将EventTrigger与来自Windows.Interactivity命名空间的InvokeCommandAction结合使用。这是一个例子:

<ListBox ...>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListBox>

您可以通过System.Windows.Interactivity引用Add reference > Assemblies > Extensions

完整的i命名空间为:xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

答案 1 :(得分:10)

这个问题有类似的问题。

WPF MVVM : Commands are easy. How to Connect View and ViewModel with RoutedEvent

我处理这个问题的方法是在ViewModel中有一个SelectedItem属性,然后将ListBox的SelectedItem绑定到该属性。

答案 2 :(得分:8)

要重构这一点,你需要改变思路。您将不再处理“选择已更改”事件,而是将所选项目存储在viewmodel中。然后,您将使用双向数据绑定,以便在用户选择项目时,您的viewmodel会更新,当您更改所选项目时,您的视图会更新。

答案 3 :(得分:7)

您最好的选择是使用Windows.Interactivity。使用EventTriggersICommand附加到任何RoutedEvent

以下是一篇可以帮助您入门的文章:Silverlight and WPF Behaviours and Triggers

答案 4 :(得分:2)

<ListBox SelectionChanged="{eb:EventBinding Command=SelectedItemChangedCommand, CommandParameter=$e}">

</ListBox>

<强>命令

{eb:EventBinding}(查找命令的简单命名模式)

{eb:EventBinding Command = CommandName}

<强> CommandParameter

$ e(EventAgrs)

$ this或$ this.Property

的字符串

https://github.com/JonghoL/EventBindingMarkup

答案 5 :(得分:1)

我会按照question

中的最佳答案进行操作

基本上,您的视图模型将包含所有项目的列表和所选项目的列表。然后,您可以将行为附加到管理所选项目列表的列表框中。

这样做意味着您在后面的代码中没有任何内容,并且xaml相当容易理解,也可以在您应用的其他地方重复使用该行为。

<ListBox ItemsSource="{Binding AllItems}" Demo:SelectedItems.Items="{Binding SelectedItems}" SelectionMode="Multiple" />

答案 6 :(得分:1)

有时,当需要绑定自定义用户控件的事件时,通过Interactivity触发器将命令绑定到命令的解决方案不起作用。 在这种情况下,您可以使用自定义行为。

声明绑定行为,如:

public class PageChangedBehavior
{
    #region Attached property

    public static ICommand PageChangedCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(PageChangedCommandProperty);
    }
    public static void SetPageChangedCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(PageChangedCommandProperty, value);
    }

    public static readonly DependencyProperty PageChangedCommandProperty =
        DependencyProperty.RegisterAttached("PageChangedCommand", typeof(ICommand), typeof(PageChangedBehavior),
            new PropertyMetadata(null, OnPageChanged));

    #endregion

    #region Attached property handler

    private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as PageControl;
        if (control != null)
        {
            if (e.NewValue != null)
            {
                control.PageChanged += PageControl_PageChanged;
            }
            else
            {
                control.PageChanged -= PageControl_PageChanged;
            }
        }
    }

    static void PageControl_PageChanged(object sender, int page)
    {
        ICommand command = PageChangedCommand(sender as DependencyObject);

        if (command != null)
        {
            command.Execute(page);
        }
    }

    #endregion

}

然后将其绑定到xaml中的命令:

        <controls:PageControl
            Grid.Row="2"
            CurrentPage="{Binding Path=UsersSearchModel.Page,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            PerPage="{Binding Path=UsersSearchModel.PageSize,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            Count="{Binding Path=UsersSearchModel.SearchResults.TotalItemCount}"
            behaviors:PageChangedBehavior.PageChangedCommand="{Binding PageChangedCommand}">
        </controls:PageControl>

答案 7 :(得分:1)

考虑Microsoft.Xaml.Behaviors.Wpf,它的所有者是Microsoft,您可以在该页面上看到。

System.Windows.Interactivity.WPF的所有者是mthamil,有人可以告诉我它可靠吗?

Microsoft.Xaml.Behaviors.Wpf的示例:

<UserControl ...
             xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
             ...>

<Button x:Name="button">
    <behaviors:Interaction.Triggers>
        <behaviors:EventTrigger EventName="Click" SourceObject="{Binding ElementName=button}">
            <behaviors:InvokeCommandAction Command="{Binding ClickCommand}" />
        </behaviors:EventTrigger>
    </behaviors:Interaction.Triggers>
</Button>

</UserControl>

答案 8 :(得分:0)

正如@Cameron MacFarland所提到的,我只是双向绑定到viewModel上的属性。在属性设置器中,您可以执行所需的任何逻辑,例如根据您的要求添加到联系人列表。

但是,我不一定会调用属性'SelectedItem',因为viewModel不应该知道视图层以及它如何与它的属性进行交互。我称之为CurrentContact之类的东西。

显然这是除非你只想创建命令作为练习等练习。

答案 9 :(得分:0)

这是使用MarkupExtension的实现。尽管具有低级性质(在这种情况下是必需的),但是XAML代码非常简单:

XAML

<SomeControl Click="{local:EventBinding EventToCommand}" CommandParameter="{local:Int32 12345}" />

Marup扩展

public class EventBindingExtension : MarkupExtension
{
    private static readonly MethodInfo EventHandlerImplMethod = typeof(EventBindingExtension).GetMethod(nameof(EventHandlerImpl), new[] { typeof(object), typeof(string) });
    public string Command { get; set; }

    public EventBindingExtension()
    {
    }
    public EventBindingExtension(string command) : this()
    {
        Command = command;
    }

    // Do not use!!
    public static void EventHandlerImpl(object sender, string commandName)
    {
        if (sender is FrameworkElement frameworkElement)
        {
            object dataContext = frameworkElement.DataContext;

            if (dataContext?.GetType().GetProperty(commandName)?.GetValue(dataContext) is ICommand command)
            {
                object commandParameter = (frameworkElement as ICommandSource)?.CommandParameter;
                if (command.CanExecute(commandParameter)) command.Execute(commandParameter);
            }
        }
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget targetProvider &&
            targetProvider.TargetObject is FrameworkElement targetObject &&
            targetProvider.TargetProperty is MemberInfo memberInfo)
        {
            Type eventHandlerType;
            if (memberInfo is EventInfo eventInfo) eventHandlerType = eventInfo.EventHandlerType;
            else if (memberInfo is MethodInfo methodInfo) eventHandlerType = methodInfo.GetParameters()[1].ParameterType;
            else return null;

            MethodInfo handler = eventHandlerType.GetMethod("Invoke");
            DynamicMethod method = new DynamicMethod("", handler.ReturnType, new[] { typeof(object), typeof(object) });

            ILGenerator ilGenerator = method.GetILGenerator();
            ilGenerator.Emit(OpCodes.Ldarg, 0);
            ilGenerator.Emit(OpCodes.Ldstr, Command);
            ilGenerator.Emit(OpCodes.Call, EventHandlerImplMethod);
            ilGenerator.Emit(OpCodes.Ret);

            return method.CreateDelegate(eventHandlerType);
        }
        else
        {
            throw new InvalidOperationException("Could not create event binding.");
        }
    }
}

答案 10 :(得分:0)

我知道现在有点晚了,但是,微软已经将其Xaml.Behaviors开源了,现在只需要一个名称空间就可以轻松使用交互性。

  1. 首先将Microsoft.Xaml.Behaviors.Wpf Nuget包添加到您的项目中。
    https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.Wpf/
  2. 将xmlns:behaviours =“ http://schemas.microsoft.com/xaml/behaviors”名称空间添加到您的 xaml。

然后像这样使用它,

<Button Width="150" Style="{DynamicResource MaterialDesignRaisedDarkButton}">
   <behaviours:Interaction.Triggers>
       <behaviours:EventTrigger EventName="Click">
           <behaviours:InvokeCommandAction Command="{Binding OpenCommand}" PassEventArgsToCommand="True"/>
       </behaviours:EventTrigger>
    </behaviours:Interaction.Triggers>
    Open
</Button>

PassEventArgsToCommand =“ True”应该设置为True,并且实现的RelayCommand可以将RoutedEventArgs或对象作为模板。如果将对象用作参数类型,则将其强制转换为适当的事件类型。

该命令将如下所示

OpenCommand = new RelayCommand<object>(OnOpenClicked, (o) => { return true; });

命令方法如下所示

private void OnOpenClicked(object parameter)
{
    Logger.Info(parameter?.GetType().Name);
}

“参数”将为路由事件对象。

还有日志,以防您好奇

2020-12-15 11:40:36.3600 | INFO | MyApplication.ViewModels.MainWindowViewModel | RoutedEventArgs

您可以看到记录的TypeName是RoutedEventArgs

RelayCommand的实现可以在这里找到。

Why RelayCommand

PS:您可以绑定到任何控件的任何事件。类似于Window的Closing事件,您将获得相应的事件。