Как зарезервировать строку для ввода в многопоточной консоли? - PullRequest
9 голосов
/ 24 апреля 2011

Этот вопрос меня уже давно беспокоит, и я понимаю, что сложно описать то, что я ищу. Я хочу иметь возможность зарезервировать строку для ввода текста в консольном приложении C #, в то же время позволяя обновлять другую информацию в оставшихся строках. Более конкретно, я хотел бы сделать небольшую грязную игру, в которой игра обновляется, даже когда пользователь занят вводом. Важно, чтобы ввод не блокировал поток информации.

Я бы хотел добиться эффекта, когда пользователь записывает ввод в последнюю видимую строку на экране, в то время как другой текст добавляется как обычно, но не прокручивает мою строку ввода и не перезаписывает ее.

Если бы я описал это в терминах форм, я бы представил эквивалент многострочного текстового поля в качестве верхней части для информации с однострочным текстовым полем внизу для ввода.

Ответы [ 3 ]

6 голосов
/ 27 апреля 2011

Один из вариантов, который вы можете попробовать, - это непосредственно управлять буфером консоли для визуализации игровой области и использовать Console.SetCursorPosition, чтобы поместить курсор в строку ввода, где вы используете Console.ReadLine, например, для ввода пользовательского ввода.

Поскольку прямое манипулирование буфером не влияет на положение курсора и не зависит от функции чтения / записи консоли, у вас может быть поток, обновляющий буфер консоли, который охватывает первые 24 строки, а 25 строк ожидаютдля ввода.Если у меня будет время, я попытаюсь собрать пример того, что я имею в виду, но пока вы можете сослаться на другие ответы, которые я предоставил, для указания на запись непосредственно в буфер консоли.

Как я могу записать быстрый цветной вывод в Консоль?

Удаление ранее написанных строк в Консоли

Конечно, вы захотите написать несколько хороших функций-оберток, чтобы сделатьс этим легко работать, я всегда думаю об этом, я просто недостаточно работаю с консолью, так что я действительно спускаюсь и что-то делаю.

Обновление: ДобавленНебольшой пример обновления консоли в потоке, все еще принимая пользовательский ввод.Просто наберите '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 голос
/ 24 апреля 2011

Посмотрите на эти привязки .NET для проклятий

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

ncurses - это, очевидно, изобретение UNIX, но APIГоворят, что они в основном кроссплатформенные (я не пробовал привязки .NET самостоятельно, но имел хорошие результаты при работе с ncurses в целом).

Это будет абсолютно необходимый вам товар и многое другое

1 голос
/ 24 апреля 2011

Консоль dotNet поддерживает SetCursorPosition(), и вы также используете старый трюк DOS для завершения строки с \r вместо \n\r.

Но многопоточность и Append не звучат какхорошая комбинация.

...