掌握 MVVM Light:.NET 桌面应用开发的 MVVM 利器,掌握 ObservableObject、RelayCommand 和 Messenger

IsInDesignMode 是什么

IsInDesignModeViewModelBase 类提供的一个设计时属性 ,用于判断当前代码是在 Visual Studio 设计器 中运行,还是在真实的应用程序运行时中执行。

它的典型用法是:在设计器模式下显示模拟数据,让界面开发人员可以实时预览布局效果;而在运行时则连接真实的数据源,显示实际业务数据。

csharp 复制代码
public class MainViewModel : ViewModelBase
{
    public ObservableCollection<RecentDocument> RecentDocuments { get; set; }

    public MainViewModel()
    {
        // 通过 IsInDesignMode 区分设计时与运行时
        if (IsInDesignMode)
        {
            // 设计时:加载模拟数据,方便在 XAML 设计器中预览
            RecentDocuments = GetSampleData();
        }
        else
        {
            // 运行时:从真实数据源加载
            RecentDocuments = LoadFromDatabase();
        }
    }
}

在某些静态上下文中,也可以使用静态版本 IsInDesignModeStatic 来判断。


MVVM Light 核心用法全解析

一、数据通知:ObservableObject 与 ViewModelBase

MVVM Light 提供了两个核心基类来处理属性变更通知:

类名 作用 适用场景
ObservableObject 实现 INotifyPropertyChanged,支持属性变更通知 Model(数据模型) 继承
ViewModelBase 继承 ObservableObject,增加 ViewModel 专属功能 ViewModel(视图模型) 继承
csharp 复制代码
// Model 层示例
public class RecentDocument : ObservableObject
{
    private string _name;
    public string Name
    {
        get => _name;
        set => Set(ref _name, value);  // MVVM Light 的 Set 方法自动触发通知
    }

    private string _path;
    public string Path
    {
        get => _path;
        set => Set(ref _path, value);
    }
}

// ViewModel 层示例
public class MainViewModel : ViewModelBase
{
    private string _statusMessage;
    public string StatusMessage
    {
        get => _statusMessage;
        set => Set(ref _statusMessage, value);  // ViewModelBase 同样使用 Set 方法
    }
}

ViewModelBase 除了提供属性变更通知,还增加了 IsInDesignMode 等 ViewModel 专属功能,以及 Cleanup() 方法用于释放资源。


二、命令系统:RelayCommand 与 RelayCommand<T>

MVVM Light 提供了 RelayCommand 类来实现 ICommand 接口,将 UI 交互(如按钮点击、菜单选择)绑定到 ViewModel 中的逻辑,彻底消除 View 代码后置文件的臃肿代码。

基础写法(无参数命令)
csharp 复制代码
public class MainViewModel : ViewModelBase
{
    private RelayCommand _openFileCommand;
    public RelayCommand OpenFileCommand
    {
        get
        {
            return _openFileCommand ?? (_openFileCommand = new RelayCommand(
                () => ExecuteOpenFile(),
                () => CanExecuteOpenFile()
            ));
        }
    }

    private void ExecuteOpenFile()
    {
        // 命令执行逻辑
    }

    private bool CanExecuteOpenFile()
    {
        // 命令可用性判断
        return true;
    }
}
带参数命令(RelayCommand<T>)
csharp 复制代码
public class MainViewModel : ViewModelBase
{
    private RelayCommand<ProgramModel> _deleteProgramCommand;
    public RelayCommand<ProgramModel> DeleteProgramCommand
    {
        get
        {
            return _deleteProgramCommand ?? (_deleteProgramCommand = new RelayCommand<ProgramModel>(
                (program) => ExecuteDeleteProgram(program),
                (program) => CanExecuteDeleteProgram(program)
            ));
        }
    }

    private void ExecuteDeleteProgram(ProgramModel program)
    {
        Programs.Remove(program);
    }

    private bool CanExecuteDeleteProgram(ProgramModel program)
    {
        return program != null;
    }
}

XAML 绑定:

xml 复制代码
<Button Command="{Binding DeleteProgramCommand}" 
        CommandParameter="{Binding SelectedProgram}" 
        Content="删除" />

