前文Reactive UI -- 反应式编程UI框架入门学习(一)  介绍了反应式编程的概念和跨平台ReactiveUI框架的简单应用。

本文通过一个简单的小应用更进一步学习ReactiveUI框架的使用和整体布局,并对比与MVVMLight的不同之处。

应用的功能很简单,读取本地计算机的所有盘符,并通过选定盘符展示该盘符下的所有文件夹的名称和创建时间。

首先新建一个工程,本文使用的是.Net6.0,并添加两个Nuget包:ReactiveUI.WPF,ReactiveUI.Fody

ReactiveUI.WPF是框架的核心代码包,而ReactiveUI.Fody是一个扩展包,像[Reactive]这样的标记就是在这个包中定义的。

绑定ViewModel

在MVVMLight框架中,View绑定ViewModel需要通过DataContext来绑定在Locator中定义的ViewModel,而在ReactiveUI框架中,则是通过继承泛型窗口类ReactiveWindow或者泛型用户控件类ReactiveUserControl来自动绑定ViewModel。

<reactiveui:ReactiveWindow  x:TypeArguments="local:MainWindowViewModel"
x:Class="Calculateor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Calculateor"
xmlns:reactiveui="http://reactiveui.net"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="500">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ComboBox Name="cmbDisks">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListBox Grid.Row="1" x:Name="lbFolders"></ListBox>
</Grid>
</reactiveui:ReactiveWindow>

注意以上Xaml代码中没有出现DataContext。

CS文件中强绑定:

public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
{
public MainWindow()
{
InitializeComponent(); ViewModel = new MainWindowViewModel();
this.WhenActivated(dispos => { this.OneWayBind(ViewModel, vm=>vm.Disks, vw=>vw.cmbDisks.ItemsSource)
.DisposeWith(dispos); this.Bind(ViewModel, vm => vm.SelectedDisk, vw => vw.cmbDisks.SelectedItem)
.DisposeWith(dispos); this.OneWayBind(ViewModel,vm=>vm.FolderModels, vw=>vw.lbFolders.ItemsSource)
.DisposeWith(dispos);
}); }
}

View通过继承指定为MainWindowViewModel类型的ReactiveWindow,便建立了View和ViewModel之间的关联,而不需要额外的指定DataContext去绑定。

界面顶部是一个下拉框,用于显示盘符信息,ItemSource绑定了ReadOnlyObservableCollection<string>类型对象。

    private readonly ReadOnlyObservableCollection<string> _disks;
public ReadOnlyObservableCollection<string> Disks => _disks;

其选中的盘符则绑定到了一个string类型的属性上。注意Reactive标记

  [Reactive]
public string SelectedDisk { get; set; }

接着用一个ListBox展示具体的文件夹信息,定义一个FolderModel类型的类来约定需要展示的信息。

public class FolderModel
{
public string FolderName { get; set; }
public DateTime CreateTime { get; set; } }

ItemSoruce绑定到一个IEnumerable<FolderModel> FolderModels类型上

        private readonly ObservableAsPropertyHelper<IEnumerable<FolderModel>> _folderModels;
public IEnumerable<FolderModel> FolderModels => _folderModels.Value;

而 ObservableAsPropertyHelper<IEnumerable<FolderModel>> _folderModels则是用来与SelectedDisk建立观察者模式的联系,每次SelectDisk的值改变时,就会触发方法LoadFolderInfoWithSelectedDiskChanged,并将返回结果赋值到FolderModels对象,最终传导到UI上。

   _folderModels = this.WhenAnyValue(s => s.SelectedDisk)
.Where(s => !string.IsNullOrWhiteSpace(s))
.SelectMany(LoadFolderInfoWithSelectedDiskChanged)
.ObserveOn(RxApp.MainThreadScheduler)//线程调度,后续的代码会在主线程上调用
.ToProperty(this, nameof(FolderModels));

这里的WhenAnyValue是构建函数声明的核心API,一般都是与ReactiveUI框架扩展的Linq方法搭配使用,前文有过简单的介绍。

在MVVMLight框架中,ViewModel继承的是ViewModelBase/ObservableObject,而在ReactiveUI框架中,ViewModel继承的是ReactiveObject

以下为完整的MainWindowViewModel文件:

public class MainWindowViewModel : ReactiveObject
{
public MainWindowViewModel()
{
DisksSource = new();
DisksSource.ToObservableChangeSet()
.Bind(out _disks)
.Subscribe(); _folderModels = this.WhenAnyValue(s => s.SelectedDisk)
.Where(s => !string.IsNullOrWhiteSpace(s))
.SelectMany(LoadFolderInfoWithSelectedDiskChanged)
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, nameof(FolderModels)); Task _ = LoadDisksIqLocal();
}
private readonly ReadOnlyObservableCollection<string> _disks;
public ReadOnlyObservableCollection<string> Disks => _disks; public ObservableCollectionExtended<string> DisksSource{get;private set;} private readonly ObservableAsPropertyHelper<IEnumerable<FolderModel>> _folderModels;
public IEnumerable<FolderModel> FolderModels => _folderModels.Value; [Reactive]
public string SelectedDisk { get; set; }

