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

IsInDesignMode 是什么

IsInDesignMode 是 ViewModelBase 类提供的一个设计时属性 ,用于判断当前代码是在 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.Default 和 IsInDesignMode,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 将 Loaded、SelectionChanged 等事件绑定到 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 用于区分设计时与运行时、ObservableObject 和 ViewModelBase 实现数据通知、RelayCommand 处理命令绑定、Messenger 实现组件间解耦通信、SimpleIoc 管理依赖注入。

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

相关推荐
samble11 分钟前
从零搭建灌装监控系统(十):生产追溯,条码触发自动记录
c#·wpf·mvvm·modbus·工业控制
czhc11400756635 小时前
退出防呆:用“总闸“收口所有退出入口,以及三个并发语法坑
开发语言·c#
淡海水7 小时前
02-01-原理篇-Mark-Sweep与变种算法
算法·unity·c#·游戏引擎·.net·gc
UIU11411 小时前
同一个变量在两个 .c 文件里类型不一致,程序会怎样?
c++·学习·c#
波力海苔夹心脆6751 天前
C# 值类型与引用类型详解:存储位置、赋值机制、参数传递、相等比较、装箱拆箱与常见陷阱
开发语言·jvm·经验分享·笔记·c#·.net
估值探索者1 天前
【Python量化策略实战 #02】多因子合成mom和vol两因子打分合成
开发语言·c++·python·数据挖掘·c#
淡海水1 天前
01-04-认知篇-Unity内存全景
unity·c#·游戏引擎·.net·gc
淡海水2 天前
01-03-认知篇-C#内存模型深度解析
unity·c#·游戏引擎·.net·gc
lzhdim2 天前
C#不为人知的10个魔法特性:资深开发者也会震惊的底层奥秘
java·前端·javascript·算法·c#
SamChan902 天前
PDF页面旋转与CropBox陷阱:译文回填位置总是错位的根因实测
python·ai·pdf·wpf