CommandParameter 将按钮的点击参数传递给命令的 Execute 方法。


三、组件间通信:Messenger 消息机制

Messenger 是 MVVM Light 中实现组件解耦通信 的核心工具,ViewModel 之间View 与 ViewModel 之间都可借助它传递消息,而无需相互持有引用。

通信过程分为三个步骤:

1. 定义消息类型
csharp 复制代码
public class NotificationMessage
{
    public string Content { get; set; }
    public string Source { get; set; }
}
2. 发送消息

在发送消息的 ViewModel 中调用 Send 方法:

csharp 复制代码
// 无 Token 方式
Messenger.Default.Send(new NotificationMessage 
{ 
    Content = "Hello from MainViewModel!", 
    Source = "MainView" 
});

// 带 Token 方式(用于区分不同上下文的同名消息类型)
Messenger.Default.Send(new NotificationMessage { Content = "MyData" }, "MyToken");
3. 接收(订阅)消息

在接收消息的 ViewModel 中注册消息处理回调:

csharp 复制代码
public class ChildViewModel : ViewModelBase
{
    public ChildViewModel()
    {
        // 注册消息订阅
        Messenger.Default.Register<NotificationMessage>(this, HandleMessage);
        
        // 带 Token 的订阅
        Messenger.Default.Register<NotificationMessage>(this, "MyToken", HandleMessage);
    }

    private void HandleMessage(NotificationMessage message)
    {
        // 处理接收到的消息,更新 ViewModel 状态
        StatusMessage = $"收到消息: {message.Content} from {message.Source}";
    }

    // 在 ViewModel 不再需要接收消息时解注册,避免内存泄漏
    public override void Cleanup()
    {
        Messenger.Default.Unregister<NotificationMessage>(this);
        base.Cleanup();
    }
}
使用 Messenger 的核心要点
操作 方法 说明
发送消息 Messenger.Default.Send(message) 发布消息,所有订阅者会收到通知
发送带 Token 的消息 Messenger.Default.Send(message, "token") 用于区分不同上下文的同名消息类型
注册订阅 Messenger.Default.Register<T>(this, callback) 指定消息类型和处理方法
带目标类型的注册 Messenger.Default.Register<T, TTarget>(this, callback) 限制只有特定类型的接收者能处理消息
解注册 Messenger.Default.Unregister<T>(this) 必须调用以避免内存泄漏

⚠️ 重要提示Messenger.Default 是静态的单例实例。在 ViewModel 不再需要接收消息时(例如 ViewModel 被销毁),必须调用 Unregister 方法解除订阅 ,否则会导致 ViewModel 无法被垃圾回收,造成内存泄漏


四、依赖注入与服务管理:SimpleIoc

SimpleIoc 是 MVVM Light 内置的轻量级 IoC 容器,主要用于管理 ViewModel 和服务的注册、解析与生命周期,减少对象之间的硬编码依赖。

完整使用流程

步骤 1:定义模型

csharp 复制代码
public class ProgramModel : ObservableObject
{
    private string _name;
    public string Name { get => _name; set => Set(ref _name, value); }
    
    private string _path;
    public string Path { get => _path; set => Set(ref _path, value); }
}

步骤 2:定义服务接口与实现

csharp 复制代码
// 服务接口
public interface IProgramService
{
    ObservableCollection<ProgramModel> LoadPrograms();
    bool AddProgram(ProgramModel program);
    bool RemoveProgram(string name);
}

// 服务实现
public class ProgramService : IProgramService
{
    public ObservableCollection<ProgramModel> LoadPrograms()
    {
        // 从注册表或数据库读取数据
        var programs = new ObservableCollection<ProgramModel>();
        // ... 数据加载逻辑
        return programs;
    }
    
    public bool AddProgram(ProgramModel program)
    {
        // ... 添加逻辑
        return true;
    }
    
    public bool RemoveProgram(string name)
    {
        // ... 删除逻辑
        return true;
    }
}

步骤 3:在 ViewModelLocator 中注册服务

csharp 复制代码
public class ViewModelLocator
{
    static ViewModelLocator()
    {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
        
        // 注册服务
        SimpleIoc.Default.Register<IProgramService, ProgramService>();
        // 注册 ViewModel
        SimpleIoc.Default.Register<MainViewModel>();
    }
    
