【UWP】使用 Rx 改善 AutoSuggestBox
在 UWP 中,有一个控件叫 AutoSuggestBox,它的主要成分是一个 TextBox 和 ComboBox。使用它,我们可以做一些根据用户输入来显示相关建议输入的功能,例如百度首页搜索框那种效果:

在看这篇文章之前,我建议先看看老周写的这一篇:https://www.cnblogs.com/tcjiaan/p/4967031.html ,先对 AutoSuggestBox 有一个大体的印象,不然下面干什么都不知道了。
接下来开始我们的实验,先准备好百度的接口(这个可以用浏览器的开发者工具抓出来):
public class BaiduService
{
static BaiduService()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
} public async Task<IReadOnlyList<string>> GetSuggestionsAsync(string query)
{
using (var client = new HttpClient())
{
var url = $"http://www.baidu.com/su?wd={HttpUtility.UrlEncode(query)}";
var str = await client.GetStringAsync(url);
str = str.Substring(str.IndexOf('{'));
str = str.Substring(, str.LastIndexOf('}') + );
var jObject = JObject.Parse(str);
return jObject["s"].ToObject<string[]>();
}
}
}
需要引用一下 Newtonsoft.Json 这个包。
静态构造函数里我注册了一下本机的 Encoding,不然会报错(百度这厮用的是 gbk,而不是常见的 utf-8)。
然后开始编写 Demo 页面
XAML
<Grid>
<Grid Margin="20">
<StackPanel Orientation="Vertical">
<AutoSuggestBox x:Name="AutoSuggestBox"
TextChanged="AutoSuggestBox_TextChanged" />
</StackPanel>
</Grid>
</Grid>
这里随便写了下,反正就是弄了个 AutoSuggestBox,订阅了一下它的 TextChanged 事件。
cs代码:
private async void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
switch (args.Reason)
{
case AutoSuggestionBoxTextChangeReason.ProgrammaticChange:
case AutoSuggestionBoxTextChangeReason.SuggestionChosen:
sender.ItemsSource = null;
return;
} // User input
var query = sender.Text;
Debug.WriteLine("get suggestion: " + query);
var suggestions = await _baiduService.GetSuggestionsAsync(query);
sender.ItemsSource = suggestions;
}
触发的事件参数中有个 Reason 属性,表面该次事件触发的原因。
在这里我如果是程序代码修改或者用户选择了建议项的话,那么就清除建议项列表。否则就去问百度要一下建议(顺便输出一下,说明触发了)。
然后就把我们的 Demo 程序跑起来吧。


看上去工作得还是蛮正常的嘛。
但是,在这里我要告诉你,这样写,是有一些坑的!
1、

