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

Sta*_*ent 9 c# multithreading console-application

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

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

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

Chr*_*lor 6

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

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

如何将快速彩色输出写入控制台?

在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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


Hen*_*man 1

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

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