【UWP】使用 Rx 改善 AutoSuggestBox

简介: 原文:【UWP】使用 Rx 改善 AutoSuggestBox在 UWP 中,有一个控件叫 AutoSuggestBox,它的主要成分是一个 TextBox 和 ComboBox。使用它,我们可以做一些根据用户输入来显示相关建议输入的功能,例如百度首页搜索框那种效果: 在看这篇文章之前,我建议先看看老周写的这一篇:https://www.cnblogs.com/tcjiaan/p/4967031.html ,先对 AutoSuggestBox 有一个大体的印象,不然下面干什么都不知道了。
原文: 【UWP】使用 Rx 改善 AutoSuggestBox

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

Snipaste_2018-11-30_16-37-49

在看这篇文章之前,我建议先看看老周写的这一篇: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(0, str.LastIndexOf('}') + 1);
            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 程序跑起来吧。

Snipaste_2018-11-30_16-55-05

Snipaste_2018-11-30_16-55-35

看上去工作得还是蛮正常的嘛。

 

但是,在这里我要告诉你,这样写,是有一些坑的!

1、

20181130

全选,复制,再粘贴,我们的文字内容是没有变化才对的,然而也触发了一次请求。

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(1));

这个也很简单,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 点就能解决掉了。

 

Snipaste_2018-11-30_18-30-46

最终下来,我们解决这么一系列问题只是写了这么点的代码,如果按传统的写法嘛,那不知道写到什么时候去了。Rx 万岁!

虽然 Rx 学习起来难度曲线非常大,但是在解决某些场景,Rx 是非常的有效的。(顺带一提,Angular 就集成了 RxJS,可见 Rx 存在其优势)

 

参考资料:

DevCamp 2010 Keynote - Rx: Curing your asynchronous programming blues

目录
相关文章
|
3月前
|
开发者 Windows Android开发
跨平台开发新选择:揭秘Uno Platform与.NET MAUI优劣对比,帮你找到最适合的框架,告别选择困难症!
【8月更文挑战第31天】本文对比了备受关注的跨平台开发框架Uno Platform与.NET MAUI的特点、优势及适用场景。Uno Platform基于WebAssembly和WebGL技术,支持Windows、iOS、Android及Web平台,而.NET MAUI由微软推出,旨在统一多种UI框架,支持Windows、iOS和Android。两者均采用C#和XAML进行开发,但在性能、平台支持及社区生态方面存在差异。Uno Platform在Web应用方面表现出色,但性能略逊于原生应用;.NET MAUI则接近原生性能,但不支持Web平台。开发者应根据具体需求选择合适的框架。
131 0
|
3月前
|
UED 开发者
哇塞!Uno Platform 数据绑定超全技巧大揭秘!从基础绑定到高级转换,优化性能让你的开发如虎添翼
【8月更文挑战第31天】在开发过程中,数据绑定是连接数据模型与用户界面的关键环节,可实现数据自动更新。Uno Platform 提供了简洁高效的数据绑定方式,使属性变化时 UI 自动同步更新。通过示例展示了基本绑定方法及使用 `Converter` 转换数据的高级技巧,如将年龄转换为格式化字符串。此外,还可利用 `BindingMode.OneTime` 提升性能。掌握这些技巧能显著提高开发效率并优化用户体验。
64 0
|
6月前
LabVIEW编程LabVIEW开发 研华PCIE-1751更改DIO方向 例程与相关资料
LabVIEW编程LabVIEW开发 研华PCIE-1751更改DIO方向 例程与相关资料
37 2
|
6月前
|
编解码 开发工具 vr&ar
RT-Thread 瑞萨 智能家居网络开发:RA6M3 HMI Board 以太网+GUI技术实践
RT-Thread 瑞萨 智能家居网络开发:RA6M3 HMI Board 以太网+GUI技术实践
256 4
|
IDE 开发工具
LabVIEW LINX Toolkit控制Arduino设备(拓展篇—1)
在LabVIEW和单片机(Arduino)系列专栏中使用LabVIEW Interface for Arduino接口工具包实现和Arduino联控。本篇博文将拓展讲解另一款新的接口工具包实现对Arduino的控制,它就是LabVIEW LINX。 LabVIEW LINX Toolkit可支持驱动Raspberry Pi、BeagleBoard和Arduino开发板,包含数字、模拟、SPI、I2C、UART、PWM等驱动接口,非常适合创客开发实践。
|
程序员 Go iOS开发
开启iOS/Mac开发之旅,过来人告诉你16件事
我曾向iOS开发者推荐了Twitter上最值得关注的30个人,收到了不少开发者的反馈,受此鼓舞,我向知名iOS开发者和设计师询问了这样一个问题–回到你开始iOS/Mac app开发的时候,你以现在的角度会给“最初的你”哪些建议。
294 0
开启iOS/Mac开发之旅,过来人告诉你16件事
|
iOS开发
iOS事件响应控制(一)
iOS事件响应控制
168 0
iOS事件响应控制(一)
|
Web App开发 缓存 UED
一起谈.NET技术,Silverlight程序集缓存巧妙设置 优化用户体验
  Silverlight中的程序集缓存可以将一些独立的程序集放在XAP包外边并可以缓存在客户端的浏览器中,这样就可以减少程序启动时下载XAP包的时间。默认情况下Silverlight并没有开启程序集缓存,因此需要自己动手开启。
1131 0
|
数据可视化 Shell API
使用VS2010为Windows“.NET研究”7编写一个杀手级WPF应用
  当你使用最新的操作系统,在最新的框架上构建应用程序,并且使用最新版本的Visual Studio的时候,你可以使用许多令人兴奋的新特性。你可以学习一下怎样使用Visual Studio 2010, WPF 4 和 the Windows API Code Pack给你的应用程序添加Windows 7 UI。
964 0