【问题标题】:How to reserve a row for input in multi-threaded Console?如何在多线程控制台中为输入保留一行?
【发布时间】:2011-08-11 22:44:03
【问题描述】:

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

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

如果我用表单来描述这一点,我想这相当于有一个多行文本框作为信息的上部,而底部有一个单行文本框用于输入。

【问题讨论】:

    标签: c# multithreading console-application


    【解决方案1】:

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

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

    How can I write fast colored output to Console?

    Deleting previously written lines in Console

    当然,您会想编写一些不错的包装函数来使其易于使用,我一直在考虑这样做,我只是没有在控制台上做足够的工作,所以我真的要下来做点什么。

    更新:添加了一个在线程中更新控制台同时仍接受用户输入的小示例。只需键入“退出”即可停止运行。请注意,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;
        }
      }
    }
    

    【讨论】:

      【解决方案2】:

      dotNet 控制台支持SetCursorPosition(),您还可以使用旧的 DOS 技巧,即以 \r 代替 \n\r 结束一行。

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

      【讨论】:

        【解决方案3】:

        看看这些 .NET 绑定的诅咒

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

        ncurses 显然是一项 UNIX 发明,但据说 API 大多是跨平台的(我自己没有尝试过 .NET 绑定,但总体上使用 ncurses 取得了非常好的结果)。

        这绝对会包含您需要的商品等等

        【讨论】:

          猜你喜欢
          • 2017-02-18
          • 1970-01-01
          • 2011-01-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-27
          • 2019-02-20
          • 2014-08-11
          相关资源
          最近更新 更多