为什么.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;
}
我认为根本问题是由斯蒂芬Toub的评论中this GitHub issue暴露了最终代码:
您可能考虑到我们现在只在ReadKey(intercept:true)调用期间禁用回声的事实,所以在用户打字和您调用ReadKey(截距:true)之间的竞赛中,即使在您希望的时候,键也可能被回显它不会,但你不会失去击键。
这是冷的舒适,但准确。这是一场很难赢的比赛。核心问题是Linux终端和Windows终端的工作方式截然不同。它的运行方式更像70年代的电传打字机。不管计算机是否注意到你键入的内容,电报类型都会在你的纸上反复输入你敲打的内容。直到你按下回车键,电脑才开始转动文字。
与Windows控制台非常不同,它要求程序具有活动的Read调用以回显任何键入的文本。
所以这是一个非常根本的不匹配控制台API。它需要一个Echo
属性给你任何正确的做法的希望。因此,您可以将其设置为false,然后再开始接受输入并自行处理回显。它仍然是一场比赛,但至少你有一枪打扫任何预先输入的文字。
您现在唯一的一半体面的解决方法是disable echo,然后再启动程序。要求你通过你的方法做所有的输入。
对我来说幸运的是,通过该方法运行我的所有输入都非常好,所以我认为这个解决方法比“半个体面”多一点。谢谢! – tyjkenn
我测试,结果发现,确实Console.ReadKey(true)
有一些错误,其中的关键实际上得到回显到控制台时打字速度快或重复键被解雇。这是你不期待的事情,但为什么会发生,我不知道。
如果你有兴趣在调试它,你可以看看下面的源代码
https://referencesource.microsoft.com/#mscorlib/system/console.cs,1476
我选择了把一个解决方法的问题。所以你的方法很少有问题。应该处理Left Arrow
和Right 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);
}
}
}
错误的链接,您需要在.NETCore中使用Console特定于Unix的风格。我认为这是[这一个](https://github.com/dotnet/corefx/blob/master/src/System.Console/src/System/ConsolePal.Unix.cs)。看起来很无辜。也许太无辜:) –
感谢修正@HansPassant,我没有意识到需要看看.NET核心代码库 –
在弄乱了这段代码之后,我设法让这种方法奏效。然而,在我的程序的另一部分中,我以另一种方式使用键盘输入(翻页)时,在整个控制台重新绘制时我无法捕捉到某些键。最终,我不得不禁用回声。 – tyjkenn
它是处理击键的*终端*。你不会得到任何没有发送到你的应用程序的击键。换行符与此无关。 .NET(和Core)将使用操作系统的设置。此外,它已经在Windows中识别'\ n'作为换行符 –
当Linux只使用换行符'\ n'时,Windows可以识别回车符'\ r'。我最初正在检查'\ r',这当然会导致问题。我试图找出是否有其他差异,如我没有考虑。我认为这暗示着这与终端处理按键的方式有所不同。我的意思是,即使我做出完全相同的击键,对ReadKey的调用在不同的机器上给出了不同的结果,毫无疑问,由于不同的系统如何处理这些击键。 – tyjkenn
不太确定这是否有意义。在Linux上,您仍然按Enter键,您不需要按Ctrl + J即可获得\ n。 ReadKey告诉你被按下的键,而不是它产生的字符。所以只要你使用Key而不是KeyChar,那么就不会有问题。也许你暴露了一个兼容性问题,这是非常新的,所以这是不可想象的。他们支持十种不同的Linux风格,Raspian不是其中之一。最好告诉他们,使用[新问题按钮](https://github.com/dotnet/coreclr/issues)。 –