![]() |
VOOZH | about |
dotnet add package HansCnc.Mvvm.WPF --version 0.3.0-preview1
NuGet\Install-Package HansCnc.Mvvm.WPF -Version 0.3.0-preview1
<PackageReference Include="HansCnc.Mvvm.WPF" Version="0.3.0-preview1" />
<PackageVersion Include="HansCnc.Mvvm.WPF" Version="0.3.0-preview1" />Directory.Packages.props
<PackageReference Include="HansCnc.Mvvm.WPF" />Project file
paket add HansCnc.Mvvm.WPF --version 0.3.0-preview1
#r "nuget: HansCnc.Mvvm.WPF, 0.3.0-preview1"
#:package HansCnc.Mvvm.WPF@0.3.0-preview1
#addin nuget:?package=HansCnc.Mvvm.WPF&version=0.3.0-preview1&prereleaseInstall as a Cake Addin
#tool nuget:?package=HansCnc.Mvvm.WPF&version=0.3.0-preview1&prereleaseInstall as a Cake Tool
轻量级 WPF MVVM 框架:Roslyn 源生成器 + 对话框服务,基于 CommunityToolkit.Mvvm、Autofac、R3。
开发环境:Git 仓库根目录为
HansCnc.Mvvm/(本目录)。若 IDE 工作区打开的是外层文件夹,请在终端中cd到本目录再执行git/dotnet。详见 与 。
| 项目 | 说明 |
|---|---|
HansCnc.Core |
领域标记接口(IModel / IDto / IEntity 等),NuGet HansCnc.Core |
HansCnc.DataCollection |
数据采集运行时 + NotifyList<T>,NuGet HansCnc.DataCollection |
HansCnc.Mvvm |
MVVM 核心 + 内置 Roslyn 源生成器(含 DataCollection 节点/根生成器) |
HansCnc.Mvvm.WPF |
WPF 实现:DialogService、DialogViewModelBase、WhenActivated、MainThreadHelper |
HansCnc.Mvvm.SourceGenerators |
共享生成器源码(仓库内;已打包进 HansCnc.Mvvm NuGet) |
HansCnc.Mvvm.SourceGenerators.Package |
历史打包布局参考(不再单独发布) |
HansCnc.Mvvm.Samples |
示例 WPF 应用 |
HansCnc.Mvvm.*.Tests |
单元 / 集成测试(含 HansCnc.Mvvm.Integration.Tests) |
<PackageReference Include="HansCnc.Mvvm" Version="0.3.0-preview1" />
<PackageReference Include="HansCnc.Mvvm.WPF" Version="0.3.0-preview1" />
完整版说明(HTML,随发布更新):。全量变更记录:。
HansCnc.Mvvm 已包含 Roslyn 分析器(analyzers/dotnet/roslyn*/cs),无需再引用 HansCnc.Mvvm.SourceGenerators。
WPF 应用还需 UseWPF、Autofac、R3 等(见下方依赖)。从旧版三件套迁移见 。
HansCnc.Mvvm.Samples 引用生成器项目以便调试:
<ProjectReference Include="..\HansCnc.Mvvm.SourceGenerators.Roslyn4120\HansCnc.Mvvm.SourceGenerators.Roslyn4120.csproj"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\HansCnc.Mvvm\HansCnc.Mvvm.csproj" />
<ProjectReference Include="..\HansCnc.Mvvm.WPF\HansCnc.Mvvm.WPF.csproj" />
消费方应用使用 NuGet 四包(HansCnc.Core、HansCnc.DataCollection、HansCnc.Mvvm、HansCnc.Mvvm.WPF;WPF 应用通常显式引用后两者,DataCollection 可由 HansCnc.Mvvm 传递依赖)。启用实时数据采集时导入 HansCnc.Mvvm.DataCollection.props(来自 HansCnc.DataCollection 包)。仅在修改生成器本身时使用 ProjectReference + OutputItemType="Analyzer"。
| 类型 | 程序集 / 命名空间 | 通知 | 典型用途 |
|---|---|---|---|
NotifyList<T> |
HansCnc.DataCollection |
自定义 CollectionChanged(细粒度) |
ViewModel 内逻辑集合 |
ObservableDictionary<TKey, TValue> |
HansCnc.Mvvm / HansCnc.Collections |
BCL INotifyCollectionChanged + INotifyPropertyChanged |
WPF XAML 绑定 |
ObservableDictionary 与 NotifyList 不同,专为 WPF 绑定契约设计。在 UI 线程上修改;SyncRoot 提供线程安全,不替代 Dispatcher。
public ObservableDictionary<string, string> Items { get; } = new();
<ItemsControl ItemsSource="{Binding Items}" />
<TextBlock Text="{Binding Items[TitleKey]}" />
[IViewFor<TViewModel>]为 WPF 视图(partial 类)生成 IViewFor<TViewModel> 实现:
[IViewFor<HomeViewModel>]
public partial class HomeView : UserControl { }
生成内容:ViewModelProperty 依赖属性、ViewModel CLR 属性、BindingRoot 便捷属性。
| ID | 说明 |
|---|---|
| HMVVM0001 | [IViewFor] 标记的类必须为 partial(支持 Code Fix:添加 partial) |
| HMVVM0002 | [DialogViewModel] 不得显式声明基类 |
| HMVVM0003 | [DialogViewModel] 标记的类必须为 partial(支持 Code Fix) |
| HMVVM0004 | [IViewFor] 中的 ViewModel 类型无法解析 |
| HMVVM0005 | 同一类型上存在多个 [IViewFor](例如多个 partial 声明各带属性) |
[EditableModel] / [DirtyPart] / [DirtyCollectionPart]为可编辑实体(partial + ObservableObject)生成 IEditableModel 脏跟踪成员:DirtyContext、DirtyCount、IsDirty、SetDirty、ClearDirty、RaiseDirtyChanged。嵌套图用 [DirtyPart](引用)与 [DirtyCollectionPart](IEnumerable<T>,T 须实现 IEditableModel 或标记 [EditableModel])。
[EditableModel]
public partial class Article : ObservableObject
{
[DirtyPart]
public Author Author { get; set; } = new();
[DirtyCollectionPart]
public List<Tag> Tags { get; set; } = [];
}
脏标记由业务代码显式调用 SetDirty(生成器不会挂钩属性 setter)。示例见 HansCnc.Mvvm.Samples/EditableModels/。
| ID | 说明 |
|---|---|
| HMVVM0006 | [EditableModel] 标记的类必须为 partial(支持 Code Fix) |
| HMVVM0007 | [EditableModel] 类型必须继承 ObservableObject |
| HMVVM0008 | [DirtyPart] 属性类型须实现 IEditableModel(或带 [EditableModel]) |
| HMVVM0009 | [DirtyCollectionPart] 须为 IEnumerable<T> 且 T 满足同上 |
在 XAML 根元素绑定到 ViewModel(运行时由 DialogService 或代码赋值):
<UserControl ...
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=ViewModel}"
d:DataContext="{d:DesignInstance Type=vm:HomeViewModel, IsDesignTimeCreatable=True}">
示例见 HansCnc.Mvvm.Samples/Views/。
[DialogViewModel<TInput, TResult>]为对话框 ViewModel(partial 类、无显式基类)注入基类 DialogViewModelBase<TInput, TResult>:
[DialogViewModel<string, string>]
public partial class MyDialogViewModel
{
public void Confirm()
{
ResultContext = MvvmDialogResult.Ok(UserInput);
Ok();
}
}
生成器只添加基类声明;属性、OkCommand / CancelCommand、生命周期与 IDialogViewModel 成员均由 DialogViewModelBase 提供。
| ID | 说明 |
|---|---|
| HMVVM0002 | 不得显式声明基类 |
| HMVVM0003 | [DialogViewModel] 标记的类必须为 partial |
var builder = new ContainerBuilder();
builder.UseDialogService();
builder.RegisterDialog<MyDialogViewModel, MyDialogWindow>();
// 约定:FooDialogViewModel → FooDialogWindow(同一程序集)
builder.RegisterDialogsFromAssembly(typeof(MyDialogViewModel).Assembly);
var result = dialogService.ShowDialog<MyDialogViewModel, string, string>("输入值");
if (result.Result)
Console.WriteLine(result.ResultValue);
DialogService 通过 IViewFor<TViewModel>.ViewModel 绑定 ViewModel(不设置 Window.DataContext)。对话框窗口须实现 IViewFor<TViewModel>(推荐 [IViewFor<T>] + partial)。
| 主题 | 说明 |
|---|---|
| Owner | 已 Show 的当前活动窗口,或已加载的 MainWindow |
| 居中 | 在 XAML 中设置 WindowStartupLocation="CenterOwner"(见 Samples) |
| ESC / 取消 | Cancel() 或 RequestClose();可配合 IsCancel="True" 按钮 |
| 生命周期 | OnDialogInitialized / Loaded / Closing / Closed;钩子内异常由服务记录日志,不中断关闭流程 |
DialogViewModelBase 提供 Input、ResultContext、Initialize、Ok() / Cancel() 及上述生命周期虚方法。确认前设置 ResultContext,再调用 Ok();取消调用 Cancel()。
[IViewFor<HomeViewModel>]
public partial class HomeView : UserControl
{
public HomeView()
{
this.WhenActivated(disposables =>
{
// 视图激活时的订阅
});
}
}
当 ViewModel 实现 IActivatableViewModel 时,WhenActivated 会在首次激活/全部停用时调用 HandleActivation / HandleDeactivation。
MainThreadHelper.Invoke(() => { /* UI 线程同步 */ });
await MainThreadHelper.InvokeAsync(() => { /* UI 线程异步 */ });
MainThreadHelper.BeginInvoke(() => { /* 投递到队列,避免重入 */ });
MainThreadHelper.DoEvents(); // 长同步操作中偶尔泵送消息
MainThreadHelper.VerifyAccess(); // 断言当前在 UI 线程
属性 UiDispatcher 暴露缓存的 WPF 调度器(避免与 System.Windows.Threading.Dispatcher 类型同名冲突)。
应用启动时配置 R3 的 WPF 调度(Samples 使用 R3Extensions.WPF):
// App.xaml.cs
WpfProviderInitializer.SetDefaultObservableSystem(
ex => Log.Error(ex, ex.Message),
DispatcherPriority.Background,
Dispatcher);
命令式 UI 用 MainThreadHelper;Observable 流 用 ObserveOnUi / SubscribeOnUi:
this.WhenActivated(d =>
{
ViewModel!.WhenAnyValue(vm => vm.Counter)
.ObserveOnUi()
.Subscribe(c => { /* UI 线程回调 */ })
.DisposeWith(d);
});
// 或把流绑到 ViewModel 属性(无 OAPH):
ViewModel!.WhenAnyValue(vm => vm.Counter)
.SubscribeOnUi(c => viewModel.CounterDescription = $"已计 {c} 次");
WPF 控件事件 → Observable 请使用 MvvmAIO.R3.SourceGenerators(FromEvents / FromRoutedEvents)。属性观察见 WhenAnyMixin;ReactiveUI 迁移见 。
| 文件 | 说明 |
|---|---|
| 当前发布版本的完整使用文档(单文件 HTML) | |
| 全部历史版本变更记录 | |
| 发布时如何同步更新上述两份文档 |
.\build.ps1
dotnet build HansCnc.Mvvm.slnx -c Release
dotnet test HansCnc.Mvvm.slnx -c Release --no-build
WPF 与集成测试需在 Windows 上运行。升级 MvvmAIO.R3.SourceGenerators 见 。
在 0.1.0 之前仍可能调整,但以下面为对外承诺的主契约:IViewFor / [IViewFor]、[DialogViewModel]、DialogViewModelBase、IDialogService、DialogService、WhenActivated、MainThreadHelper、MvvmDialogResult<T>、诊断 HMVVM0001–0005。详见 与 。
DependencyProperty 样板代码DialogViewModelBase 分工DoEventsHansCnc.Mvvm.WPF 全 TFM 引用,最低 net472;应用启动建议 WpfProviderInitializer)HansCnc.Mvvm.WPF 内部使用;应用侧同样可用)HansCnc.Mvvm.WPF 可选)| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0-windows7.0 net8.0-windows7.0 is compatible. net9.0-windows net9.0-windows was computed. net10.0-windows net10.0-windows was computed. net10.0-windows7.0 net10.0-windows7.0 is compatible. |
| .NET Framework | net472 net472 is compatible. net48 net48 is compatible. net481 net481 was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.0-preview1 | 50 | 6/4/2026 |
| 0.2.3-preview1 | 50 | 6/3/2026 |
| 0.2.2-preview1 | 49 | 6/2/2026 |
| 0.2.1-preview1 | 67 | 5/28/2026 |
| 0.2.0-preview1 | 58 | 5/28/2026 |
| 0.1.0-preview4 | 50 | 5/27/2026 |
| 0.1.0-preview3 | 55 | 5/27/2026 |
| 0.1.0-preview2 | 53 | 5/27/2026 |
| 0.1.0-preview1 | 54 | 5/27/2026 |
| 0.0.2-preview1 | 55 | 5/26/2026 |
| 0.0.1-preview7 | 55 | 5/21/2026 |
| 0.0.1-preview6 | 51 | 5/18/2026 |
| 0.0.1-preview5 | 50 | 5/18/2026 |
| 0.0.1-preview4 | 44 | 5/17/2026 |
| 0.0.1-preview3 | 51 | 5/17/2026 |
| 0.0.1-preview2 | 56 | 5/17/2026 |
| 0.0.1-preview1 | 68 | 5/17/2026 |