English | 中文
本项目源于作者在从事 Wpf 开发时的一些游戏之作,是对现有 Mvvm 框架的补充。要说解决了什么重大问题,到也没有,仅仅是提供了一些语法糖,让人少写几行代码而已。其服务对象也不局限于 Wpf 开发,其它类似的 Xaml 框架,如 Uwp、Maui 等应该也可以使用,只是作者从未在其它框架上测试过。
项目的结构如下:
WpfExtensions.Xaml
:提供了一些MarkupExtension
以简化 Xaml 开发。WpfExtensions.Binding
:为简化属性依赖更新的代码,提供了类似 Vue.js 中的计算属性功能。WpfExtensions.Infrastructure
:一些杂项,等待时机成熟后将被分离出来,作为独立的模块发布。
Package | NuGet |
---|---|
WpfExtensions.Xaml |
|
WpfExtensions.Binding |
将 Vue3 响应式模块的部分功能引入到了 Wpf 中。
以下文档中出现的“可观测”一词,指的是实现了
INotifyPropertyChanged
或INotifyCollectionChanged
的对象。
订阅一个可观测的表达式,并在其值发生改变的时候,触发回调函数。
// 更多重载见源码,其签名与 vue3 的 watch() 保持一致,使用示例亦可直接参考 vue3 文档。
Reactivity.Default.Watch(() => Width * Height, area => Debug.WriteLine(area));
深度遍历地订阅一个可观测的对象,并在其属性、或属性的属性发生变化时,触发回调函数。
// `path` 将打印出具体被修改的属性的路径。
Reactivity.Default.WatchDeep(obj, path => Debug.WriteLine(path))
计算属性,是 BindableBase
基类的实例方法。
public class ViewModel : BindableBase {
// 可 binding 到 xaml,当 Width 或 Height 发生改变时,自动通知 Area 的改变。
public double Area => Computed(() => Width * Height);
}
- View (XAML):
<Element Command={markup:Command Execute} />
<Element Command={markup:Command ExecuteWithArgumentAsync, CanExecute}
CommandParameter={Binding Argument} />
- View Model (*.cs):
class ViewModel
{
public void Execute() {}
public void ExecuteWithArgument(string arg) {}
// The `Execute` method supports async, and its default `Can Execute` method will disable the command when it is busy.
public Task ExecuteAsync() => Task.Completed;
public Task ExecuteWithArgumentAsync(string arg) => Task.Completed;
// The `Can Execute` method does not support async.
public bool CanExecute() => true;
public bool CanExecuteWithArgument(string arg) => true;
}
Combine multiple Converters into one pipeline.
<TextBlock Visibility="{Binding DescriptionText, Converter={markup:Compose
{StaticResource IsNullOrEmptyOperator},
{StaticResource NotConverter},
{StaticResource BooleanToVisibilityConverter}}}"
Text="{Binding DescriptionText}" />
Using the Conditional expression
in XAML.
<Button Command="{markup:If {Binding BoolProperty},
{Binding OkCommand},
{Binding CancelCommand}}" />
<UserControl>
<markup:If Condition="{Binding IsLoading}">
<markup:If.True>
<views:LoadingView />
</markup:If.True>
<markup:If.False>
<views:LoadedView />
</markup:If.False>
</markup:If>
</UserControl>
Using the Switch expression
in XAML.
<Image Source="{markup:Switch {Binding FileType},
{Case {x:Static res:FileType.Music}, {StaticResource MusicIcon}},
{Case {x:Static res:FileType.Video}, {StaticResource VideoIcon}},
{Case {x:Static res:FileType.Picture}, {StaticResource PictureIcon}},
...
{Case {StaticResource UnknownFileIcon}}}" />
<UserControl>
<Switch To="{Binding SelectedViewName}">
<Case Label="View1">
<views:View1 />
</Case>
<Case Label="{x:Static res:Views.View2}">
<views:View2 />
</Case>
<Case>
<views:View404 />
</Case>
</Switch>
</UserControl>
Dynamically switch the culture resource without restarting the app.
<TextBlock Text="{markup:I18n {x:Static languages:UiStrings.MainWindow_Title}}" />
<TextBlock Text="{markup:I18nString {x:Static languages:UiStrings.SayHello}, {Binding Username}}" />
<TextBlock Text="{markup:I18nString {x:Static languages:UiStrings.StringFormat},
{Binding Arg0},
{Binding Arg1},
...,
{Binding Arg15}}" />
<Button Style="{markup:Styles {StaticResource FlatButtonStyle},
{StaticResource AnimationStyle},
...}" />