代码之家  ›  专栏  ›  技术社区  ›  Alex Turpin

同时控制台输入和输出?

  •  7
  • Alex Turpin  · 技术社区  · 15 年前

    我正在编写一个服务器应用程序,我希望它是基于控制台的。我需要用户能够输入不同的命令,但同时有一种可能性,即当用户在写东西的时候,一些东西将被输出到控制台。这会把缓冲区搞得一团糟。有什么干净的方法可以做到吗?

    谢谢。

    7 回复  |  直到 6 年前
        1
  •  4
  •   BlueMonkMN    15 年前

    我开始编写一个测试程序,演示如何将控制台划分为一个输出区域和一个输入区域,在这里输入区域随着输出区域的扩展而向下移动。它还不是完美的,但是你可以把它发展成你想要的答案:

      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;
      }
    
        2
  •  2
  •   Community    7 年前

    我个人会使用事件处理程序来管理一个同时处理输入和输出的控制台,创建一个类屏幕管理器或其他,在该类中添加一个void runprogram()mthod,创建一个带有处理程序和读取输入键“console.readkey(bool.key)”所需变量的事件。

    static Consolekey newKey;
    

    在主程序上,创建类“whatev you called it”的实例,然后创建该实例的线程内部方法,thread corethread=new thread(delegate()myinstance.myprogrammorthod());

    在主线程中循环,直到线程启动并运行。而(!)线程.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;
    }
    

    把你所有的逻辑都放在你想如何处理钥匙的地方。 How to use multiple modifier keys in C# 可能会有所帮助。

    在实例的方法runprogram()或您选择调用它的对象中,在您完成了所需的任何代码之后,创建一个无限循环来检查键的更改。

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

    此循环存储按下的任何键,然后检查是否有新键,如果为真,则激发事件。

    现在,您有了所需内容的核心,一个字符串循环作为新键的kng,而主循环可以自由显示您希望显示的任何文本。

    有两个我能想到的可以修复的错误,一个是开关内部的“默认”将以大写或字符串的形式打印到控制台。另一种是在光标点添加任何添加到控制台的文本,这样它就添加到用户刚输入的文本中。

    尽管如此,既然我已经做到了,那么如何管理添加到控制台中的文本。我再次使用一个事件。我可以在整个过程中使用方法和函数,但我认为事件增加了程序的移动灵活性。

    好的,所以我们希望能够向控制台添加文本,而不会影响我们输入的内容。将输入保持在底部;

    创建一个带有字符串参数signiture的新委托,void delegate mydelegate(string arg)。然后用这个委托创建一个事件,称之为newline、newinput和您喜欢的什么。

    事件处理程序将接受一个字符串参数(重新发送控制台更新文本:要插入到用户输入上方的控制台中的内容),它将获取用户已输入控制台中的文本,存储该文本,然后将参数字符串打印到控制台上,然后在neith下打印出用户输入。

    我个人选择在方法的顶部创建一个静态字符串,将其初始化为空,因为它将被频繁使用,并且您不希望创建一个新的标识符,然后在每次调用方法时初始化变量,然后在方法的末尾处理它,只需重新创建一个新的标识符,然后再重新创建一个。

    调用字符串“input”或其他。

    在keychange事件句柄的默认区域中,添加input+=newkey。

    在consolekey.enter部分console writline input,然后input=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.
    
        3
  •  1
  •   Lucas B    15 年前

    如果您需要允许输出在用户键入时到达,我建议将输出发送到一个新窗口。因此,您可以有一个用于启动应用程序的窗口,然后它生成一个线程来打开一个新的控制台进行输入,然后继续向原始窗口发送任何输出消息。我认为,如果您试图将所有内容保持在同一窗口中,会遇到太多的资源锁定问题。

        4
  •  1
  •   John Saunders    15 年前

    如果将服务器视为客户机/服务器应用程序,那么这类问题会变得更简单一些。让服务器与发送命令和接收输出的客户机管理应用程序有“n”连接。客户机应用程序可以完全分离输入和输出,有一个线程处理输入,还有一个线程处理输出。

    如果输入线程在输入行的中间,则输出线程可能会阻塞,如果取消或提交行,则会取消阻塞。

        5
  •  1
  •   Tera    6 年前

    我的示例使用 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;
        }
    }
    
        6
  •  0
  •   Richard Szalay    15 年前

    你试过打电话吗 OpenStandardInput ,读取任何输入并重置其位置,然后写入输出流。然后,您可以再次调用OpenStandardInput并将数据填充回流中。

        7
  •  0
  •   Noldorin    15 年前

    我认为没有完美的方法来实现这一点。telnet所做的(至少是我使用的最后一个版本)不是打印任何输入(只是读取击键),而是在输出到达时简单地打印输出。另一种方法是将需要输出到控制台的任何数据存储在一个缓冲区中,并且只有在用户完成输入命令后才打印出来。(您甚至可以给输出设置时间戳,以使其更明显。)我真的看不到更好的选择—您不可避免地会遇到使用同步I/O接口(即命令行)和后端异步操作的问题。