    public MainViewModel Main
    {
        get { return SimpleIoc.Default.GetInstance<MainViewModel>(); }
    }
}

步骤 4:在 ViewModel 构造函数中注入服务

csharp 复制代码
public class MainViewModel : ViewModelBase
{
    private readonly IProgramService _programService;
    
    // 构造函数注入
    public MainViewModel(IProgramService programService)
    {
        _programService = programService;
        
        // 使用服务加载数据
        Programs = _programService.LoadPrograms();
    }
}

除了 Messenger.DefaultIsInDesignMode,MVVM Light 框架还有很多实用的核心组件。

📦 MVVM Light 核心功能与类型一览表

🏗️ 基础核心层 (GalaSoft.MvvmLight)

类/接口 作用 核心方法/属性 典型使用场景
ObservableObject 实现 INotifyPropertyChanged,属性变更通知的基类 Set<T>(), RaisePropertyChanged() Model 层数据模型继承,如 RecentDocument
ViewModelBase 继承 ObservableObject,增加 ViewModel 专属功能 IsInDesignMode, Cleanup() ViewModel 层继承,支持设计时数据和资源释放
ICleanup 资源清理接口 Cleanup() ViewModel 释放时清理资源(如取消消息注册)

⚡ 命令系统 (GalaSoft.MvvmLight.Command)

作用 核心方法/属性 典型使用场景
RelayCommand 无参命令实现 ICommand Execute(), CanExecute() 无参数的按钮点击、菜单命令
RelayCommand<T> 泛型命令,支持传递参数 Execute(T), CanExecute(T) 带参数的命令,如 DeleteCommand 传入选中项
EventToCommand 将任意 UI 事件绑定到 Command Command, CommandParameter, PassEventArgsToCommand LoadedSelectionChanged 等事件绑定到 ViewModel 命令

💬 消息系统 (GalaSoft.MvvmLight.Messaging)

作用 核心方法 典型使用场景
Messenger 全局消息总线,解耦组件通信 Send<TMessage>(), Register<TMessage>(), Unregister() ViewModel 之间、View 与 ViewModel 之间传递消息
MessageBase 消息基类 Sender, Target 自定义消息类型的基类
GenericMessage<T> 泛型消息,携带任意类型数据 Content 传递简单数据,如 GenericMessage<string>("保存成功")
NotificationMessage 通知消息,携带字符串通知 Notification 发送纯文本通知,如状态栏消息
NotificationMessage<T> 泛型通知消息 Notification, Content 既有通知文本又携带数据
NotificationMessageAction 带回调的通知消息 Execute() 需要接收方执行回调的场景(如确认对话框)
DialogMessage 对话框消息 Button, Callback ViewModel 触发 View 显示对话框,接收返回结果
PropertyChangedMessage<T> 属性变更消息 OldValue, NewValue, PropertyName 广播属性变更,弱化版 PropertyChanged 事件

🔧 辅助工具层 (GalaSoft.MvvmLight.Threading / Helpers)

作用 核心方法/属性 典型使用场景
DispatcherHelper UI 线程调度器,解决跨线程问题 CheckBeginInvokeOnUI(), RunAsync() 在后台线程中更新 UI 集合或属性
WeakAction 弱引用 Action,防止内存泄漏 Execute(), IsAlive Messenger 内部使用,避免强引用导致的内存泄漏
WeakFunc<TResult> 弱引用 Func Execute(), IsAlive 带返回值的弱引用委托

🧩 IoC 容器层 (GalaSoft.MvvmLight.IoC)

类/接口 作用 核心方法 典型使用场景
ISimpleIoc IoC 容器接口 Register(), GetInstance(), Unregister() 定义依赖注入容器规范
SimpleIoc 轻量级 IoC 容器实现 Register<T, TImplement>(), GetInstance<T>() 注册服务、ViewModel,实现依赖注入

🖥️ View 服务层 (GalaSoft.MvvmLight.Views)

类/接口 作用 核心方法 典型使用场景
IDialogService 对话框服务接口 ShowMessage(), ShowMessageBox() 解耦 ViewModel 与对话框逻辑
INavigationService 页面导航服务接口 Navigate(), GoBack() 用于导航框架(如 NavigationWindow、Frame)

