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

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

如果我需要写到这个位置,它只会是:

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

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

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

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

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

样品用法:

 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 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); } 

一个简化的演示,适用于Windows 10,用于从屏幕上指定的(X, Y)位置读取单个字符。 使用.NET 4.7.2进行测试.¹

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

  static void Populate_Console() { Console.Clear(); Console.Write(@" ┌───────┐ 1│CDEF│ 2│GHIJ│ 3│KLMN│ 4│OPQR│ └───────┘ 2 4 6 8 ".TrimStart('\r', '\n')); } 

它应该如下所示:

在此处输入图像描述

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

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

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

有单独的AnsiUnicode版本,您需要根据在控制台窗口中处于活动状态的代码页调用正确的版本。 我在这里展示了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 + " "); } } 

你有它......

在此处输入图像描述


笔记:
1.代码可能使用7.2中的一些C#编译器function。 对于Visual Studion 2017,在“项目属性”高级构建选项中启用“最新”选项。

关于什么:

 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 buffer = new List(); 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 ]; } } 

编辑:

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

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

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

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

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

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

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