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

发布于 2024-11-03 06:46:07 字数 259 浏览 0 评论 0原文

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

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

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

This question has bugged me for a while now, and I realize it's hard to describe what I am looking for. I want to be able to reserve a row for text input in a C# Console Application, while still allowing other information to be updated in the remaining rows. More specifically, I'd like to make a small mud game where the game is updated even while the user is busy making input. It's important that the input doesn't block the information flow.

I'd like to achieve the effect of the user writing input to the last visible row in the screen, while the other text append as usual, but not scrolling down my line of input, nor overwrite it.

If I would describe this in terms of Forms, I'd imagine the equivalent of having a multi-line textbox as the upper portion for the information, with a single-line textbox at the bottom for the input.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

野却迷人 2024-11-10 06:46:08

查看这些curses 的.NET 绑定

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

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

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

Look at these .NET bindings for curses

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

ncurses is obviously a UNIX invention, but the API's are said to be mostly cross-platform (I haven't tried the .NET bindings myself, but have had very good results working with ncurses in general).

This will absolutely contain the goods you need and more

欲拥i 2024-11-10 06:46:07

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

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

如何将快速彩色输出写入控制台? < /一>

<一href="https://stackoverflow.com/questions/3173750/deleting-previously-writing-lines-in-console/3174089#3174089">删除控制台中以前写入的行

当然,你会想写一些很好的包装函数使这很容易使用,我总是考虑这样做,我只是没有在控制台上做足够的工作,所以我实际上坐下来做点什么。

更新:添加了一个在线程中更新控制台的小示例,同时仍然接受用户输入。只需输入“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;
    }
  }
}

One option that you could try, is to directly manipulate the console buffer to render your game area and use the Console.SetCursorPosition to position the cursor to the input line where you use Console.ReadLine for example to take the user input.

Since the direct manipulation of the buffer does not affect the cursor position and is independent of the Console Read/Write functionality you can have a thread updating the Console buffer which covers the first 24 lines and the 25 line is waiting for input. If I get some time I will try put together a sample of what I mean, but in the meantime you can reference the other answers I have provided for a pointer to writing directly to the Console buffer.

How can I write fast colored output to Console?

Deleting previously written lines in Console

Of course you will want to write some nice wrapper functions to make this easy to work with, I always think about doing this, I just don't do enough work with the console so that I actually get down and do something.

Update: Added a small example of updating the console in a thread while still accepting user input. Just type 'quit' to stop it running. Note the the ConsoleBuffer class is not ideal, I am not closing the console handle, it was just a quick piece of code for the demo.

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;
    }
  }
}
演多会厌 2024-11-10 06:46:07

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

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

The dotNet Console supports SetCursorPosition() and you also use the old DOS trick of ending a line with \r instead of \n\r.

But multi-threading and Append doesn't sound like a good combination.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文