我正在编写一个服务器应用程序,我希望它是基于控制台的。我需要用户能够输入不同的命令,但同时有可能在用户写入时将某些内容输出到控制台。这会使缓冲区混乱。有没有干净的方法呢?
感谢。
答案 0 :(得分:4)
我开始研究测试程序,以展示如何将控制台划分为输出区域和输入区域,输出区域随着输出区域扩展而输出更多。它还不完美,但你可以将它发展成你正在寻找的答案:
static int outCol, outRow, outHeight = 10;
static void Main(string[] args)
{
bool quit = false;
System.DateTime dt = DateTime.Now;
do
{
if (Console.KeyAvailable)
{
if (Console.ReadKey(false).Key == ConsoleKey.Escape)
quit = true;
}
System.Threading.Thread.Sleep(0);
if (DateTime.Now.Subtract(dt).TotalSeconds > .1)
{
dt = DateTime.Now;
WriteOut(dt.ToString(" ss.ff"), false);
}
} while (!quit);
}
static void WriteOut(string msg, bool appendNewLine)
{
int inCol, inRow;
inCol = Console.CursorLeft;
inRow = Console.CursorTop;
int outLines = getMsgRowCount(outCol, msg) + (appendNewLine?1:0);
int outBottom = outRow + outLines;
if (outBottom > outHeight)
outBottom = outHeight;
if (inRow <= outBottom)
{
int scrollCount = outBottom - inRow + 1;
Console.MoveBufferArea(0, inRow, Console.BufferWidth, 1, 0, inRow + scrollCount);
inRow += scrollCount;
}
if (outRow + outLines > outHeight)
{
int scrollCount = outRow + outLines - outHeight;
Console.MoveBufferArea(0, scrollCount, Console.BufferWidth, outHeight - scrollCount, 0, 0);
outRow -= scrollCount;
Console.SetCursorPosition(outCol, outRow);
}
Console.SetCursorPosition(outCol, outRow);
if (appendNewLine)
Console.WriteLine(msg);
else
Console.Write(msg);
outCol = Console.CursorLeft;
outRow = Console.CursorTop;
Console.SetCursorPosition(inCol, inRow);
}
static int getMsgRowCount(int startCol, string msg)
{
string[] lines = msg.Split('\n');
int result = 0;
foreach (string line in lines)
{
result += (startCol + line.Length) / Console.BufferWidth;
startCol = 0;
}
return result + lines.Length - 1;
}
答案 1 :(得分:2)
我个人会使用事件处理程序管理一个同时处理输入和输出的控制台,创建一个类ScreenManager或其他什么,在该类中添加一个void RunProgram()方法,创建一个带有处理程序和必需变量的事件用于读取输入键“Console.ReadKey(bool).key”。
static Consolekey newKey;
在你的主程序上,创建你的类的实例“whatev你叫它”,然后创建一个该实例内部方法的线程,Thread coreThread = new Thread(delegate(){myinstance.myProgramMrthod()}); < / p>
在你的主循环中循环,直到线程启动并运行。 while(!Thread.IsAlive);
然后创建主程序循环。
while (true)
{
}
然后为安全,加入你的自定义线程,这样主程序就不会继续,直到关闭/处理自定义线程。
customThread.Join();
你现在有两个线程单独运行。
回到你的班级,在你的事件处理程序方法中创建一个开关。
switch (newkey)
{
case Consolekey.enter
Console.WriteLine("enter pressed");
break;
ect, ect.
default:
Console.write(newkey); // writes character key that dont match above conditions to the screen.
break;
}
坚持allyour逻辑,因为你想要处理密钥。 How to use multiple modifier keys in C# 可能会有所帮助。
在你的实例的方法RunProgram()或whatev中你选择调用它,在你完成所需的任何代码之后,创建一个无限循环来检查密钥更改。
while (true)
{
newKey = Console.ReadKey(true).Key;
if (newKey != oldKey)
{
KeyChange.Invoke();
}
}
此循环存储按下的任何键,然后检查是否为新键,如果为true则触发事件。
你现在拥有你所寻找的核心,一个循环请求新密钥的字符串,而主循环可以自由显示你想要显示的任何文本。
我可以想到的两个可修复的错误,一个是“默认”内部开关将打印到控制台的大写字母或字符串。另一个是添加到控制台的任何文本都添加到光标点,因此它会添加到用户刚刚输入的文本中。
我会,因为我刚刚创建它,你如何管理文本被添加到控制台。我再次使用一个事件。我可以在整个过程中使用方法和函数,但事件会增加程序的灵活性,我认为。
好的,所以我们希望能够在控制台中添加文本,而不会让我们输入的输入变得烦恼。保持输入在底部;
创建一个具有带有字符串参数的签名的新委托,void委托myDelegate(字符串Arg)。然后用这个委托创建一个事件,称之为newline,newinput,whatev你喜欢。
事件处理程序将采用字符串参数(重新显示控制台更新文本:您希望在用户输入上方插入控制台)它将获取用户输入控制台的文本,存储它,然后打印将paramiter字符串输出到控制台上,然后打印输入underneith的用户。
我个人选择在方法外部的顶部创建一个静态字符串,将其初始化为空,因为它经常被使用,你不想创建一个新的标识符,然后每次调用该方法时初始化变量,然后在方法结束时处理它,再次重新创建一个新的,再次。
调用字符串“input”或其他。
在keychange事件句柄的默认区域中添加输入+ = newkey。
在Consolekey.enter部分控制台中写入输入然后输入= string.empty或string =“”。
在事件处理程序中添加一些逻辑。
public void OnInsert(string Argument)
{
Console.CursorTop -= 1;
// moves the cursor to far left so new input overwrites the old.
// if arg string is longer, then print arg string then print input // string.
if (Argument.Length > input.Length)
{
Console.WriteLine(Argument);
Console.WriteLine(input);
}
else
{
// if the users input if longer than the argument text then print
// out the argument text, then print white spaces to overwrite the
// remaining input characters still displayed on screen.
for (int i = 0; i < input.Length;i++ )
{
if (i < Argument.Length)
{
Console.Write(Argument[i]);
}
else
{
Console.Write(' ');
}
}
Console.Write(Environment.NewLine);
Console.WriteLine(input);
}
}
hope this helps some of you, its not perfect, a quick put together test that works enough to be built on.
答案 2 :(得分:1)
如果您需要在用户输入时允许输出到达,我建议将输出发送到新窗口。因此,您可以拥有一个用于启动应用程序的窗口,然后它会生成一个线程以打开一个新的控制台进行输入,然后它继续将任何输出消息发送到原始窗口。如果你试图将所有内容保存在同一个窗口中,我认为你会遇到太多的资源锁定问题。
答案 3 :(得分:1)
如果将服务器视为客户端/服务器应用程序,则此类问题会变得更加简单。让服务器与发送命令和接收输出的客户端管理应用程序建立“n”连接。客户端应用程序可以完全分离输入和输出,一个线程处理输入,一个处理输出。
如果输入线程正在输入一行,输出线程可能会阻塞,而当线路被取消或提交时,输出线程会解锁。
答案 4 :(得分:1)
我得到了使用Console.MoveBufferArea()
的示例,但是请注意,该方法不适用于Windows以外的平台,因为该方法在这些平台上为not implemented。
在此示例中,您将在代码中使用Read()
代替Console.ReadLine()
,并使用Log(...)
代替Console.WriteLine(...)
。
class Program
{
static void Main(string[] args)
{
// Reader
new Thread(() =>
{
string line;
while ((line = Read()) != null)
{
//...
}
Environment.Exit(0);
}).Start();
// Writer
new Thread(() =>
{
while (true)
{
Thread.Sleep(1000);
Log("----------");
}
}).Start();
}
static int lastWriteCursorTop = 0;
static void Log(string message)
{
int messageLines = message.Length / Console.BufferWidth + 1;
int inputBufferLines = Console.CursorTop - lastWriteCursorTop + 1;
Console.MoveBufferArea(sourceLeft: 0, sourceTop: lastWriteCursorTop,
targetLeft: 0, targetTop: lastWriteCursorTop + messageLines,
sourceWidth: Console.BufferWidth, sourceHeight: inputBufferLines);
int cursorLeft = Console.CursorLeft;
Console.CursorLeft = 0;
Console.CursorTop -= inputBufferLines - 1;
Console.WriteLine(message);
lastWriteCursorTop = Console.CursorTop;
Console.CursorLeft = cursorLeft;
Console.CursorTop += inputBufferLines - 1;
}
static string Read()
{
Console.Write(">"); // optional
string line = Console.ReadLine();
lastWriteCursorTop = Console.CursorTop;
return line;
}
}
答案 5 :(得分:0)
您是否尝试过调用OpenStandardInput,读取任何输入并重置其位置,然后写入输出流。然后,您可以再次调用OpenStandardInput并将数据填回流中。
答案 6 :(得分:0)
我认为没有完美的方法来实现这一目标。 telnet做了什么(至少我使用的最后一个版本)没有打印任何输入(只是读取击键)并只是在输出到达时打印输出。另一种方法是将需要输出的任何数据存储在缓冲区中,并且只有在用户输入命令后才打印出来。 (你甚至可以给输出添加时间戳,使其更加明显。)我真的看不到更好的选择 - 你不可避免地会遇到使用同步I / O接口(即命令行)和后端的异步操作。