//通过WMI读取本地计算机的所有磁盘的盘符
private async Task LoadDisksIqLocal()
{
await Task.Run(() => { ManagementObjectSearcher query = new("SELECT * From Win32_LogicalDisk");
var queryCollection = query.Get(); foreach (var item in queryCollection)
{
var diriveType = (DriveType)int.Parse(item["DriveType"].ToString());
if (diriveType == DriveType.Fixed)
{
var diskID = item["DeviceID"].ToString();
DisksSource.Add(diskID);
}
}
});
} private async Task<IEnumerable<FolderModel>> LoadFolderInfoWithSelectedDiskChanged(string diskName)
{
List<FolderModel> folderModels = new List<FolderModel>();
await Task.Run(() => {
var files = Directory.GetDirectories(diskName);
foreach (var fileName in files)
{
FolderModel folderModel = new FolderModel();
DirectoryInfo directoryInfo = new DirectoryInfo(fileName);
folderModel.FolderName = directoryInfo.Name;
folderModel.CreateTime = directoryInfo.CreationTime;
folderModels.Add(folderModel);
}
});
return folderModels;
}
}

下面需要定义ListBox信息需要以怎样的格式来展示。一般的常规做法是通过Style来定制控件的模板展示定制化的数据格式,而在ReactiveUI框架中,还有其他的选择。

在ReactiveUI中,会根据ListBox ItemSource所绑定的集合类型来自动的搜索这个类型所关联的UserControl来作为ListBox的模板。

简单的说,只需要给上文中的FolderModel指定一个UserControl即可,而不需要额外的指定Style或者Template。

所以View中的ListBox代码很简单:

<ListBox Grid.Row="1" x:Name="lbFolders"></ListBox>

新增一个UserControl的类FolderInfoUC.xaml与FolderModel绑定:

<reactiveui:ReactiveUserControl x:Class="Calculateor.FolderInfoUC"
x:TypeArguments="local:FolderModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Calculateor"
xmlns:reactiveui="http://reactiveui.net"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UniformGrid Columns="2">
<TextBlock Text="{Binding FolderName}" HorizontalAlignment="Left"/> <TextBlock Text="{Binding CreateTime}" HorizontalAlignment="Right"/>
</UniformGrid>
</reactiveui:ReactiveUserControl>

这里的TextBlock控件除了展示数据之外没有其他用途,所以直接使用了Xaml的绑定方式,而View通过ReactiveUserControl来指定他的ViewModel类型为FolderModel,这样就建立了FolderModel和FolderInfoUC之间的联系。

当然,在很多情况下处理复杂的高度自定义的数据展示时,还是需要Style的配合。

需要注意的是,这里的FolderModel数据类型本身比较简单,不需要继承自ReactiveObject。

还有一个情况需要注意,如主界面上的下拉框Combobox。这个控件绑定的是一个简单的string类型的集合 ReadOnlyObservableCollection<string>,不推荐为CLR中的基础类型关联UserControl,所以需要Xaml中指定ItemTemplate,否则无法显示数据。

总结

截至本文,ReactiveUI相比于MVVMLight框架,有以下的不同点:

1.ReactiveUI推荐强绑定,并提供了管理ViewModel和属性的生命周期的方法。

2.易于构建响应式的可观察的函数声明式的数据流处理。

3.简化了ViewModel和View之间绑定的操作方式,并强化了两者之间的联系贯穿在整个应用的生命周期中。

4.扩展了动态数据集合在多线程下的操作,提供线程安全的可绑定动态集合。

本文以一个小应用简单介绍了ReactiveUI整体框架的使用,其中一些核心的API WhenAnyValue、ObservableAsPropertyHelper、ObservableCollectionExtended等没有详细展开,后续会对这些API的高级应用有更深入的学习和了解,学习和阅读ReactiveUI的源码。

git地址:https://github.com/reactiveui/reactiveui

官网地址:https://www.reactiveui.net/



