为什么.NET Core在Raspbian上处理ReadKey的方式不同?

问题描述:

我在写一个.NET Core控制台应用程序。我想将控制台输入限制为每个输入的一定数量的最大字符数。我有一些代码可以通过构建一个字符串Console.ReadKey()而不是Console.ReadLine()来完成这一切,它能够在Windows上完美地测试它。然后,当我部署到运行Raspbian的Raspberry Pi 3时,我很快遇到了各种各样的问题。我记得Linux处理与Windows不同的行结束符,并且似乎退格处理也是不同的。我改变了我处理这些方式的方式,关闭了ConsoleKey而不是角色,并且换行符问题消失了,但是退格仅在有时注册。此外,有时候字符会输出到我的输入框外的控制台,即使我将ReadKey设置为不能自行输出到控制台。我是否错过了Linux如何处理控制台输入?为什么.NET Core在Raspbian上处理ReadKey的方式不同?

//I replaced my calls to Console.ReadLine() with this. The limit is the 
//max number of characters that can be entered in the console. 
public static string ReadChars(int limit) 
{ 
    string str = string.Empty; //all the input so far 
    int left = Console.CursorLeft; //store cursor position for re-outputting 
    int top = Console.CursorTop; 
    while (true) //keep checking for key events 
    { 
     if (Console.KeyAvailable) 
     { 
      //true to intercept input and not output to console 
      //normally. This sometimes fails and outputs anyway. 
      ConsoleKeyInfo c = Console.ReadKey(true); 
      if (c.Key == ConsoleKey.Enter) //stop input on Enter key 
       break; 
      if (c.Key == ConsoleKey.Backspace) //remove last char on Backspace 
      { 
       if (str != "") 
       { 
        tr = str.Substring(0, str.Length - 1); 
       } 
      } 
      else if (c.Key != ConsoleKey.Tab && str.Length < limit) 
      { 
       //don't allow tabs or exceeding the max size 
       str += c.KeyChar; 
      } 
      else 
      { 
       //ignore tabs and when the limit is exceeded 
       continue; 
      } 
      Console.SetCursorPosition(left, top); 
      string padding = ""; //padding clears unused chars in field 
      for (int i = 0; i < limit - str.Length; i++) 
      { 
       padding += " "; 
      } 
      //output this way instead 
      Console.Write(str + padding); 
     } 
    } 
    return str; 
} 
+0

它是处理击键的*终端*。你不会得到任何没有发送到你的应用程序的击键。换行符与此无关。 .NET(和Core)将使用操作系统的设置。此外,它已经在Windows中识别'\ n'作为换行符 –

+0

当Linux只使用换行符'\ n'时,Windows可以识别回车符'\ r'。我最初正在检查'\ r',这当然会导致问题。我试图找出是否有其他差异,如我没有考虑。我认为这暗示着这与终端处理按键的方式有所不同。我的意思是,即使我做出完全相同的击键,对ReadKey的调用在不同的机器上给出了不同的结果,毫无疑问,由于不同的系统如何处理这些击键。 – tyjkenn

+4