全选,复制,再粘贴,我们的文字内容是没有变化才对的,然而也触发了一次请求。
2、
如果我的内容为空,那么就不应该请求才对的。
3、
在上面的图中,我 UWP 这三个字母的输入速度应该是比较快的,那么 U 那一次就不应该去请求才对。应该以停止输入一段时间后,才去进行请求。AutoSuggestBox 控件应该是做了(不然在 UW 时也应该会触发才对),但目测时间非常短(可能就 0.1 秒),而且也没有相关的属性能够控制这个时长。
4、
因为这个请求是一个异步的网络请求,所以说不好的话,后发起的请求有可能先返回。按上面的代码逻辑来说,这样输入和建议项就对不上了。
按传统思路,第 1 点我们可以在请求前加个判断,如果跟上一次相同就不请求。第 2 点加个空字符串判断即可。第 3 点就麻烦了,真要实现我们得加个计时之类的方法来做。第 4 点也是很麻烦,我目前想到的是发起请求时给个 token 之类,接收到的时候再对比是否是最新的 token。
但说实话,这么一整套下来,不麻烦么?而且代码量不是一点两点。
在这里,我要安利各位,只要你使用 Rx,解决这点小问题完全不在话下。
Rx 的全称是 Reactive Extensions,是一种针对异步编程的编程模型。Rx 不仅仅在 .Net 下有实现,在 JavaScript、Java 等等平台都有相关的实现。
概念说完了,继续实验。
引用 Rx 的 nuget 包,System.Reactive。
在页面的构造函数先编写如下的代码:
var changed =
Observable.FromEventPattern<TypedEventHandler<AutoSuggestBox, AutoSuggestBoxTextChangedEventArgs>, AutoSuggestBox, AutoSuggestBoxTextChangedEventArgs>(
handler => AutoSuggestBox.TextChanged += handler,
handler => AutoSuggestBox.TextChanged -= handler);
这段代码以 AutoSuggestBox 的 TextChanged 事件创建一个可监听的数据源 changed 对象。
接下来,我们处理第 1 点,需要忽略掉相同的文本内容。
var input = changed
.DistinctUntilChanged(temp => temp.Sender.Text);
DistinctUntilChanged 这个扩展方法是 Rx 提供的,如果数据源内容不变,则不会触发。
然后我们处理第 3 点,只有停止输入一段时间后,我们再去发起请求。
var input = changed
.DistinctUntilChanged(temp => temp.Sender.Text)
.Throttle(TimeSpan.FromSeconds());
这个也很简单,Rx 提供了 Throttle 方法,传入需要的时间就可以了,这里我设定成停止输入 1 秒后才触发。
然后接下来我们要区分两种情况,一个是用户输入的,另一个是非用户输入的。
var notUserInput = input
.ObserveOnDispatcher()
.Where(temp => temp.EventArgs.Reason != AutoSuggestionBoxTextChangeReason.UserInput); var userInput = input
.ObserveOnDispatcher()
.Where(temp => temp.EventArgs.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
.Where(temp => !string.IsNullOrEmpty(temp.Sender.Text));
在用户输入的时候,输入后文本框非空我们才触发(第 2 点)。
这里注意到还有 ObserveOnDispatcher 这个方法的调用,这个调用就是说,接下来我的操作需要在当前线程上进行。Rx 默认是会在另一个线程上的,在 Where 方法中我们引用到了 AutoSuggestBox 控件,所以需要调用到该方法。
接下来我们处理一下 userInput,有了输入,我们自然需要输出,输出就是建议项:
var userInput = input
.ObserveOnDispatcher()
.Where(temp => temp.EventArgs.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
.Where(temp => !string.IsNullOrEmpty(temp.Sender.Text))
.Select(temp => _baiduService.GetSuggestionsAsync(temp.Sender.Text));
调用百度接口,返回 Task<IReadOnlyList<string>>。同时,我们对 notUserInput 也处理一下,返回 null,但类型也是 Task<IReadOnlyList<string>>。
var notUserInput = input
.ObserveOnDispatcher()
.Where(temp => temp.EventArgs.Reason != AutoSuggestionBoxTextChangeReason.UserInput)
.Select(temp => Task.FromResult<IReadOnlyList<string>>(null));
现在,我们把这两个重新合成为一个,因为我们数据源触发的条件是 TextChanged,而不是因为上面这一大堆东西才进行触发。
var merge = Observable
.Merge(notUserInput, userInput);
最后,我们可以监听这个数据源了,调用 Subscribe 方法(当然还要再 ObserveOnDispatcher 一次):
merge
.ObserveOnDispatcher()
.Subscribe(suggestions =>
{
AutoSuggestBox.ItemsSource = suggestions;
});
这样更新上去我们的 AutoSuggestBox 就行了。
慢着,我们的第 4 点还没处理呢。这个只需要稍微修改一下就可以了(Rx 真方便)。
var merge = Observable
.Merge(notUserInput, userInput)
.Switch();
Switch 方法会将输出的顺序按照输入的顺序来排序,这样之后,我们的第 4 点就能解决掉了。

最终下来,我们解决这么一系列问题只是写了这么点的代码,如果按传统的写法嘛,那不知道写到什么时候去了。Rx 万岁!
虽然 Rx 学习起来难度曲线非常大,但是在解决某些场景,Rx 是非常的有效的。(顺带一提,Angular 就集成了 RxJS,可见 Rx 存在其优势)
参考资料:
DevCamp 2010 Keynote - Rx: Curing your asynchronous programming blues
【UWP】使用 Rx 改善 AutoSuggestBox的更多相关文章
- UWP自动填充控件AutoSuggestBox小优化
UWP提供的AutoSuggestBox本身非常好用,在项目中经常用到,但是当我们使用时发现一下不人性化的设置,例子1如下: <Page x:Class="SelfInkCanvas. ...
- UWP开发随笔——UWP新控件!AutoSuggestBox!
摘要 要开发一款优秀的application,控件肯定是必不可少的,uwp就为开发者提供了各种各样的系统控件,AutoSuggestBox就是uwp极具特色的控件之一,也是相对于之前win8.1的ua ...
- 【Win 10应用开发】提供建议列表的输入控件(AutoSuggestBox)
AutoSuggestBox控件与TextBox控件相似,但,AutoSuggestBox控件可以提供一个下拉列表,用户可以从弹出的下拉列表中选择一个项,并把被选项的内容显示在输入框上.就类似于搜索引 ...
- UWP学习记录12-应用到应用的通信
UWP学习记录12-应用到应用的通信 1.应用间通信 “共享”合约是用户可以在应用之间快速交换数据的一种方式. 例如,用户可能希望使用社交网络应用与其好友共享网页,或者将链接保存在笔记应用中以供日后参 ...
- Win10 UWP 开发系列:使用SplitView实现汉堡菜单及页面内导航
在Win10之前,WP平台的App主要有枢轴和全景两种导航模式,我个人更喜欢Pivot即枢轴模式,可以左右切换,非常方便.全景视图因为对设计要求比较高,自己总是做不出好的效果.对于一般的新闻阅读类Ap ...
- UWP?UWP! - Build 2015有些啥?(2)
UWP?UWP! - Build 2015有些啥? Build 2015圆满落幕了,不知大家有多少人刷夜看了直播呢?不管怎么说,想必各位都很好奇在这场微软开发者盛宴上,Microsoft又发布了什么令 ...
- [UWP]了解模板化控件(3):实现HeaderedContentControl
1. 概述 来看看这段XMAL: <StackPanel Width="300"> <TextBox Header="TextBox" /&g ...
- [UWP]了解模板化控件(4):TemplatePart
1. TemplatePart TemplatePart(部件)是指ControlTemplate中的命名元素.控件逻辑预期这些部分存在于ControlTemplate中,并且使用protected ...
- [UWP]浅谈按钮设计
一时兴起想谈谈UWP按钮的设计. 按钮是UI中最重要的元素之一,可能也是用得最多的交互元素.好的按钮设计可以有效提高用户体验,构造让人眼前一亮的UI.而且按钮通常不会影响布局,小小的按钮无论怎么改也不 ...
随机推荐
- win7+win10系统使用日常经验集锦
请保留此份 Cmd Markdown 的欢迎稿兼使用说明, 当然你也可以使用彩色字体. 或者使用小体字. 或者使用大体字. 如需撰写新稿件,点击顶部工具栏右侧的 新文稿 或者使用快捷键 Ctrl+Al ...
- win7激活成功 但每次开机后又显示此windows副本不是正版的解决办法
进入命令行界面,输入 SLMGR -REARM 命令,点击回车 此方法只是暂时激活系统,大概4个月左右,即要使用此方法再次进行激活
- 63(原67).纯 CSS 创作单元素点阵 loader
原文地址:https://segmentfault.com/a/1190000015444368 感想:CSS又一次让我见识到它的强大之处 --> box-shadow . box-shadow ...
- mac无密登陆到linux
最近弄了台linux云服务器,然而每次登陆linux都好麻烦,所以倒腾了下ssh无密登陆. linux:centos 6.5,自带ssh mac:yosemite,自带ssh 步骤: 1. 创建key ...
- mac 安装软件
一.安装spark 1.官网下载最新tar文件 2.解压 3.安装java开发环境 3.1.安装下载java 8 https://www.oracle.com/technetwork/java/ja ...
- C#设计模式(4)——抽象工厂模式(Abstract Factory)
简单工厂模式: 简单工厂模式的工厂类随着产品类的增加需要增加额外的代码 工厂方法模式: 工厂方法模式每个具体工厂类只完成单个实例的创建,所以它具有很好的可扩展性 但是在实际应用中,一个工厂不止会创建单 ...
- 《面向对象程序设计(Java)》第四周学习总结
第一部分 第四章部分理论知识 1.面向对象程序设计概述:java是完全面向对象的,必须熟悉OOP才能编写java程序. 类:由类构造对象的过程称为创建类的实例. 封装:封装是将数据和行为组合在一个包中 ...
- 创建Flask实例对象时的参数和app.run()中的参数
app=Flask(name,static_folder=“static”,static_url_path="/aaa",template_folder=“templates”) ...
- ElasicSearch(1)
ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasticsearch是用Java开发的,并作为Apach ...
- Unity中的屏幕坐标:ComputeScreenPos/VPOS/WPOS
[Unity中的屏幕坐标:ComputeScreenPos/VPOS/WPOS] 1.通过 VPOS / WPOS 语义获取. VPOS 是 HLSL 中 对 屏幕 坐标 的 语义, 而 WPOS 是 ...