WPF在ViewModel中处理View中的事件

本文介绍了一种在WPF应用程序中使用MVVM模式处理视图事件的最佳实践——通过Behavior方式。详细展示了如何利用CallMethodAction和InvokeCommandAction来在ViewModel中响应视图事件,并给出了具体的XAML和C#代码示例。

序言

WPF VM中处理V的事件,这是一个老生常谈的问题。
无论什么时候总能在网上搜到一些MVVM的理想主义者,企图用"你程序设计不合理“去逃避这个问题。但是现实是这个问题确实存在,我们有时候也确实需要去解决。
这里我不想依次陈列出各种方案,因为这是9102年的文章,相信读者也都能搜到各种历史资料。
最佳实践就是微软官方的Behavior的方式。这里主要结合历史资料讲一下使用方法(老瓶装新酒…)。

正文

首先是引用库,老文章可能会让使用Blend SDK,但是微软已开源了XamlBehaviorsWpf项目,并提供了官方NuGet包。
这里直接搜索 Microsoft.Xaml.Behaviors 选择对应的包即可,这里我们使用 Microsoft.Xaml.Behaviors.Wpf
在ViewModel中处理事件我们可以通过 CallMethodActionInvokeCommandAction 两种方式来解决

View的xaml里面

<Window x:Class="TestBehavior.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:b="http://schemas.microsoft.com/xaml/behaviors"
        Title="MainWindow"
        Width="100"
        Height="100"
        mc:Ignorable="d">
    <Grid>
        <Rectangle x:Name="Element"
                   Width="50"
                   Height="50"
                   Fill="Red">
            <b:Interaction.Triggers>
                <b:EventTrigger EventName="MouseLeftButtonDown">
                    <b:CallMethodAction TargetObject="{Binding}" MethodName="NoParameMethod" />
                </b:EventTrigger>
                <b:EventTrigger EventName="MouseLeftButtonDown">
                    <b:CallMethodAction TargetObject="{Binding}" MethodName="OriginParameMethod" />
                </b:EventTrigger>
                <b:EventTrigger EventName="MouseLeftButtonDown">
                    <b:InvokeCommandAction Command="{Binding NoParameCommand}" />
                </b:EventTrigger>
                <b:EventTrigger EventName="MouseLeftButtonDown">
                    <b:InvokeCommandAction Command="{Binding OneParameCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}}" />
                </b:EventTrigger>
            </b:Interaction.Triggers>
        </Rectangle>
    </Grid>
</Window>

ViewModel的cs文件里面

        /// <summary>
        /// 由事件触发无参数的方法
        /// </summary>
        public void NoParameMethod()
        {
            Trace.TraceInformation(nameof(NoParameMethod));
        }

        /// <summary>
        /// 由事件触发和事件一致参数的方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OriginParameMethod(object sender, MouseButtonEventArgs e)
        {
            Trace.TraceInformation($"{nameof(OriginParameMethod)} {sender.GetType()}  {e.GetPosition(sender as IInputElement)} ");
        }

        /// <summary>
        /// 由事件触发无参数的命令
        /// </summary>
        public ICommand NoParameCommand
        {
            get
            {
                return new ActionCommand(() =>
                {
                    Trace.TraceInformation(nameof(NoParameCommand));
                });
            }
        }

        /// <summary>
        /// 由事件触发有一个参数的命令
        /// </summary>
        public ICommand OneParameCommand
        {
            get
            {
                return new ActionCommand(_ =>
                {
                    Trace.TraceInformation($"{nameof(OneParameCommand)} {_.GetType()}");
                });
            }
        }

调试输出

TestBehavior.exe Information: 0 : NoParameMethod
TestBehavior.exe Information: 0 : OriginParameMethod System.Windows.Shapes.Rectangle  30,33.5 
TestBehavior.exe Information: 0 : NoParameCommand
TestBehavior.exe Information: 0 : OneParameCommand System.Windows.Controls.Grid

拓展

某些应用场景,可能需要多个事件联合处理,比如复杂的画图板功能,地图软件的标注功能等,需要对鼠标点击并拖动,鼠标按住左键并拖动等组合操作实现不同的功能
可以继承并自定义Behavior,在Behavior中能和获取View,并注册各种事件,实现多个事件的联合处理。

