如何在命令行中監聽用戶輸入文本的改變?

  • 2020 年 2 月 10 日
  • 筆記

這真是一個詭異的需求。為什麼我需要在命令行中得知用戶輸入文字的改變啊!實際上我希望實現的是:在命令行中輸入一段文字,然後不斷地將這段文字發往其他地方。

本文將介紹如何監聽用戶在命令行中輸入文本的改變。


在命令行中輸入有三種不同的方法:

  • Console.Read()
    • 用戶可以一直輸入,在用戶輸入回車之前,此方法都會一直阻塞。而一旦用戶輸入了回車,你後面的 Console.Read 就不會一直阻塞了,直到把用戶在這一行輸入的文字全部讀完。
  • Console.ReadKey()
    • 用戶輸入之前此方法會一直阻塞,用戶只要按下任何一個鍵這個方法都會返回並得到用戶按下的按鍵資訊。
  • Console.ReadLine()
    • 用戶可以一直輸入,在用戶輸入回車之前,此方法都會一直阻塞。當用戶輸入了回車之後,此方法會返回用戶在這一行輸入的字元串。

從表面上來說,以上這三個方法都不能滿足我們的需求,每一個方法都不能直接監聽用戶的輸入文本改變。尤其是 Console.Read()Console.ReadLine() 方法,在用戶輸入回車之前,我們都得不到任何資訊。看起來我們似乎只能通過 Console.ReadKey() 來完成我們的需求了。

但是,一旦我們使用了 Console.ReadKey(),我們將不能獲得另外兩個方法中的輸入體驗。例如,我們按下退格鍵(BackSpace)可以刪除游標的前一個字元,按下刪除鍵(Delete)可以刪除游標的後一個字元,按下左右鍵可以移動游標到合適的文本上。

然而,不幸的是,除了這三個方法,我們還真的沒有原生的方法來實現命令行的輸入監聽了。所以看樣子我們需要自己來使用 Console.ReadKey() 實現用戶輸入文字的監聽了。

我在 如何讓 .NET Core 命令行程式接受密碼的輸入而不顯示密碼明文 – walterlv 一問中有說到如何在命令行中輸入密碼而不會顯示明文。我們用到的就是此部落格中所述的方法。

var builder = new StringBuilder();  while (true)  {      var i = Console.ReadKey(true);        if (i.Key == ConsoleKey.Enter)      {          Console.WriteLine();          // 用戶在這裡輸入了回車,於是我們需要結束輸入了。      }        if (i.Key == ConsoleKey.Backspace)      {          if (builder.Length > 0)          {              Console.Write("b b");              builder.Remove(builder.Length - 1, 1);          }      }      else      {          builder.Append(i.KeyChar);          Console.Write(i.KeyChar);      }  }

然而實際上在使用此方法的時候並不符合預期,因為退格的時候我們得到了半個字:

額外的,我們還不支援左右鍵移動游標,而且按住控制鍵的時候也會輸入一個字元;這些都是我還沒有處理的。

這就意味著我們使用 "b b" 來刪除我們輸入的字元的時候,有可能在一些字元的情況下我們需要刪除兩個字元寬度。

然而如何獲取一個字的字元寬度呢?還是很複雜的。於是我很暴力地使用 OnChar函數的中文處理問題,退格鍵時,怎麼處理-CSDN論壇 論壇中使用的方法直接通過編碼範圍判斷中文的方式來推測字元寬度。如果你有更正統的方法,非常歡迎指導我。

簡單起見,我寫了一個類來封裝輸入文本改變。閱讀以下程式碼,或者訪問 Walterlv.CloudKeyboard/ConsoleLineReader.cs 閱讀此類型的最新版本的程式碼。

using System;  using System.Text;    namespace Walterlv.Demo  {      public sealed class ConsoleLineReader      {          public event EventHandler<ConsoleTextChangedEventArgs> TextChanged;            public string ReadLine()          {              var builder = new StringBuilder();              while (true)              {                  var i = Console.ReadKey(true);                    if (i.Key == ConsoleKey.Enter)                  {                      var line = builder.ToString();                      OnTextChanged(line, i.Key);                      Console.WriteLine();                      return line;                  }                    if (i.Key == ConsoleKey.Backspace)                  {                      if (builder.Length > 0)                      {                          var lastChar = builder[builder.Length - 1];                          Console.Write(lastChar > 0xA0 ? "bb  bb" : "b b");                          builder.Remove(builder.Length - 1, 1);                      }                  }                  else                  {                      builder.Append(i.KeyChar);                      Console.Write(i.KeyChar);                  }                    OnTextChanged(builder.ToString(), i.Key);              }          }            private void OnTextChanged(string line, ConsoleKey key)          {              TextChanged?.Invoke(this, new ConsoleTextChangedEventArgs(line, key));          }      }        public class ConsoleTextChangedEventArgs : EventArgs      {          public ConsoleTextChangedEventArgs(string line, ConsoleKey consoleKey)          {              Line = line;              ConsoleKey = consoleKey;          }            public string Line { get; }          public ConsoleKey ConsoleKey { get; }      }  }

那麼使用的時候,則會簡單很多:

var reader = new ConsoleLineReader();  reader.TextChanged += (sender, args) =>  {      // 這裡可以在用戶每次輸入的文本改變的時候執行。  };    while (true)  {      // 我在這裡循環執行,於是即便用戶按了回車,也會繼續輸入。      reader.ReadLine();  }
參考資料
本文會經常更新,請閱讀原文: https://blog.walterlv.com/post/notify-text-changed-when-typing-in-

本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名 呂毅 (包含鏈接: https://blog.walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發布。如有任何疑問,請 與我聯繫 ([email protected])