📌 补充说明

1. 框架现状

重要提示 :MVVM Light 已停止更新,官方推荐迁移到 CommunityToolkit.Mvvm(MVVM 工具包)。 但学习 MVVM Light 的设计思想对理解 MVVM 模式仍有帮助。

2. 使用频率参考

使用频率 类/组件
⭐⭐⭐⭐⭐ 天天用 ViewModelBase, RelayCommand, Set(), Messenger.Default
⭐⭐⭐⭐ 常用 ObservableObject, SimpleIoc, DispatcherHelper, IsInDesignMode
⭐⭐⭐ 偶尔用 EventToCommand, ICleanup, IDialogService, NotificationMessage
⭐⭐ 少用 WeakAction, GenericMessage, PropertyChangedMessage
⭐ 极少用 DialogMessage, INavigationService, NotificationMessageAction

3. 与 CommunityToolkit.Mvvm 对照

MVVM Light CommunityToolkit.Mvvm 变化说明
ObservableObject ObservableObject 命名空间不同,功能类似
ViewModelBase ObservableRecipient 概念合并,减少基类数量
Set() SetProperty() 方法名变更,功能相同
RelayCommand RelayCommand 命名空间变更,功能增强
Messenger.Default WeakReferenceMessenger.Default 默认使用弱引用,防止内存泄漏
SimpleIoc Ioc / IServiceProvider 推荐使用 Microsoft.Extensions.DependencyInjection

🔧 实战代码示例

csharp 复制代码
// 完整示例:综合运用多个核心组件

// 1. Model 继承 ObservableObject
public class RecentDocument : ObservableObject
{
    private string _name;
    public string Name 
    { 
        get => _name; 
        set => Set(ref _name, value);  // Set 方法自动触发通知
    }
}

// 2. ViewModel 继承 ViewModelBase
public class MainViewModel : ViewModelBase
{
    private readonly IDialogService _dialogService;
    
    public MainViewModel(IDialogService dialogService)
    {
        _dialogService = dialogService;
        
        // IsInDesignMode:设计时加载模拟数据
        if (IsInDesignMode)
        {
            LoadDesignData();
        }
    }
    
    // RelayCommand:命令绑定
    public RelayCommand SaveCommand => new RelayCommand(
        () => { /* 保存逻辑 */ },
        () => CanSave()
    );
    
    // Messenger:发送消息
    private void OnSaveComplete()
    {
        Messenger.Default.Send(new NotificationMessage("保存成功"));
    }
    
    // DispatcherHelper:跨线程更新UI
    private async Task LoadDataAsync()
    {
        await Task.Run(() => {
            var data = GetDataFromDatabase();
            DispatcherHelper.CheckBeginInvokeOnUI(() => {
                Items.Clear();
                foreach(var item in data) Items.Add(item);
            });
        });
    }
    
    // ICleanup:资源清理
    public override void Cleanup()
    {
        Messenger.Default.Unregister<NotificationMessage>(this);
        base.Cleanup();
    }
}

这个表格涵盖了 MVVM Light 的核心类型,希望能帮助你快速查阅和使用。

完整实战示例:CAD 文件管理器

以下是一个完整的实际开发示例,综合运用 MVVM Light 的所有核心功能。

项目结构

复制代码
CADViewer.GUO/
├── Model/
│   └── RecentDocument.cs          // 继承 ObservableObject
├── ViewModel/
│   ├── MainViewModel.cs            // 主视图模型
│   ├── OpenFileDialogService.cs    // 服务接口与实现
│   └── ViewModelLocator.cs         // IoC 容器配置
└── View/
    └── MainWindow.xaml

1. Model 层

csharp 复制代码
using GalaSoft.MvvmLight;
using System;

namespace CADViewer.GUO.Model
{
    public class RecentDocument : ObservableObject
    {
        private string _name;
        private string _path;
        private bool _isPinned;
        private DateTime _lastAccessTime;

        public string Name
        {
            get => _name;
            set => Set(ref _name, value);
        }

        public string Path
        {
            get => _path;
            set => Set(ref _path, value);
        }

