简体   繁体   English

C#simultanous控制台输入输出?

[英]C# simultanous console input and output?

I am writing a server app and I want it to be console based. 我正在编写一个服务器应用程序,我希望它是基于控制台的。 I need the user to be able to input different commands, but at the same time there is a possibility that something will be output to the console while the user is writing. 我需要用户能够输入不同的命令,但同时有可能在用户写入时将某些内容输出到控制台。 This messes the buffer up. 这会使缓冲区混乱。 Is there any clean way of doing this? 这样做有什么干净的方法吗?

Thanks. 谢谢。

I started work on a test program to show how you could divide the console into an output area and an input area, where the input area is moved down as the output area expands with more output. 我开始研究测试程序,以展示如何将控制台划分为输出区域和输入区域,输出区域随着输出区域扩展而输出更多。 It's not perfect yet, but you may be able to develop it into the answer you're looking for: 它还不完美,但您可以将它发展成您正在寻找的答案:

  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;
  }

Personally i would use event handlers to managed a console that handles both input and outup at the same time, create a class ScreenManager or whatever, inside that class add a void RunProgram() mthod, create an event with handler and required variables for reading the input key "Console.ReadKey(bool).key". 我个人会使用事件处理程序管理一个同时处理输入和输出的控制台,创建一个类ScreenManager或其他什么,在该类中添加一个void RunProgram()方法,创建一个带有处理程序的事件和所需的变量来读取输入键“Console.ReadKey(bool).key”。

static Consolekey newKey;

on your main program, creat an instance of your class "whatev you called it", then create a thread of that instances internal method, Thread coreThread = new Thread(delegate() {myinstance.myProgramMrthod()}); 在你的主程序中,创建你的类的实例“whatev你叫它”,然后创建该实例内部方法的线程,Thread coreThread = new Thread(delegate(){myinstance.myProgramMrthod()});

loop in your main until the threads up and running. 在你的主循环,直到线程启动并运行。 while (!Thread.IsAlive) ; while(!Thread.IsAlive);

then create the main program loop. 然后创建主程序循环。

while (true)
{
}

then for safty, join your custom thread so the main program doesnt continue until the custom thread is closed/disposed. 然后为安全,加入您的自定义线程,以便主程序不会继续,直到自定义线程关闭/处置。

customThread.Join();

you now have two threads running seperatly. 你现在有两个线程单独运行。

back to your class, create a switch inside your event handler method. 回到你的班级,在你的事件处理程序方法中创建一个开关。

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;
}

stick allyour logic inhere with how you want to handle keys. 坚持allyour逻辑与你想要处理键的方式。 How to use multiple modifier keys in C# might be of some help. 如何在C#中使用多个修饰键可能会有所帮助。

inside your instance's method RunProgram() or whatev you choose to call it, after you've done whatever code you need to, create an infinite loop to check for key change. 在您的实例的方法RunProgram()或whatev中,您选择调用它,在您完成所需的任何代码之后,创建一个无限循环来检查密钥更改。

while (true)
{
newKey = Console.ReadKey(true).Key;
if (newKey != oldKey)
{
KeyChange.Invoke();
}
}

this loop stores any key pressed and then checks to see if theres a new key, if true fires the event. 此循环存储按下的任何键,然后检查是否为新键,如果为true则触发事件。

you now have the core of what your looking for, one string that loops askng for a new key, whilst the main loop is free to display whatever text you wish to display. 你现在拥有你所寻找的核心,一个循环请求新密钥的字符串,而主循环可以自由显示你想要显示的任何文本。

two fixable bugs with this that i can think of, one is "default" inside switch will print to console in caps or strings. 我能想到的两个可修复的错误,一个是开关内的“默认”将打印到帽子或字符串中的控制台。 and the other is any text added to the console is added at the cursor point so it adds to the text the user has just input. 另一个是添加到控制台的任何文本都添加到光标点,因此它会添加到用户刚刚输入的文本中。

hwoever i will, since i've just made it, how you have to manager the text been added to the console. 我会,因为我刚刚创建它,你如何管理文本被添加到控制台。 again im using an event. 我再次使用一个事件。 i could use methods and functions throughout but events add move flexability to the program, i think. 我认为,我可以使用方法和功能,但事件会增加程序的灵活性。

okay so we want to be able to add text to the console, without it upsetting the input we enter. 好的,所以我们希望能够在控制台上添加文本,而不会让我们输入的输入变得烦恼。 keeping the input at the bottom; 保持输入在底部;

