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 应用。