        public bool IsPinned
        {
            get => _isPinned;
            set => Set(ref _isPinned, value);
        }

        public DateTime LastAccessTime
        {
            get => _lastAccessTime;
            set => Set(ref _lastAccessTime, value);
        }
    }
}

2. 服务层(依赖注入)

csharp 复制代码
using GalaSoft.MvvmLight;
using System.Collections.ObjectModel;

namespace CADViewer.GUO.ViewModel
{
    // 服务接口
    public interface IDialogService
    {
        string OpenFileDialog(string filter, string title);
    }

    // 服务实现
    public class DialogService : IDialogService
    {
        public string OpenFileDialog(string filter, string title)
        {
            var dialog = new Microsoft.Win32.OpenFileDialog
            {
                Filter = filter,
                Title = title
            };
            return dialog.ShowDialog() == true ? dialog.FileName : null;
        }
    }
}

3. ViewModel 层(综合运用)

csharp 复制代码
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;

namespace CADViewer.GUO.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        private readonly IDialogService _dialogService;

        // 集合属性
        private ObservableCollection<RecentDocument> _recentDocuments;
        public ObservableCollection<RecentDocument> RecentDocuments
        {
            get => _recentDocuments;
            set => Set(ref _recentDocuments, value);
        }

        // 选中项属性
        private RecentDocument _selectedDocument;
        public RecentDocument SelectedDocument
        {
            get => _selectedDocument;
            set => Set(ref _selectedDocument, value);
        }

        // 状态消息属性
        private string _statusMessage;
        public string StatusMessage
        {
            get => _statusMessage;
            set => Set(ref _statusMessage, value);
        }

        // 构造函数(依赖注入)
        public MainViewModel(IDialogService dialogService)
        {
            _dialogService = dialogService;
            
            // 设计时显示模拟数据
            if (IsInDesignMode)
            {
                LoadDesignTimeData();
            }
            else
            {
                // 运行时加载真实数据
                LoadFromSettings();
            }
            
            // 注册消息接收
            Messenger.Default.Register<NotificationMessage>(this, OnMessageReceived);
        }

        #region 命令定义

        private RelayCommand _openFileCommand;
        public RelayCommand OpenFileCommand => _openFileCommand ?? (_openFileCommand = new RelayCommand(
            execute: ExecuteOpenFile,
            canExecute: () => true
        ));

        private RelayCommand<RecentDocument> _pinDocumentCommand;
        public RelayCommand<RecentDocument> PinDocumentCommand => _pinDocumentCommand ?? (_pinDocumentCommand = new RelayCommand<RecentDocument>(
            execute: ExecutePinDocument,
            canExecute: doc => doc != null
        ));

        private RelayCommand<RecentDocument> _removeDocumentCommand;
        public RelayCommand<RecentDocument> RemoveDocumentCommand => _removeDocumentCommand ?? (_removeDocumentCommand = new RelayCommand<RecentDocument>(
            execute: ExecuteRemoveDocument,
            canExecute: doc => doc != null
        ));

        private RelayCommand _clearAllCommand;
        public RelayCommand ClearAllCommand => _clearAllCommand ?? (_clearAllCommand = new RelayCommand(
            execute: ExecuteClearAll,
            canExecute: () => RecentDocuments?.Count > 0
        ));

        #endregion

        #region 命令执行逻辑

        private void ExecuteOpenFile()
        {
            // 使用依赖注入的服务打开文件选择对话框
            var filePath = _dialogService.OpenFileDialog("DWG Files (*.dwg)|*.dwg", "选择 DWG 文件");
            
            if (string.IsNullOrEmpty(filePath)) return;

            // 创建新的最近文档
            var newDocument = new RecentDocument
            {
                Name = System.IO.Path.GetFileName(filePath),
                Path = filePath,
                LastAccessTime = System.DateTime.Now,
                IsPinned = false
            };

            // 移除已存在的同名文件
            var existing = RecentDocuments.FirstOrDefault(d => d.Path == filePath);
            if (existing != null)
                RecentDocuments.Remove(existing);

            // 插入到固定文档之后的位置
            int insertIndex = RecentDocuments.Count(d => d.IsPinned);
            RecentDocuments.Insert(insertIndex, newDocument);

            // 发送消息通知其他组件(如状态栏、日志记录器等)
            Messenger.Default.Send(new NotificationMessage
            {
                Content = $"已打开文件: {newDocument.Name}",
                Source = "MainViewModel"
            });
        }

        private void ExecutePinDocument(RecentDocument document)
        {
            document.IsPinned = !document.IsPinned;
            ReorderDocuments();
            
            Messenger.Default.Send(new NotificationMessage
            {
                Content = document.IsPinned ? $"已固定 {document.Name}" : $"已取消固定 {document.Name}",
                Source = "MainViewModel"
            }, "StatusMessage");
        }

        private void ExecuteRemoveDocument(RecentDocument document)
        {
            if (MessageBox.Show($"确定移除 {document.Name} 吗?", "确认", 
                MessageBoxButton.YesNo) == MessageBoxResult.Yes)
            {
                RecentDocuments.Remove(document);
                
                Messenger.Default.Send(new NotificationMessage
                {
                    Content = $"已移除: {document.Name}",
                    Source = "MainViewModel"
                }, "StatusMessage");
            }
        }

        private void ExecuteClearAll()
        {
            if (MessageBox.Show("清空所有最近文档记录吗?", "确认", 
                MessageBoxButton.YesNo) == MessageBoxResult.Yes)
            {
                var nonPinned = RecentDocuments.Where(d => !d.IsPinned).ToList();
                foreach (var doc in nonPinned)
                {
                    RecentDocuments.Remove(doc);
                }
            }
        }

        #endregion

        #region 私有方法

        private void LoadDesignTimeData()
        {
            // 设计时模拟数据
            RecentDocuments = new ObservableCollection<RecentDocument>
            {
                new RecentDocument { Name = "Main Building.dwg", Path = "D:\\CAD\\Main Building.dwg", IsPinned = true },
                new RecentDocument { Name = "Electrical Plan.dwg", Path = "D:\\CAD\\Electrical Plan.dwg", IsPinned = false }
            };
        }

        private void LoadFromSettings()
        {
            // 实际应用时从本地存储加载
            RecentDocuments = new ObservableCollection<RecentDocument>();
        }

        private void ReorderDocuments()
        {
            var pinned = RecentDocuments.Where(d => d.IsPinned).OrderByDescending(d => d.LastAccessTime);
            var unpinned = RecentDocuments.Where(d => !d.IsPinned).OrderByDescending(d => d.LastAccessTime);
            
            RecentDocuments.Clear();
            foreach (var doc in pinned) RecentDocuments.Add(doc);
            foreach (var doc in unpinned) RecentDocuments.Add(doc);
        }

        private void OnMessageReceived(NotificationMessage message)
        {
            // 处理接收到的消息
            StatusMessage = $"[{message.Source}] {message.Content}";
        }

        #endregion

        // ViewModel 销毁时清理资源
        public override void Cleanup()
        {
            Messenger.Default.Unregister<NotificationMessage>(this);
            base.Cleanup();
        }
    }
}

