目录
[1. MvvmLight 框架准备](#1. MvvmLight 框架准备)
[2. MvvmLight 中的相关基类](#2. MvvmLight 中的相关基类)
[3. MvvmLight 中的数据绑定与通知](#3. MvvmLight 中的数据绑定与通知)
[a. 核心功能](#a. 核心功能)
[b. 关键方法与属性](#b. 关键方法与属性)
[c. 完整示例](#c. 完整示例)
[d. 高级用法](#d. 高级用法)
[4. MvvmLight 中的命令对象](#4. MvvmLight 中的命令对象)
[a. 命令对象的作用](#a. 命令对象的作用)
[b. 核心接口:ICommand](#b. 核心接口:ICommand)
[c. MvvmLight 中的 RelayCommand](#c. MvvmLight 中的 RelayCommand)
[d. 动态更新命令的可执行状态](#d. 动态更新命令的可执行状态)
[e. 高级用法](#e. 高级用法)
[5. Messenger 对象使用](#5. Messenger 对象使用)
[a. Messenger 的核心作用](#a. Messenger 的核心作用)
[b. MvvmLight 中的 Messenger 实现](#b. MvvmLight 中的 Messenger 实现)
[c. 基本使用场景](#c. 基本使用场景)
[d. 高级用法](#d. 高级用法)
[6. DispatcherHelper 对象使用](#6. DispatcherHelper 对象使用)
[7. SimpleIoc 对象使用](#7. SimpleIoc 对象使用)
[a. SimpleIoc 的核心作用](#a. SimpleIoc 的核心作用)
[b. 核心方法与属性](#b. 核心方法与属性)
[c. 基本使用步骤](#c. 基本使用步骤)
[d. 高级用法](#d. 高级用法)
[e. 与 ViewModelLocator 结合使用](#e. 与 ViewModelLocator 结合使用)
- MvvmLight 框架准备
- 作用: 快速搭建 MVVM 架构的应用程序,简化数据绑定、命令和消息传递。
- 步骤 : 通过 NuGet 安装
MvvmLightLibs
包。
2. MvvmLight 中的相关基类
- 核心基类:
-
ViewModelBase
: ViewModel 基类,实现INotifyPropertyChanged
。
cs
public class MainViewModel : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set => Set(ref _name, value); // 自动触发 PropertyChanged
}
}
-
ObservableObject
: 轻量级可观察对象。RelayCommand
: 命令对象基类。
3. MvvmLight 中的数据绑定与通知
a. 核心功能
ViewModelBase
继承自 ObservableObject
,并实现了 INotifyPropertyChanged
接口,主要负责:
- 属性变更通知:当 ViewModel 的某个属性值发生变化时,自动通知 UI 更新。
- 简化代码 :通过
Set
方法简化属性定义,避免手动触发PropertyChanged
事件。 - 设计模式支持 :提供静态属性
IsInDesignMode
,用于区分代码是在设计时(如 Visual Studio 设计器)还是运行时执行。
b. 关键方法与属性
(1) Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
- 作用 :在属性的
set
方法中调用此方法,自动比较新旧值,若不同则更新字段并触发PropertyChanged
事件。 - 示例:
cs
private string _name;
public string Name
{
get => _name;
set => Set(ref _name, value); // 自动触发通知
}
(2)RaisePropertyChanged(string propertyName)
- 作用 :手动触发某个属性的
PropertyChanged
事件。 - 场景:当某个属性的值依赖于其他属性时,手动通知 UI 更新。
cs
public string FullName => $"{FirstName} {LastName}";
private string _firstName;
public string FirstName
{
get => _firstName;
set
{
Set(ref _firstName, value);
RaisePropertyChanged(nameof(FullName)); // 通知 FullName 属性变化
}
}
(3) IsInDesignMode
- 作用:静态属性,判断当前代码是否在设计器(如 Visual Studio 或 Blend)中运行。
- 用途:在设计时提供假数据,避免调用真实服务或数据库。
cs
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
if (IsInDesignMode)
Name = "Design Mode Sample"; // 设计器显示假数据
else
LoadRealData(); // 运行时加载真实数据
}
}
c. 完整示例
cs
using GalaSoft.MvvmLight;
public class UserViewModel : ViewModelBase
{
private string _userName;
private int _age;
public string UserName
{
get => _userName;
set => Set(ref _userName, value);
}
public int Age
{
get => _age;
set => Set(ref _age, value);
}
// 计算属性(依赖其他属性)
public string UserInfo => $"{UserName} (Age: {Age})";
// 当 UserName 或 Age 变化时,手动通知 UserInfo 更新
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == nameof(UserName) || propertyName == nameof(Age))
RaisePropertyChanged(nameof(UserInfo));
}
}
d. 高级用法
(1)批量通知多个属性
通过 RaisePropertyChanged(null)
或指定空字符串,通知所有属性更新(慎用,可能影响性能):
cs
public void ResetAllProperties()
{
_userName = "Default";
_age = 0;
RaisePropertyChanged(""); // 通知所有属性更新
}
(2) 继承与扩展
可继承 ViewModelBase
并添加通用逻辑(如日志记录、验证):
cs
public abstract class CustomViewModelBase : ViewModelBase
{
protected void LogPropertyChange(string propertyName)
{
Debug.WriteLine($"属性 {propertyName} 已更新");
}
public override void RaisePropertyChanged(string propertyName = null)
{
base.RaisePropertyChanged(propertyName);
LogPropertyChange(propertyName);
}
}
4. MvvmLight 中的命令对象
a. 命令对象的作用
- 解耦 UI 与业务逻辑:将用户操作(如点击按钮)映射到 ViewModel 的方法。
- 控制可执行状态 :根据条件动态启用或禁用 UI 元素(例如按钮的
IsEnabled
)。 - 支持参数传递:允许从 UI 传递参数到 ViewModel(如选中项的 ID)。
b. 核心接口:ICommand
所有命令对象均实现 System.Windows.Input.ICommand
接口,其定义如下:
cs
public interface ICommand
{
event EventHandler CanExecuteChanged; // 通知命令可执行状态变化
bool CanExecute(object parameter); // 判断命令是否可执行
void Execute(object parameter); // 执行命令逻辑
}
c. MvvmLight 中的 RelayCommand
MvvmLight 提供 RelayCommand
和 RelayCommand<T>
类,简化了 ICommand
的实现。
(1) 基本用法(无参数)
cs
public class MainViewModel : ViewModelBase
{
public RelayCommand SaveCommand { get; }
public MainViewModel()
{
// 初始化命令:绑定方法 + 可执行条件
SaveCommand = new RelayCommand(SaveData, CanSave);
}
private void SaveData()
{
// 保存逻辑
}
private bool CanSave()
{
return !string.IsNullOrEmpty(Name); // 仅当 Name 非空时按钮可用
}
}
(2) 支持参数传递(RelayCommand<T>)
cs
public RelayCommand<string> FilterCommand { get; }
public MainViewModel()
{
FilterCommand = new RelayCommand<string>(param => ApplyFilter(param), param => !string.IsNullOrEmpty(param));
}
private void ApplyFilter(string keyword)
{
// 根据关键字过滤数据
}
d. 动态更新命令的可执行状态
(1) 手动触发更新
当 CanExecute
依赖的属性变化时,调用 RelayCommand
的 RaiseCanExecuteChanged
方法:
cs
private string _name;
public string Name
{
get => _name;
set
{
Set(ref _name, value);
SaveCommand.RaiseCanExecuteChanged(); // 触发重新检查 CanSave
}
}
(1) 自动触发更新
利用 ViewModelBase
的 Set
方法自动触发属性变更通知,无需手动调用 RaiseCanExecuteChanged
:
cs
private string _name;
public string Name
{
get => _name;
set => Set(ref _name, value); // Set 方法已自动触发 PropertyChanged
}
// CanSave 方法中依赖 Name 属性
private bool CanSave() => !string.IsNullOrEmpty(Name);
e. 高级用法
(1) 异步命令
直接在命令中执行异步操作时,需注意线程安全(通过 DispatcherHelper
):
cs
public RelayCommand LoadDataCommand { get; }
public MainViewModel()
{
LoadDataCommand = new RelayCommand(async () => await LoadDataAsync());
}
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
var data = await _dataService.GetData();
// 更新 UI(确保在 UI 线程)
DispatcherHelper.CheckBeginInvokeOnUI(() => DataList = data);
}
finally
{
IsLoading = false;
}
}
(2) 复合命令
将多个命令组合成一个逻辑操作:
cs
public RelayCommand SubmitAllCommand { get; }
public MainViewModel()
{
SubmitAllCommand = new RelayCommand(() =>
{
SaveCommand.Execute(null);
LogCommand.Execute(null);
}, () => SaveCommand.CanExecute(null) && LogCommand.CanExecute(null));
}
(3) 命令的泛型约束
cs
public RelayCommand<int> DeleteItemCommand { get; }
public MainViewModel()
{
DeleteItemCommand = new RelayCommand<int>(id => DeleteItem(id), id => id > 0);
}
private void DeleteItem(int itemId)
{
// 删除指定 ID 的项
}
5. Messenger 对象使用
在 MVVM 模式 中,Messenger 是一个用于实现 松耦合通信 的核心组件,尤其在 MvvmLight 框架 中被广泛使用。它允许不同组件(如 ViewModel、View、服务)之间通过消息进行通信,而无需直接引用彼此,从而降低依赖、提升代码可维护性。
a. Messenger 的核心作用
- 解耦组件通信:组件无需持有对方引用,通过消息订阅/发布机制交互。
- 跨层级传递数据:例如从子 ViewModel 通知父 ViewModel,或跨页面传递状态。
- 支持复杂场景:如广播通知、请求-响应模式、事件聚合等。
b. MvvmLight 中的 Messenger 实现
MvvmLight 的 Messenger
类是一个静态单例(Messenger.Default
),提供消息的注册、发送和注销功能。其核心方法如下:
|-----------------------------------------------------------------|-------------------------------------------------------|
| 方法 | 作用 |
| Register<TMessage>(object recipient, Action<TMessage> action)
| 订阅类型为 TMessage
的消息。recipient
为接收者标识(通常为 this
)。 |
| Send<TMessage>(TMessage message)
| 发送一条 TMessage
类型的消息,所有订阅者会收到通知。 |
| Unregister(object recipient)
| 注销某个接收者的所有消息订阅,避免内存泄漏。 |
c. 基本使用场景
(1) 发送简单通知消息
- 场景:ViewModel A 完成数据加载后,通知 ViewModel B 刷新界面。
发送消息方:
cs
// 发送一个无参数通知
Messenger.Default.Send(new NotificationMessage("DataLoaded"));
接收消息方(ViewModel B):
cs
public ViewModelB()
{
// 注册接收 NotificationMessage 类型的消息
Messenger.Default.Register<NotificationMessage>(this, message =>
{
if (message.Notification == "DataLoaded")
{
RefreshData();
}
});
}
(2) 传递数据对象
- 场景:用户选择某条数据后,跨页面传递选中项。
定义消息类型:
cs
public class ItemSelectedMessage
{
public int ItemId { get; set; }
public ItemSelectedMessage(int id) => ItemId = id;
}
发送方:
cs
// 用户选择某项后发送消息
Messenger.Default.Send(new ItemSelectedMessage(selectedItem.Id));
接收方:
cs
Messenger.Default.Register<ItemSelectedMessage>(this, message =>
{
LoadItemDetails(message.ItemId); // 根据 ItemId 加载详情
});
d. 高级用法
(1) 消息令牌(Token)
- 作用:区分相同消息类型的不同用途,避免消息冲突。
发送带令牌的消息:
cs
// 发送消息时指定令牌
Messenger.Default.Send(
new NotificationMessage("UpdateChart"),
"ChartToken" // 令牌标识
);
接收指定令牌的消息:
cs
Messenger.Default.Register<NotificationMessage>(
this,
message => UpdateChartData(),
"ChartToken" // 仅接收带有此令牌的消息
);
(2) 泛型消息
- 场景:传递强类型数据,避免类型转换。
定义泛型消息:
cs
public class GenericMessage<T>
{
public T Content { get; }
public GenericMessage(T content) => Content = content;
}
发送泛型消息:
cs
var data = new List<User>();
Messenger.Default.Send(new GenericMessage<List<User>>(data));
接收泛型消息:
cs
Messenger.Default.Register<GenericMessage<List<User>>>(this, message =>
{
UserList = message.Content; // 直接获取 List<User>
});
(3) 双向通信(请求-响应模式)
- 场景:ViewModel A 请求数据,ViewModel B 响应返回结果。
发送请求消息:
cs
public class DataRequestMessage
{
public Action<string> Callback { get; set; } // 定义回调委托
}
// 发送请求,并注册回调
Messenger.Default.Send(new DataRequestMessage
{
Callback = response => HandleResponse(response)
});
接收请求并响应:
cs
Messenger.Default.Register<DataRequestMessage>(this, message =>
{
var data = FetchDataFromService(); // 获取数据
message.Callback?.Invoke(data); // 执行回调
});
6. DispatcherHelper 对象使用
- 作用: 在非 UI 线程更新 UI。
- 初始化:
cs
DispatcherHelper.Initialize(); // 在 App.xaml.cs 中调用
- 使用:
cs
Task.Run(() =>
{
// 后台线程操作
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
// 更新 UI 元素
StatusText = "Processing...";
});
});
7. SimpleIoc 对象使用
SimpleIoc
是 MvvmLight 框架中提供的一个轻量级依赖注入(Dependency Injection, DI)容器,用于管理应用程序中各个组件(如 ViewModel、服务、数据源)的依赖关系。它通过 控制反转(IoC) 和 依赖注入 机制,帮助开发者实现代码解耦、提高可测试性和可维护性。
a. SimpleIoc 的核心作用
- 解耦组件依赖:将类的依赖关系从代码中抽离,通过容器统一管理。
- 单例生命周期管理:默认以单例模式提供实例,避免重复创建对象。
- 简化实例获取:通过接口或类型直接获取已注册的实例。
- 支持构造函数注入:自动解析构造函数参数,完成依赖注入。
b. 核心方法与属性
以下是 SimpleIoc
的常用方法及其功能:
|----------------------------------|------------------------------------|
| 方法/属性 | 作用 |
| Register<TInterface, TClass>()
| 注册接口 TInterface
和其实现类 TClass
。 |
| Register<TClass>()
| 直接注册类型 TClass
(无接口)。 |
| GetInstance<T>()
| 获取类型 T
的实例(已注册的接口或类)。 |
| ContainsCreated<T>()
| 检查类型 T
的实例是否已被创建。 |
| Reset()
| 重置容器,清空所有注册和实例(用于测试或重新初始化)。 |
| IsRegistered<T>()
| 检查类型 T
是否已注册。 |
c. 基本使用步骤
(1) 注册服务与 ViewModel
在应用程序启动时(如 ViewModelLocator
或 App.xaml.cs
中),注册所有依赖项:
cs
public class ViewModelLocator
{
public ViewModelLocator()
{
// 注册服务(接口 + 实现类)
SimpleIoc.Default.Register<IDataService, DataService>();
// 注册 ViewModel(无接口)
SimpleIoc.Default.Register<MainViewModel>();
}
// 提供 ViewModel 实例的公共属性
public MainViewModel Main => SimpleIoc.Default.GetInstance<MainViewModel>();
}
(2) 获取实例
通过 GetInstance<T>
方法获取已注册的实例:
cs
// 获取 ViewModel 实例
var mainVM = SimpleIoc.Default.GetInstance<MainViewModel>();
// 获取服务实例
var dataService = SimpleIoc.Default.GetInstance<IDataService>();
(3) 构造函数注入
当注册的类(如 ViewModel)依赖其他服务时,SimpleIoc
会自动解析构造函数参数:
cs
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
// 构造函数依赖注入:自动传入已注册的 IDataService 实例
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
}
}
d. 高级用法
(1) 单例模式 vs. 瞬时模式
单例模式(默认):整个应用程序生命周期内只创建一个实例。
cs
// 默认单例模式
SimpleIoc.Default.Register<IDataService, DataService>();
瞬时模式 :每次调用 GetInstance
时创建新实例。
cs
SimpleIoc.Default.Register<IDataService, DataService>(createInstanceImmediately: false);
var service = SimpleIoc.Default.GetInstance<IDataService>(); // 每次返回新实例
(2) 手动指定实例
允许直接注册一个已存在的对象实例:
cs
var logger = new FileLogger();
SimpleIoc.Default.Register<ILogger>(() => logger); // 注册现有实例
(3) 依赖覆盖
在测试中,可以替换实现类以注入 Mock 对象:
cs
// 生产环境注册真实服务
SimpleIoc.Default.Register<IDataService, DataService>();
// 测试环境覆盖为 Mock 服务
SimpleIoc.Default.Unregister<IDataService>();
SimpleIoc.Default.Register<IDataService, MockDataService>();
e. 与 ViewModelLocator 结合使用
ViewModelLocator
是 MvvmLight 中用于集中管理 ViewModel 的类,通常与 SimpleIoc
配合使用,通过 XAML 绑定 ViewModel。
(1) 定义 ViewModelLocator
cs
public class ViewModelLocator
{
public ViewModelLocator()
{
// 注册服务和 ViewModel
SimpleIoc.Default.Register<IDataService, DataService>();
SimpleIoc.Default.Register<MainViewModel>();
}
// 暴露 ViewModel 属性供 XAML 绑定
public MainViewModel Main => SimpleIoc.Default.GetInstance<MainViewModel>();
}
(2) 在 XAML 中声明资源
在 App.xaml
中合并 ViewModelLocator
:
cs
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</Application.Resources>
(3) 在 View 中绑定 ViewModel
cs
<Window
DataContext="{Binding Main, Source={StaticResource Locator}}">
<!-- UI 元素绑定到 MainViewModel 的属性 -->
</Window>