create a new delegate that has a signiture with a string argument, void delegate myDelegate(string Arg). 创建一个具有带有字符串参数的签名的新委托,void委托myDelegate(字符串Arg)。 then create an event with this delegate, call it newline, newinput, whatev you like. 然后用这个委托创建一个事件,称之为newline,newinput,whatev你喜欢。

the events handler will take a string argument (repersenting the console update text: what you want to insert into the console above the users input) it will grab the text the user has been entering into the console, store it, then print out the paramiter string onto the console, then print out the users input underneith. 事件处理程序将采用字符串参数(重新显示控制台更新文本:您要在用户输入上方插入控制台)它将获取用户输入控制台的文本,存储它,然后打印出paramiter字符串到控制台上,然后打印用户输入underneith。

personally i chose to create a static string at the top outside the method, initialise it to empty, cos its going to be frequently used and you dont want to be creating a new identifyer and then initialising the variable everytime the method is called, then dispose of it at the end of the method, only to recreate a new one again, and again. 我个人选择在方法外部的顶部创建一个静态字符串,将其初始化为空,因为它经常被使用,你不想创建一个新的标识符,然后在每次调用该方法时初始化变量,然后处置它在方法结束时,只是再次重新创建一个新的。

call the string "input" or whatever. 调用字符串“input”或其他什么。

in the default area of the keychange event handle add input +=newkey. 在keychange事件句柄的默认区域中添加input + = newkey。

in the Consolekey.enter section console writline input then input = string.empty Or string = "". 在Consolekey.enter部分控制台写入输入然后输入= string.empty或string =“”。

in the event handler add some logic. 在事件处理程序中添加一些逻辑。

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.

I got my example working using Console.MoveBufferArea() , but note that this won't work on platforms other than Windows because the method is not implemented on those platforms. 我的示例使用Console.MoveBufferArea() ,但请注意,这不适用于Windows以外的平台,因为该方法在这些平台上实现

With this example you would use Read() instead of Console.ReadLine() and Log(...) instead of Console.WriteLine(...) in your code. 在此示例中,您将在代码中使用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;
    }
}

If you need to allow output to arrive while the user is typing I recommend sending the output to a new window. 如果您需要在用户输入时允许输出到达,我建议将输出发送到新窗口。 So, you could have one window that is used to start the application and then it spawns a thread to open a new console for input and then it continues to send any output messages to the original window. 因此,您可以拥有一个用于启动应用程序的窗口,然后它会生成一个线程以打开一个新的控制台进行输入,然后它继续将任何输出消息发送到原始窗口。 I think you will run in to too many resource locking issues if you try to keep everything in the same window. 如果您尝试将所有内容保存在同一窗口中,我认为您将遇到太多的资源锁定问题。

This sort of thing becomes a somewhat simpler problem if you treat the server as a client/server application. 如果将服务器视为客户端/服务器应用程序,则此类问题会变得更简单。 Let the server have "n" connections to client admin applications that send commands and receive output. 让服务器与发送命令和接收输出的客户端管理应用程序建立“n”连接。 The client application could completely separate input and output, having one thread to handle input, and one to handle output. 客户端应用程序可以完全分离输入和输出,一个线程处理输入,另一个处理输出。

The output thread could block if the input thread is in the middle of entering a line, and unblock when the line is either cancelled or committed. 如果输入线程正在进入一行,则输出线程可能会阻塞,而当线路被取消或提交时,输出线程会阻塞。

Have you tried calling OpenStandardInput , reading any input and resetting it's position, then writing to the output stream. 您是否尝试过调用OpenStandardInput ,读取任何输入并重置其位置,然后写入输出流。 Afterwards, you can call OpenStandardInput again and fill the data back into the stream. 然后,您可以再次调用OpenStandardInput并将数据填充回流中。

There's no perfect way of accomplishing this, I think. 我想,没有完美的方法来实现这一目标。 What telnet does (at least the last version I used) was not print any input (just read the keystrokes) and simply print the output as it arrives. telnet做了什么(至少我使用的最后一个版本)没有打印任何输入(只是读取击键)并只是在输出到达时打印输出。 The alternative is to store any data that needs to be output to the console in a buffer, and only print it once the user has finished entering their command. 另一种方法是将需要输出的任何数据存储在缓冲区中,并且只有在用户输入命令后才打印出来。 (You could even timestamp the output, to make it more obvious.) I really can't see any better alternative here - you're inevitably going to run into problems using a synchronous I/O interface (ie the command line) together with asynchronous operations in the backend. (你甚至可以给输出添加时间戳,使其更加明显。)我真的看不到更好的选择 - 你不可避免地会遇到使用同步I / O接口(即命令行)和后端的异步操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM