在 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

最新文章

  1. [LeetCode] Factorial Trailing Zeroes 求阶乘末尾零的个数
  2. HDU 5183 Negative and Positive (NP) --Hashmap
  3. Java 基础【09】 日期类型
  4. memcpy函数
  5. XML中如何使用schema
  6. jxl读写excel的方法
  7. Linux系统安全需要注意的一些问题
  8. oracle修改表列名和列类型
  9. mysql的定时器
  10. Pycharm 2018.2.1最新版破解到2099年图解教程
  11. Python元组的一点用法
  12. Python第四章(北理国家精品课 嵩天等)
  13. Golang入门教程(三)beego 框架安装
  14. JAVA 程序的基本语法
  15. nssm和AlwaysUp来包装exe文件为windows服务
  16. java 多维数据定义
  17. 20145316许心远《网络对抗》第一次实验拓展:shellcode注入+return-to-libc
  18. 专访TK教主于旸:原来那些搞安全的说的都是真的(图灵访谈)
  19. POI 解析excel 空行问题
  20. 如何从 GitHub 上下载单个文件夹

热门文章

  1. Redis-Sentinel 数据源配置
  2. Tools:apache部署https服务
  3. TP5 自定义验证器
  4. OS模块的介绍
  5. 【Noip模拟 20160929】划区灌溉
  6. ExecutorService——shutdown方法和awaitTermination方法
  7. ReactiveX 学习笔记(21)使用 Rx.NET + ReactiveUI 进行 GUI 编程
  8. Quartz基础知识了解(一)
  9. 对话框改变颜色 宽度沾满屏幕 Dialog
  10. 使用jmail发送短信