如果说我看得比别人更远些,那是因为我站在巨人的肩膀上。 --牛顿

### 实现 WPF ViewModel 调用 View 的方法 在 WPF 中,遵循 MVVM(Model-View-ViewModel)设计模式时,通常希望保持 ViewModelView 之间的松耦合关系。然而,在某些情况下,可能需要从 ViewModel 执行一些特定的操作来影响 View 的行为,比如关闭窗口、显示消息框等。 #### 方法一:定义接口并使用依赖注入 一种常用的做法是通过定义一个用于关闭窗口的接口,并将其实例传递给 ViewModel。这样可以确保 ViewModel 不直接引用具体的 UI 组件[^1]。 ```csharp // 定义接口 IWindowManager public interface IWindowManager { void Close(Window window); } // 在 ViewModel 中注入 IWindowManager public class MainViewModel : INotifyPropertyChanged { private readonly IWindowManager _windowManager; private Window _associatedWindow; public MainViewModel(IWindowManager windowManager) { _windowManager = windowManager; } public ICommand CloseCommand => new RelayCommand(Close); private void Close() { if (_associatedWindow != null) _windowManager.Close(_associatedWindow); } } ``` 在此实现中,`IWindowManager` 是负责管理窗口生命周期的服务,而 `_associatedWindow` 则是在初始化 ViewModel 或绑定过程中设置的具体窗口实例[^1]。 --- #### 方法二:利用 `CallMethodAction` 调用 ViewModel 方法 如果需要响应某个事件并通过 ViewModel 处理逻辑,则可以通过 `System.Windows.Interactivity` 提供的功能实现这一点。例如,使用 `EventTrigger` 配合 `InvokeCommandAction` 可以轻松触发 ViewModel 中的方法[^2]。 ```xml <i:Interaction.Triggers> <i:EventTrigger EventName="SomeCustomEvent"> <ei:CallMethodAction TargetObject="{Binding}" MethodName="HandleEvent"/> </i:EventTrigger> </i:Interaction.Triggers> ``` 上述代码片段展示了如何监听视图中的自定义事件并将该事件映射到 ViewModel 的指定方法上[^2]。 --- #### 方法三:借助 Prism 框架简化交互 Prism 是一款支持 MVVM 开发的强大框架,它提供了许多工具帮助开发者更方便地处理 ViewModelView 间的通信。例如,通过继承 `BindableBase` 并结合 `DelegateCommand`,可以在 ViewModel 中声明命令并与按钮或其他控件绑定[^3]。 ```csharp using Prism.Commands; using Prism.Mvvm; public class MainWindowViewModel : BindableBase { private DelegateCommand _closeCommand; public DelegateCommand CloseCommand => _closeCommand ??= new DelegateCommand(() => { // 关闭当前窗口的逻辑 }); } ``` 与此同时,还需要在 XAML 文件中配置数据上下文以及绑定: ```xml <Window.DataContext> <local:MainWindowViewModel /> </Window.DataContext> <Button Content="Close" Command="{Binding CloseCommand}" /> ``` 这种方式不仅简洁明了,还充分利用了 Prism 提供的基础功能[^3]。 --- #### 方法四:基于事件触发器操作控件 对于更加复杂的场景,可以直接通过附加行为或者事件触发器完成对控件的行为控制。下面是一个例子,展示如何在 ViewModel 加载完成后执行某种动作[^4]。 ```xml <i:Interaction.Triggers> <i:EventTrigger EventName="Loaded"> <i:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=WMPPlayer}"/> </i:EventTrigger> </i:Interaction.Triggers> ``` 这里的关键在于将事件名称 (`Loaded`) 映射至相应的命令属性 (如 `LoadedCommand`) 上面去。 --- ### 总结 以上介绍了四种不同的技术手段来实现在 WPF 应用程序中由 ViewModel 主动调用或间接作用于 View 的目的。每种方案都有其适用范围和优缺点,实际项目开发当中可以根据具体情况灵活选用合适的策略。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值