【UWP】使用 Rx 改善 AutoSuggestBox

来源:https://www.cnblogs.com/h82258652/archive/2018/11/30/10045937.html
-Advertisement-
Play Games

在 UWP 中,有一個控制項叫 AutoSuggestBox,它的主要成分是一個 TextBox 和 ComboBox。使用它,我們可以做一些根據用戶輸入來顯示相關建議輸入的功能,例如百度首頁搜索框那種效果: 在看這篇文章之前,我建議先看看老周寫的這一篇:https://www.cnblogs.com ...


在 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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 小伙伴們好今天給大家分享一下冒泡排序法: 它的原理是利用兩次for迴圈,外層for用來控制輪數,內層for用來控制每一輪比較的次數,比較時每相鄰的兩個數進行比較依次向後,這樣每一輪都會出比較出一個極值。 ...
  • 一道DP。 給你一個矩陣裡面有很多數,你需要從上往下找到一種跳躍方法使得經過的點的價值之和最大。 具體題面見鏈接 洛谷P1107 BZOJ1270 很明顯是一個二維的DP。 混搭碼風,求諒解。 ...
  • Python基礎知識(18):面向對象高級編程 使用__slots__:限制實例的屬性,只允許實例對類添加某些屬性 (1)實例可以隨意添加屬性 (2)某個實例綁定的方法對另一個實例不起作用 (3)給類綁定方法市所有類都綁定了該方法,且所有實例都可以調用該方法 用__slots__定義屬性反對這個類的 ...
  • 熟悉java的過程中發現了一些小問題,定義的類Car老是提示必須在它自己的文件中定義。自己想了想試試把Car繼承的類Vehicle中的public換到Car類中,結果發現輸出問題很大。它只顯示了一個輸出(我代碼有4個輸出)。後來查了查度娘原來是public類的類名和存儲的.java名字要一樣。問題雖 ...
  • 感謝博客園團隊日夜為廣大需要獲取知識人們所做的奉獻 博客園團隊您們辛苦了 ASP.NET MVC 實現有論壇功能的網站(有iis發佈網站 這是之前寫的... www.lazyfitness.cn 經過一個月的修正 通過ASP.NET MVC 所做的網站www.lazyfitness.cn正式發佈了! ...
  • 翻看了下以前大學學習的一些小項目,突然發現有個項目比較有意思,覺得有必要把它分享出來。當然現在看來,裡面有很多的不足之處,但因博主現在已經工作,沒有時間再去優化。這個項目就是利用C#編寫一個Windows系統下的掃雷小游戲。 首先講下掃雷小游戲的玩法: (1)掃雷就是要把所有非地雷的格子揭開即勝利; ...
  • ASP.NET與ASP.NET Core很類似,但它們之間存在一些細微區別以及ASP.NET Core中新增特性的使用方法,在此之前也寫過一篇簡單的對比文章ASP.NET MVC應用遷移到ASP.NET Core及其異同簡介,但沒有進行深入的分析和介紹,在真正使用ASP.NET Core進行開發時, ...
  • ASP.NET Core操作MySql資料庫, 這樣整套環境都可以佈署在Linux上 使用微軟的 Microsoft.EntityFrameworkCore(2.1.4) 和MySql出的 MySql.Data.EntityFrameworkCore(8.0.13) 軟體版本 Asp.net Cor ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...