You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现ViewModel间通信?(Prism事件聚合器可行吗?)

嘿,作为刚上手C#和WPF的新手就敢直接冲MVVM,这勇气必须给你点个赞!虽然把小应用搞复杂了,但确实是练手的绝佳机会——我当初学MVVM的时候,也在ViewModel之间的交互这儿卡了好久,太懂这种摸不着头脑的迷茫了😂

下面给你分享几种实用的解决方案,都是我踩坑后总结出来的,适合新手快速上手:

解决ViewModel间交互的核心思路:解耦

MVVM的核心就是解耦,所以绝对不能让两个ViewModel直接互相引用,不然就违背模式的初衷了。常用的方法都是通过一个中间“桥梁”来传递消息,下面分三种情况说:

1. 自己实现极简版“信使”(Messenger)模式

不用引入任何第三方框架,自己写几十行代码就能搞定,新手友好还能加深理解。

先写一个全局的Messenger类,负责消息的订阅和发送:

public static class Messenger
{
    // 存储不同类型消息的订阅者
    private static readonly Dictionary<Type, List<Action<object>>> _subscribers = new();

    // 订阅指定类型的消息
    public static void Subscribe<T>(Action<T> action)
    {
        var messageType = typeof(T);
        if (!_subscribers.ContainsKey(messageType))
        {
            _subscribers[messageType] = new List<Action<object>>();
        }
        _subscribers[messageType].Add(obj => action((T)obj));
    }

    // 发送指定类型的消息
    public static void Send<T>(T message)
    {
        var messageType = typeof(T);
        if (_subscribers.TryGetValue(messageType, out var actions))
        {
            // 用ToList避免遍历过程中集合被修改
            foreach (var action in actions.ToList())
            {
                action(message);
            }
        }
    }

    // 取消订阅,一定要记得调用,防止内存泄漏
    public static void Unsubscribe<T>(Action<T> action)
    {
        var messageType = typeof(T);
        if (_subscribers.TryGetValue(messageType, out var actions))
        {
            actions.RemoveAll(a => a.Target == action.Target && a.Method == action.Method);
            if (actions.Count == 0)
            {
                _subscribers.Remove(messageType);
            }
        }
    }
}

然后定义一个消息类,用来传递绘制矩形的参数:

public class DrawRectangleMessage
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Width { get; set; }
    public double Height { get; set; }
    public Brush Fill { get; set; }
}

接下来在OverlayViewModel里订阅消息,收到消息后添加矩形数据:

public class OverlayViewModel : INotifyPropertyChanged
{
    private ObservableCollection<RectangleModel> _rectangles;
    public ObservableCollection<RectangleModel> Rectangles
    {
        get => _rectangles;
        set { _rectangles = value; OnPropertyChanged(); }
    }

    public OverlayViewModel()
    {
        Rectangles = new ObservableCollection<RectangleModel>();
        // 订阅绘制矩形的消息
        Messenger.Subscribe<DrawRectangleMessage>(msg =>
        {
            Rectangles.Add(new RectangleModel
            {
                X = msg.X,
                Y = msg.Y,
                Width = msg.Width,
                Height = msg.Height,
                Fill = msg.Fill
            });
        });
    }

    // 实现INotifyPropertyChanged接口,通知UI更新
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    // 建议在View的Unloaded事件里取消订阅,比析构函数更可靠
    public void Cleanup()
    {
        Messenger.Unsubscribe<DrawRectangleMessage>(msg => 
        {
            Rectangles.Add(new RectangleModel
            {
                X = msg.X,
                Y = msg.Y,
                Width = msg.Width,
                Height = msg.Height,
                Fill = msg.Fill
            });
        });
    }
}

在MainWindowViewModel里,需要绘制矩形的时候就发送消息:

public class MainWindowViewModel : INotifyPropertyChanged
{
    public ICommand DrawRectangleCommand { get; }

    public MainWindowViewModel()
    {
        DrawRectangleCommand = new RelayCommand(() =>
        {
            // 这里可以替换成你实际获取的矩形参数
            var drawMsg = new DrawRectangleMessage
            {
                X = 50,
                Y = 50,
                Width = 100,
                Height = 80,
                Fill = Brushes.LightBlue
            };
            // 发送消息
            Messenger.Send(drawMsg);
        });
    }

    // 同样实现INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

顺便给你一个简单的RelayCommand实现(如果不用第三方框架的话):

public class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action execute, Func<bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute?.Invoke() ?? true;
    }

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

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

最后在OverlayView里绑定数据,用Canvas和ItemsControl绘制矩形:

<Canvas>
    <ItemsControl ItemsSource="{Binding Rectangles}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Rectangle Width="{Binding Width}"
                           Height="{Binding Height}"
                           Fill="{Binding Fill}">
                    <Canvas.Left>
                        <Binding Path="X"/>
                    </Canvas.Left>
                    <Canvas.Top>
                        <Binding Path="Y"/>
                    </Canvas.Top>
                </Rectangle>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Canvas>

2. 用第三方MVVM框架的现成工具

如果不想自己写Messenger,也可以用成熟的框架,省很多事:

  • MVVM Light:自带Messenger.Default,用法和上面自己写的差不多,还支持弱引用,更不容易内存泄漏。先安装NuGet包MvvmLight,然后发送消息用Messenger.Default.Send(...),订阅用Messenger.Default.Register<DrawRectangleMessage>(this, msg => { /* 处理逻辑 */ }),销毁时用Messenger.Default.Unregister(this)。
  • Prism:适合复杂一点的应用,用IEventAggregator来发布订阅事件,解耦性更强,不过学习成本稍微高一点。

3. 共享状态服务(超简单的小应用专属)

如果你的应用特别小,也可以搞一个单例的状态服务,两个ViewModel都引用它,通过服务的事件来传递消息:

public class DrawingStateService
{
    // 单例模式,保证全局只有一个实例
    public static DrawingStateService Instance => _instance ??= new DrawingStateService();
    private static DrawingStateService _instance;

    // 定义事件,OverlayViewModel订阅这个事件
    public event Action<DrawRectangleMessage> RectangleDrawRequested;

    // MainWindowViewModel调用这个方法触发事件
    public void RequestDrawRectangle(DrawRectangleMessage msg)
    {
        RectangleDrawRequested?.Invoke(msg);
    }
}

然后在MainWindowViewModel里调用DrawingStateService.Instance.RequestDrawRectangle(...),OverlayViewModel的构造函数里订阅DrawingStateService.Instance.RectangleDrawRequested += (msg) => { /* 添加矩形逻辑 */ }。这种方式最简单,但耦合度比信使模式高一点,适合小应用快速实现。

最后提醒两句

  • 不管用哪种方式,一定要记得取消订阅/移除事件处理程序,不然ViewModel销毁不了会导致内存泄漏!
  • 尽量让每个ViewModel只负责自己的逻辑,不要互相依赖,这样后续扩展起来才不会头疼。

内容的提问来源于stack exchange,提问作者Kyle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:59:16