4. ViewModelLocator(IoC 容器配置)

csharp 复制代码
using CommonServiceLocator;
using GalaSoft.MvvmLight.Ioc;

namespace CADViewer.GUO.ViewModel
{
    public class ViewModelLocator
    {
        static ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
            
            // 注册服务
            SimpleIoc.Default.Register<IDialogService, DialogService>();
            
            // 注册 ViewModel
            SimpleIoc.Default.Register<MainViewModel>();
        }

        public MainViewModel Main => SimpleIoc.Default.GetInstance<MainViewModel>();
        
        // 清理资源(窗体关闭时调用)
        public static void Cleanup()
        {
            SimpleIoc.Default.Unregister<MainViewModel>();
        }
    }
}

5. App.xaml(注册定位器)

xml 复制代码
<Application x:Class="CADViewer.GUO.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="clr-namespace:CADViewer.GUO.ViewModel"
             StartupUri="View/MainWindow.xaml">
    <Application.Resources>
        <vm:ViewModelLocator x:Key="Locator" />
    </Application.Resources>
</Application>

6. XAML 绑定

xml 复制代码
<Window x:Class="CADViewer.GUO.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
        Title="CAD Viewer" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!-- 工具栏 -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="10">
            <Button Content="打开 DWG" Command="{Binding OpenFileCommand}" Width="100" Margin="5" />
            <Button Content="清空记录" Command="{Binding ClearAllCommand}" Width="100" Margin="5" />
        </StackPanel>

        <!-- 文档列表 -->
        <ListBox Grid.Row="1" ItemsSource="{Binding RecentDocuments}"
                 SelectedItem="{Binding SelectedDocument}" Margin="10">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border Margin="2" Padding="5">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="20" />
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <Image Grid.Column="0" Source="Assets/dwg.png" Width="16" Height="16" />
                            <TextBlock Grid.Column="1" Text="{Binding Name}" Margin="8,0" />
                            <Button Grid.Column="2" Content="📌" 
                                    Command="{Binding DataContext.PinDocumentCommand, 
                                    RelativeSource={RelativeSource AncestorType=Window}}"
                                    CommandParameter="{Binding}" />
                            <Button Grid.Column="3" Content="❌" 
                                    Command="{Binding DataContext.RemoveDocumentCommand,
                                    RelativeSource={RelativeSource AncestorType=Window}}"
                                    CommandParameter="{Binding}" />
                        </Grid>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <!-- 状态栏 -->
        <StatusBar Grid.Row="2">
            <StatusBarItem Content="{Binding StatusMessage}" />
        </StatusBar>
    </Grid>
