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

C# WPF将UI事件绑定到ViewModel中的命令,c#,wpf,mvvm,C#,Wpf,Mvvm,我正在按照MVVM对一个简单的应用程序进行重构,我的问题是如何将SelectionChanged事件从代码中移到viewModel中?我看过一些将元素绑定到命令的示例,但没有完全理解。有人能帮忙吗。谢谢 任何人都可以使用下面的代码提供解决方案吗?非常感谢 public partial class MyAppView : Window { public MyAppView() { InitializeComponent(); this.DataC

我正在按照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;
    }
}
公共部分类MyAppView:窗口
{
公共MyAppView()
{
初始化组件();
this.DataContext=新的MyAppViewModel();
//在此点下方插入创建对象所需的代码。
}
私人无效联系人列表\u选择已更改(对象发送者,System.Windows.Controls.SelectionChangedEventArgs e)
{
//TODO:在此处添加事件处理程序实现。
//对于每个选定的联系人,获取标签并放入集合
ObservableCollection contactListLabels=新的ObservableCollection();
foreach(contactsList中的ContactListModel contactList.SelectedItems)
{
foreach(contactList.AggregatedLabelModel Aggregatabel中的AggregatedLabelModel Aggregatabel)
{
contactListLabels.Add(聚合标签);
}
}
//按名称聚合contactListLabels
ListCollectionView selectedLabelsView=新建ListCollectionView(contactListLabels);
SelectedLabelView.GroupDescriptions.Add(新属性GroupDescription(“名称”);
tagsList.ItemsSource=selectedLabelsView.Groups;
}
}

您的最佳选择是使用
Windows.Interactivity
。使用
EventTriggers
ICommand
附加到任何
RoutedEvent


这里有一篇文章可以帮助您入门:

您应该使用Windows.Interactivity命名空间中的
EventTrigger
InvokeCommandAction
结合使用。以下是一个例子:

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

通过执行
添加引用>程序集>扩展
,可以引用
System.Windows.Interactivity


完整的
i
名称空间是:
xmlns:i=“clr名称空间:System.Windows.Interactivity;assembly=System.Windows.Interactivity”
这个问题也有类似的问题


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

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

但是,我不必调用属性“SelectedItem”,因为viewModel不应该知道视图层以及它如何与其属性交互。我会叫它CurrentContact之类的


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

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

我将遵循本节中的顶部答案

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

这样做意味着你没有任何代码隐藏,xaml相当容易理解,而且这种行为也可以在你的应用程序的其他地方重复使用

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


命令

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

{eb:EventBinding命令=CommandName}

命令参数

$e(事件数)

$this或$this.Property


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

声明绑定行为,如:

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>

考虑一下,它的所有者是
Microsoft
,您可以在该页面中看到它

所有者是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>

这是一个使用
标记扩展的实现。尽管具有低级别的性质(这在本场景中是必需的),但XAML代码非常简单:

XAML

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

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

  • 首先将Microsoft.Xaml.Behaviors.Wpf Nuget packge添加到项目中。
  • 添加xmlns:behaviors=”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>
    
    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 | RoutedEventTargets

    如您所见,记录的类型名为RoutedEventArgs

    在这里可以找到RelayCommand激励


    PS:您可以绑定到任何控件的任何事件。就像关闭窗口的事件一样,您将得到相应的事件。

    我认为,作为一名相当新手的程序员,我正在努力改变思维的部分!我不同意视图模型不应该知道视图层。毕竟,它是视图的一个模型。它不应该在视图中操作对象,但这只是为了在索引中实例化它
    OpenCommand = new RelayCommand<object>(OnOpenClicked, (o) => { return true; });
    
    private void OnOpenClicked(object parameter)
    {
        Logger.Info(parameter?.GetType().Name);
    }