Reactive UI -- 反应式编程UI框架入门学习(二)的更多相关文章

  1. Reactive UI -- 反应式编程UI框架入门学习(一)

    推荐一个反应式编程的MVVM跨平台框架. 反应式编程 反应式编程是一种相对于命令式的编程范式,由函数式的组合声明来构建异步数据流.要理解这个概念,可以简单的借助Excel中的单元格函数. 上图中,A1 ...

  2. 响应式编程系列(一):什么是响应式编程?reactor入门

    响应式编程 系列文章目录 (一)什么是响应式编程?reactor入门 (二)Flux入门学习:流的概念,特性和基本操作 (三)Flux深入学习:流的高级特性和进阶用法 (四)reactor-core响 ...

  3. (转)MyBatis框架的学习(二)——MyBatis架构与入门

    http://blog.csdn.net/yerenyuan_pku/article/details/71699515 MyBatis框架的架构 MyBatis框架的架构如下图: 下面作简要概述: S ...

  4. ReactJS入门学习二

    ReactJS入门学习二 阅读目录 React的背景和基本原理 理解React.render() 什么是JSX? 为什么要使用JSX? JSX的语法 如何在JSX中如何使用事件 如何在JSX中如何使用 ...

  5. SpringMVC入门学习(二)

    SpringMVC入门学习(二) ssm框架 springMVC  在上一篇博客中,我简单介绍了一下SpringMVC的环境配置,和简单的使用,今天我们将进一步的学习下Springmvc的操作. mo ...

  6. (转)Spring Boot 2 (十):Spring Boot 中的响应式编程和 WebFlux 入门

    http://www.ityouknow.com/springboot/2019/02/12/spring-boot-webflux.html Spring 5.0 中发布了重量级组件 Webflux ...

  7. Spring Boot 2 (十):Spring Boot 中的响应式编程和 WebFlux 入门

    Spring 5.0 中发布了重量级组件 Webflux,拉起了响应式编程的规模使用序幕. WebFlux 使用的场景是异步非阻塞的,使用 Webflux 作为系统解决方案,在大多数场景下可以提高系统 ...

  8. 函数响应式编程(FRP)从入门到”放弃”——基础概念篇

    前言 研究ReactiveCocoa一段时间了,是时候总结一下学到的一些知识了. 一.函数响应式编程 说道函数响应式编程,就不得不提到函数式编程,它们俩到底有什么关系呢?今天我们就详细的解析一下他们的 ...

  9. 深度学习初探——符号式编程、框架、TensorFlow

    一.命令式编程(imperative)和符号式编程(symblic) 命令式: import numpy as np a = np.ones(10) b = np.ones(10) * 2 c = b ...

随机推荐

  1. 好客租房55-props深入(2props校验)

    对于组件来说 props是外来的 无法保证使用者传入什么格式的数据 传入的数据格式不对 可能会导致组件内部报错 关键问题:不知道报错的具体原因 1安装包props-types 2导入props-typ ...

  2. 2022Gartner容器预测:2025年85%的企业将使用容器管理服务

    近日,国际知名权威分析机构Gartner发布了最新<全球容器管理预测>.预测中指出:在加速的数字化转型驱动下,到2025年全球容器管理领域市场规模将突破14亿美元,预计年复合增长率将达到2 ...

  3. 《回炉重造 Java 基础》——集合(容器)

    整体框架 绿色代表接口/抽象类:蓝色代表类. 主要由两大接口组成,一个是「Collection」接口,另一个是「Map」接口. 前言 以前刚开始学习「集合」的时候,由于没有好好预习,也没有学好基础知识 ...

  4. 12.MYSQL基础-常见函数

    4. 常见函数 一.字符函数 概念 类似于Java的方法,将一组逻辑语句封装在方法中,对外暴露方法名 优点 隐藏了实现细节 提高代码的重用性 调用 select 函数名(实参列表) [ from 表] ...

  5. lanedet项目调试记录

    苦水时间:最近深度学习调代码真的是调的郁闷,每次调都是旧的问题没有解决,新的问题又冒出来了.新的好不容易解决了,旧的问题还是没有解决思路解决不了. 正文 最近找到一个实现了很多车道线检测算法的gith ...

  6. Jmeter接口参数化<自动化>(csv文件)管理测试用例以及断言

    1.创建相关线程组(不解释) 2.创建相应的请求(在请求中设置变量) 下面截图中①②③④⑤⑥⑦皆可以设置为变量 3.新建CSV文件 将请求中设置的变量为明确了解每个字段的含义(皆可以将变量填写到列表的 ...

  7. 纯css就能实现可点击切换的轮播图,feel起来很丝滑

    前言 轮播图经常会在项目里用到,但是实际上用到的轮播图都是比较简单的,没有复杂的特效,这个时候如果去引入swiper那些库的话,未免就有点杀鸡焉用牛刀了. 所以不如自己手写一个,而今天我要分享的一种写 ...

  8. 如何优雅的使用MyBatis?

    ​本文目录 什么是 MyBatis ? 映射器(mappers) typeAliases 类型别名减少类完全限制名的冗余 处理枚举类型 多行插入 重用 SQL 代码段,消除重复 字符串替换#{}和${ ...

  9. (数据科学学习手札139)geopandas 0.11版本重要新特性一览

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,就在几天前,geopandas ...

  10. 简述基于CPU的机器码运行过程

    引言:会写日志的人不一定是优秀的人,但优秀的人往往是会写日志的 这里涉及五个部分,胡歌,林拜,贾以枚,罗伊人,冯眷眷-林拜老婆 依次对应CPU里的控制器, CPU里的寄存器,存储器,输入电路,输出电路 ...