</Window>

框架对比与现状说明

MVVM Light 是微软早期推出的轻量级 MVVM 框架,适用于中小型 WPF、Silverlight、UWP、Xamarin 等应用开发。它的核心优势是轻量易上手、内置 IoC 容器、命令系统和消息机制

对比维度 MVVM Light CommunityToolkit.Mvvm Prism
当前状态 已停止更新 官方积极维护 持续更新
学习曲线
功能规模 轻量(消息、命令、简单IoC) 轻量(源码生成器、强类型) 全面(模块化、导航)
适用场景 中小型应用 中小型应用 大型复杂应用

由于 MVVM Light 已停止更新,微软官方推荐的替代方案是 CommunityToolkit.Mvvm(MVVM 工具包)。迁移方法主要包括:

MvvmLight 方法 MVVM Toolkit 替代方法
Set<T>(Expression, ref T, T) SetProperty(ref T, T)
RaisePropertyChanged(string) OnPropertyChanged(string)
ObservableObject ObservableObject(命名空间变更)
RelayCommand RelayCommand(命名空间变更)

结语

MVVM Light 作为一个优秀的轻量级 MVVM 框架,以其简洁的 API 设计和实用的功能模块,帮助了无数 WPF 开发者快速上手 MVVM 开发模式。本文详细介绍了其核心功能:IsInDesignMode 用于区分设计时与运行时、ObservableObjectViewModelBase 实现数据通知、RelayCommand 处理命令绑定、Messenger 实现组件间解耦通信、SimpleIoc 管理依赖注入。

如果你正在使用 MVVM Light,建议关注微软官方的 CommunityToolkit.Mvvm,它提供了更好的性能和更现代的开发体验。希望本文能帮助你更深入地理解和使用 MVVM Light 框架,构建出更加优雅、可维护的 WPF 应用。

相关推荐
Ws_2 小时前
WPF 面试题 + 参考答案,偏 C# 桌面端开发高频。
开发语言·c#·wpf
曹牧13 小时前
C#:主线程能够捕获到子线程中的异常
开发语言·数据库·c#
LCG元18 小时前
现代Web应用高可用架构设计与性能调优实战
前端·wpf
jiayong2320 小时前
海量数据处理技术方案与实现原理
大数据·c#·linq
小二·1 天前
向量数据库深度对比:PGVector vs Qdrant vs Milvus vs Chroma(附性能测试数据)
数据库·wpf·milvus
z落落1 天前
C# 类与对象、字段、静态与非静态+四大访问修饰符
开发语言·c#
魔法阵维护师1 天前
从零开发游戏需要学习的c#模块,第三十二章(Boss 战系统)
学习·游戏·c#
魔法阵维护师1 天前
从零开发游戏需要学习的c#模块,第三十三章(暂停菜单)
学习·游戏·c#