如何在多线程控制台中保留输入行?

时间:2011-04-24 17:52:39

标签: c# multithreading console-application

这个问题已经让我感到困扰了一段时间,我意识到很难描述我在寻找什么。我希望能够在C#控制台应用程序中为文本输入保留一行,同时仍允许在其余行中更新其他信息。更具体地说,我想制作一个小型泥浆游戏,即使在用户忙于输入时游戏也会更新。输入不会阻止信息流,这一点很重要。

我想实现用户将输入写入屏幕中最后一个可见行的效果,而另一个文本像往常一样追加,但不会向下滚动我的输入行,也不会覆盖它。

如果我用形式来描述这个,我想象相当于将多行文本框作为信息的上半部分,在输入的底部有一个单行文本框。

3 个答案:

答案 0 :(得分:6)

您可以尝试的一个选项是直接操作控制台缓冲区以渲染您的游戏区域,并使用Console.SetCursorPosition将光标定位到输入行,您可以使用Console.ReadLine来获取用户输入。

由于缓冲区的直接操作不会影响光标位置,并且与控制台读/写功能无关,因此您可以让线程更新控制台缓冲区,该缓冲区覆盖前24行,25行正在等待输入。如果我有一些时间,我会尝试整理一个我的意思的样本,但在此期间你可以参考我提供的其他答案,直接写入Console缓冲区。

How can I write fast colored output to Console?

Deleting previously written lines in Console

当然你会想要写一些不错的包装函数来使这个易于使用,我总是考虑这样做,我只是没有做足够的工作与控制台,所以我实际上下来做一些事情。

更新:添加了一个在线程中更新控制台同时仍接受用户输入的小例子。只需输入'quit'即可停止运行。注意ConsoleBuffer类并不理想,我没有关闭控制台句柄,它只是演示的一段快速代码。

using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.Threading;

namespace ConsoleDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      Thread t = new Thread(new ThreadStart(UpdateConsole));
      t.IsBackground=true;
      t.Start();      

      string input;
      do
      {
        Console.SetCursorPosition(0, 23);
        Console.Write("Command: ");
        input = Console.ReadLine();
        ConsoleBuffer.ClearArea(0, 21, 80, 3);
        Console.SetCursorPosition(0, 22);
        Console.Write(input);
      } while (!string.Equals(input, "quit", StringComparison.OrdinalIgnoreCase));
    }

    static void UpdateConsole()
    {
      int i = 0;
      Random rnd = new Random();
      while (true)
      {
        string s = new string((char)(65 + (i % 26)),1);
        for (short x = 0; x < 80; ++x)
        {
          for (short y = 0; y < 20; ++y)
          {
            ConsoleBuffer.WriteAt(x, y, s);
            ConsoleBuffer.SetAttribute(x, y, (short)(rnd.Next(15)+1));
          }          
        }
        Thread.Sleep(500);
        i++;
      }
    }
  }

  public class ConsoleBuffer
  {
    private static SafeFileHandle _hBuffer = null;

    static ConsoleBuffer()
    {
      _hBuffer = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

      if (_hBuffer.IsInvalid)
      {
        throw new Exception("Failed to open console buffer");
      }      
    }

    public static void WriteAt(short x, short y, string value)
    {
      int n = 0;
      WriteConsoleOutputCharacter(_hBuffer, value, value.Length, new Coord(x, y), ref n);
    }

    public static void SetAttribute(short x, short y, short attr)
    {
      SetAttribute( x, y, new short[] { attr });
    }

    public static void SetAttribute(short x, short y, short[] attrs)
    {
      int n = 0;
      WriteConsoleOutputAttribute(_hBuffer, attrs, attrs.Length, new Coord(x, y), ref n);
    }

    public static void ClearArea(short left, short top, short width, short height, char ch = ' ')
    {
      ClearArea(left, top, width, height, new CharInfo() { Char = new CharUnion() { UnicodeChar = ch } });
    }

    public static void ClearArea(short left, short top, short width, short height)
    {
      ClearArea(left, top, width, height, new CharInfo() { Char = new CharUnion() { AsciiChar = 32 } });
    }

    private static void ClearArea(short left, short top, short width, short height, CharInfo charAttr)
    {
      CharInfo[] buf = new CharInfo[width * height];
      for (int i = 0; i < buf.Length; ++i)
      {
        buf[i] = charAttr;
      }

      SmallRect rect = new SmallRect() { Left = left, Top = top, Right = (short)(left + width), Bottom = (short)(top + height) };
      WriteConsoleOutput(_hBuffer, buf,
        new Coord() { X = width, Y = height },
        new Coord() { X = 0, Y = 0 },
        ref rect);      
    }

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern SafeFileHandle CreateFile(
      string fileName,
      [MarshalAs(UnmanagedType.U4)] uint fileAccess,
      [MarshalAs(UnmanagedType.U4)] uint fileShare,
      IntPtr securityAttributes,
      [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
      [MarshalAs(UnmanagedType.U4)] int flags,
      IntPtr template);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CloseHandle(IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool WriteConsoleOutput(
      SafeFileHandle hConsoleOutput,
      CharInfo[] lpBuffer,
      Coord dwBufferSize,
      Coord dwBufferCoord,
      ref SmallRect lpWriteRegion);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool WriteConsoleOutputCharacter(
      SafeFileHandle hConsoleOutput,
      string lpCharacter,
      int nLength,
      Coord dwWriteCoord,
      ref int lpumberOfCharsWritten);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool WriteConsoleOutputAttribute(
      SafeFileHandle hConsoleOutput,
      short[] lpAttributes,
      int nLength,
      Coord dwWriteCoord,
      ref int lpumberOfAttrsWritten);

    [StructLayout(LayoutKind.Sequential)]
    struct Coord
    {
      public short X;
      public short Y;

      public Coord(short X, short Y)
      {
        this.X = X;
        this.Y = Y;
      }
    };

    [StructLayout(LayoutKind.Explicit)]
    struct CharUnion
    {
      [FieldOffset(0)]
      public char UnicodeChar;
      [FieldOffset(0)]
      public byte AsciiChar;
    }

    [StructLayout(LayoutKind.Explicit)]
    struct CharInfo
    {
      [FieldOffset(0)]
      public CharUnion Char;
      [FieldOffset(2)]
      public short Attributes;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct SmallRect
    {
      public short Left;
      public short Top;
      public short Right;
      public short Bottom;
    }
  }
}

答案 1 :(得分:1)

dotNet控制台支持SetCursorPosition(),您还可以使用旧的DOS技巧来结束\r而不是\n\r的行。

但是多线程和追加听起来并不是一个好的组合。

答案 2 :(得分:1)

查看这些用于curses的.NET绑定

http://www.mono-project.com/Libraries#Curses

ncurses显然是一个UNIX发明,但据说API主要是跨平台的(我自己没有尝试过.NET绑定,但是通常使用ncurses有非常好的结果)。

这绝对包含您需要的商品和更多