不太确定这是否有意义。在Linux上,您仍然按Enter键,您不需要按Ctrl + J即可获得\ n。 ReadKey告诉你被按下的键,而不是它产生的字符。所以只要你使用Key而不是KeyChar,那么就不会有问题。也许你暴露了一个兼容性问题,这是非常新的,所以这是不可想象的。他们支持十种不同的Linux风格,Raspian不是其中之一。最好告诉他们,使用[新问题按钮](https://github.com/dotnet/coreclr/issues)。 –

我认为根本问题是由斯蒂芬Toub的评论中this GitHub issue暴露了最终代码:

您可能考虑到我们现在只在ReadKey(intercept:true)调用期间禁用回声的事实,所以在用户打字和您调用ReadKey(截距:true)之间的竞赛中,即使在您希望的时候,键也可能被回显它不会,但你不会失去击键。

这是冷的舒适,但准确。这是一场很难赢的比赛。核心问题是Linux终端和Windows终端的工作方式截然不同。它的运行方式更像70年代的电传打字机。不管计算机是否注意到你键入的内容,电报类型都会在你的纸上反复输入你敲打的内容。直到你按下回车键,电脑才开始转动文字。

与Windows控制台非常不同,它要求程序具有活动的Read调用以回显任何键入的文本。

所以这是一个非常根本的不匹配控制台API。它需要一个Echo属性给你任何正确的做法的希望。因此,您可以将其设置为false,然后再开始接受输入并自行处理回显。它仍然是一场比赛,但至少你有一枪打扫任何预先输入的文字。

您现在唯一的一半体面的解决方法是disable echo,然后再启动程序。要求你通过你的方法做所有的输入。

+0

对我来说幸运的是,通过该方法运行我的所有输入都非常好,所以我认为这个解决方法比“半个体面”多一点。谢谢! – tyjkenn

我测试,结果发现,确实Console.ReadKey(true)有一些错误,其中的关键实际上得到回显到控制台时打字速度快或重复键被解雇。这是你不期待的事情,但为什么会发生,我不知道。

如果你有兴趣在调试它,你可以看看下面的源代码

https://referencesource.microsoft.com/#mscorlib/system/console.cs,1476

我选择了把一个解决方法的问题。所以你的方法很少有问题。应该处理Left ArrowRight Arrow键,否则他们不应该被允许。我选择了后者一个通过添加以下代码

if (c.Key == ConsoleKey.LeftArrow || c.Key == ConsoleKey.RightArrow) { 
    continue; 
} 

当您使用以下

Console.Write(str + padding); 

你基本上扰乱光标位置也,这是不正确键入的字符。因此,你需要使用设置光标位置后,这低于

Console.CursorLeft = str.Length; 

现在来处理泄漏键这可能是一个.NET错误的部分,我添加了下面的代码

else 
{ 
    //ignore tabs and when the ilimit is exceeded 
    if (Console.CursorLeft > str.Length){ 

     var delta = Console.CursorLeft - str.Length; 
     Console.CursorLeft = str.Length; 
     Console.Write(new String(' ',delta)); 
     Console.CursorLeft = str.Length; 
    } 
    continue; 
} 

所以我们检查因为任何看不见的理由都会回应,然后抹去它。然后,压力测试它

$ docker run -it console 
Please enter some text: 
tarun6686e 
You entered: tarun6686e 

下面是我用了

using System; 

namespace ConsoleTest 
{ 
    public class Program { 
     public static string tr=""; 
     //I replaced my calls to Console.ReadLine() with this. The limit is the 
     //max number of characters that can be entered in the console. 
     public static string ReadChars(int limit) 
     { 
      string str = string.Empty; //all the input so far 
      int left = Console.CursorLeft; //store cursor position for re-outputting 
      int top = Console.CursorTop; 

      while (true) //keep checking for key events 
      { 
       if (Console.KeyAvailable) 
       { 
        //true to intercept input and not output to console 
        //normally. This sometimes fails and outputs anyway. 
        ConsoleKeyInfo c = Console.ReadKey(true); 
        string name = Enum.GetName(typeof(ConsoleKey), c.Key); 
        var key = c.KeyChar; 
        // Console.WriteLine(String.Format("Name={0}, Key={1}, KeyAscii={2}", name, key,(int)key)); 
        if (c.Key == ConsoleKey.Enter) //stop input on Enter key 
         { 
          Console.WriteLine(); 
          break; 
         } 
        if (c.Key == ConsoleKey.LeftArrow || c.Key == ConsoleKey.RightArrow) { 
         continue; 
        } 

        if (c.Key == ConsoleKey.Backspace) //remove last char on Backspace 
        { 
         if (str != "") 
         { 
          str = str.Substring(0, str.Length - 1); 
         } 
        } 
        else if (c.Key != ConsoleKey.Tab && str.Length < limit) 
        { 
         //don't allow tabs or exceeding the max size 
         str += c.KeyChar; 
        } 
        else 
        { 
         //ignore tabs and when the ilimit is exceeded 
         if (Console.CursorLeft > str.Length){ 

          var delta = Console.CursorLeft - str.Length; 
          Console.CursorLeft = str.Length; 
          Console.Write(new String(' ',delta)); 
          Console.CursorLeft = str.Length; 
         } 
         continue; 
        } 
        Console.SetCursorPosition(left, top); 
        string padding = ""; //padding clears unused chars in field 
        for (int i = 0; i < limit - str.Length; i++) 
        { 
         padding += " "; 
        } 
        //output this way instead 
        Console.Write(str + padding); 
        Console.CursorLeft = str.Length; 
       } 
      } 
      return str; 
     } 

     public static void Main(string[] args) { 
      Console.WriteLine("Please enter some text: "); 
      var text = ReadChars(10); 

      Console.WriteLine("You entered: " + text); 
     } 
    } 
} 
+0

错误的链接,您需要在.NETCore中使用Console特定于Unix的风格。我认为这是[这一个](https://github.com/dotnet/corefx/blob/master/src/System.Console/src/System/ConsolePal.Unix.cs)。看起来很无辜。也许太无辜:) –

+0

感谢修正@HansPassant,我没有意识到需要看看.NET核心代码库 –

+0

在弄乱了这段代码之后,我设法让这种方法奏效。然而,在我的程序的另一部分中,我以另一种方式使用键盘输入(翻页)时,在整个控制台重新绘制时我无法捕捉到某些键。最终,我不得不禁用回声。 – tyjkenn