从控制台C#上的位置读取

时间:2012-09-10 16:03:18

标签: c# console

我需要从控制台中的特定位置读取文本,例如5,5。

如果我需要写信到这个位置,那就是:

Console.SetCursorPosition(5, 5);
Console.Write("My text");

有什么方法可以用类似的方式阅读吗?

只是为了澄清: 我不想停止接受用户的输入,甚至有可能输入不是来自用户,而是以前打印出来的东西。我真的想要某种形式: Console.GetCharAtLocation(5,5)或类似的东西。

6 个答案:

答案 0 :(得分:10)

此功能不存在。从理论上讲,您可以覆盖控制台上的输入和输出流,以保留您自己可以读取的控制台缓冲区的副本,但这将是非常重要的(并且可能无法支持所有边缘情况,例如作为一个外部程序挂钩到您的控制台并读/写它。)

答案 1 :(得分:10)

这是一个C#代码实用程序,可以读取控制台缓冲区中当前的内容(不是窗口,缓冲区):

样本用法:

class Program
{
    static void Main(string[] args)
    {
        // read 10 lines from the top of the console buffer
        foreach (string line in ConsoleReader.ReadFromBuffer(0, 0, (short)Console.BufferWidth, 10))
        {
            Console.Write(line);
        }
    }
}

实用程序:

public class ConsoleReader
{
    public static IEnumerable<string> ReadFromBuffer(short x, short y, short width, short height)
    {
        IntPtr buffer = Marshal.AllocHGlobal(width * height * Marshal.SizeOf(typeof(CHAR_INFO)));
        if (buffer == null)
            throw new OutOfMemoryException();

        try
        {
            COORD coord = new COORD();
            SMALL_RECT rc = new SMALL_RECT();
            rc.Left = x;
            rc.Top = y;
            rc.Right = (short)(x + width - 1);
            rc.Bottom = (short)(y + height - 1);

            COORD size = new COORD();
            size.X = width;
            size.Y = height;

            const int STD_OUTPUT_HANDLE = -11;
            if (!ReadConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), buffer, size, coord, ref rc))
            {
                // 'Not enough storage is available to process this command' may be raised for buffer size > 64K (see ReadConsoleOutput doc.)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            IntPtr ptr = buffer;
            for (int h = 0; h < height; h++)
            {
                StringBuilder sb = new StringBuilder();
                for (int w = 0; w < width; w++)
                {
                    CHAR_INFO ci = (CHAR_INFO)Marshal.PtrToStructure(ptr, typeof(CHAR_INFO));
                    char[] chars = Console.OutputEncoding.GetChars(ci.charData);
                    sb.Append(chars[0]);
                    ptr += Marshal.SizeOf(typeof(CHAR_INFO));
                }
                yield return sb.ToString();
            }
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct CHAR_INFO
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public byte[] charData;
        public short attributes;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct COORD
    {
        public short X;
        public short Y;
    }

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

    [StructLayout(LayoutKind.Sequential)]
    private struct CONSOLE_SCREEN_BUFFER_INFO
    {
        public COORD dwSize;
        public COORD dwCursorPosition;
        public short wAttributes;
        public SMALL_RECT srWindow;
        public COORD dwMaximumWindowSize;
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool ReadConsoleOutput(IntPtr hConsoleOutput, IntPtr lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, ref SMALL_RECT lpReadRegion);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetStdHandle(int nStdHandle);
}

答案 2 :(得分:5)

一种简化的演示,可在 Windows 10 中使用,以从屏幕上指定的(X, Y)位置读取单个字符。已通过 .NET 4.7.2

进行了测试

首先,这是一行代码,其中使用演示网格填充了控制台。请注意,它应该呈现在屏幕的左上角,以便演示可以正常工作。

        static void Populate_Console()
        {
            Console.Clear();
            Console.Write(@"
 ┌───────┐
1│C D E F│
2│G H I J│
3│K L M N│
4│O P Q R│
 └───────┘
  2 4 6 8

    ".TrimStart('\r', '\n'));
        }

它应该像这样:

enter image description here

现在让我们回读一些字符。首先,您需要 stdout 的本机控制台手柄。这是从 Win32 获取它的P / Invoke方法:

[DllImport("kernel32", SetLastError = true)]
static extern IntPtr GetStdHandle(int num);

现在是最酷的部分;到目前为止,这似乎是此页面上唯一使用ReadConsoleOutputCharacter Win32函数的答案。尽管它不能让您获得字符颜色属性,但这种方法确实省去了处理复制矩形以及必须使用CreateConsoleScreenBuffer来分配屏幕缓冲区并在它们之间进行复制的所有麻烦。

有单独的 Ansi Unicode 版本,您需要根据控制台窗口中活动的代码页来调用正确的版本。我在这里显示了两个P / Invoke签名,但是为了简单起见,在示例中,我将继续使用 Ansi 版本:

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
    [return: MarshalAs(UnmanagedType.Bool)] //   ̲┌──────────────────^
    static extern bool ReadConsoleOutputCharacterA(
        IntPtr hStdout,   // result of 'GetStdHandle(-11)'
        out byte ch,      // A̲N̲S̲I̲ character result
        uint c_in,        // (set to '1')
        uint coord_XY,    // screen location to read, X:loword, Y:hiword
        out uint c_out);  // (unwanted, discard)

    [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)] //   ̲┌───────────────────^
    static extern bool ReadConsoleOutputCharacterW(
        IntPtr hStdout,   // result of 'GetStdHandle(-11)'
        out Char ch,      // U̲n̲i̲c̲o̲d̲e̲ character result
        uint c_in,        // (set to '1')
        uint coord_XY,    // screen location to read, X:loword, Y:hiword
        out uint c_out);  // (unwanted, discard)

您可能会注意到,出于示例代码的目的,我已将它们的编组处理降低到最低限度,该示例代码旨在一次仅获取一个字符。因此,由于托管指针声明“ c_in”和“ 1”,您可能会发现out byte ch必须始终为out Char ch

这实际上就是您所需要的;如果将自己限制为读取单个字符,则如上所述调用适当的P / Invoke函数通常是不言自明的。为了用一个简单的例子展示这一点,我将完成一个可爱的演示程序,该程序从Console沿我们在上面绘制的网格的对角线读取四个字符。

static void Windows_Console_Readback()
{
    var stdout = GetStdHandle(-11);

    for (uint coord, y = 1; y <= 4; y++)
    {
        coord = (5 - y) * 2;        // loword  <-- X coord to read
        coord |= y << 16;           // hiword  <-- Y coord to read

        if (!ReadConsoleOutputCharacterA(
                stdout,
                out byte chAnsi,    // result: single ANSI char
                1,                  // # of chars to read
                coord,              // (X,Y) screen location to read (see above)
                out _))             // result: actual # of chars (unwanted)
            throw new Win32Exception();

        Console.Write(" " + (Char)chAnsi + " ");
    }
}

就在那里...

enter image description here




注释:
1。该代码可能使用7.2中的某些C#编译器功能。对于Visual Studion 2017,请在“项目属性”高级生成选项中启用“最新”选项。

答案 3 :(得分:1)

忘记它了,麻烦太多了,您可以从缓冲区读取并获得当前控制台的所有输出,但是那太多了。

我的建议是创建一个ConsoleWriter委托,您选择如何选择它,可以是一个类,也可以只是一个静态方法,并且该编写器会将最后一行保留在属性中,以便每次您使用Console.WriteLine时都可以将其称为委派,以及您的实现,最后调用Console.WriteLine。

答案 4 :(得分:0)

怎么样:

class Program {
    static void Main( string[ ] args ) {
        CustomizedConsole.WriteLine( "Lorem Ipsum" ); //Lorem Ipsum
        Console.WriteLine( CustomizedConsole.ReadContent( 6, 5 ) ); //Ipsum
        Console.WriteLine( CustomizedConsole.GetCharAtLocation( 0, 0 ) ); //L
    }
}

static class CustomizedConsole {
    private static List<char> buffer = new List<char>();
    private static int lineCharCount = 0;

    public static void Write(string s){
        lineCharCount += s.Length;
        buffer.AddRange( s );
        Console.Write( s );
    }

    public static void WriteLine(string s ) {
        for ( int i = 0; i < Console.BufferWidth - lineCharCount - s.Length; i++ )
            s += " ";

        buffer.AddRange( s );
        Console.WriteLine( s );
        lineCharCount = 0;
    }

    public static string ReadContent( int index, int count ) {
        return new String(buffer.Skip( index ).Take( count ).ToArray());
    }

    public static char GetCharAtLocation( int x, int y ) {
        return buffer[ Console.BufferHeight * x + y ];
    }
}

编辑:

正如其他人所说,这只是一个微不足道的案例,还有很多其他需要改进的地方。但我这只写了一个起点。

答案 5 :(得分:0)

正如@Servy所说,没有任何内置功能(我知道或可以找到)可以做你想要的。但是,有一种解决方法(它有点像黑客,但它有效)。

您可以在内存或磁盘上创建自己的缓冲区。每当您输出到控制台时,也会输出到缓冲区。然后,您可以使用缓冲区以您无法使用控制台的方式进行读取。

有两种缓冲方式:磁盘或内存。您可以使用Console.BufferWidthConsole.BufferHeight属性来查找缓冲区大小。我发现使用字符串数组在内存中执行此操作更简单(每个字符串都是一行输出,如果我没记错的话,数组中有许多字符串等于BufferHeight)。一位同事最终在磁盘上做同样的事情。

您需要创建一个替换Console.WriteConsole.WriteLine的方法,以便您可以一次写入两个缓冲区。类似的东西:

public void MyWrite( string output ) {
    Console.Write( output );
    Array.Write( output );  // obvious pseudo-code
}

我发现围绕数组包装一个类并实现支持它的方法很有帮助......然后你可以实现你的GetCharAtLocation( int i, int j )方法,以及你需要的任何其他功能。