打印到控制台时无法显示 Unicode 字符(使用双缓冲区)
Trouble getting Unicode character to display when printing to console (using a double buffer)
我在将 unicode 代码传递到我的双缓冲区时遇到了问题。现在我已经尝试单步执行代码,我会指出 unicode 字符在哪里仍然是正确的。 (看画法)
我认为问题出在最后的 "print" 方法中。
快速概述发生的情况:
- 创建缓冲区,允许绘制函数将字符插入缓冲区
- 打印功能会将缓冲区发送到控制台,以便显示
在我提供的示例中,我传入了 unicode 字符“\u2580”,但它打印了 ascii 字符“80”。
使用这个 link:
http://www.kreativekorp.com/charset/font.php?font=Consolas
我可以正确打印出 Basic Latin 和 Latin 1,但没有别的。
经过进一步研究,我认为问题不在于控制台代码页。除了测试切换代码页(没有效果),我仍然可以做 Console.Out.WriteLine("\u2580") 并获得正确的 unicode 字符。
提供一些附加信息...下面是调用的最终函数(作为 print 方法的结果)
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteConsoleOutput(
SafeFileHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
可在此处找到相关文档:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx
现在我可以保证当传入 lpbuffer 时,它有一个多维数组,其中的第一个值(在这种情况下)由属性和字符组成——此时它仍然正确,使用调试器检查过。
我已经提供了完整的代码以使其成为运行。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;
using System.Text;
namespace DoubleBuffer
{
///<summary>
///This class allows for a double buffer in Visual C# cmd promt.
///The buffer is persistent between frames.
///</summary>
class buffer
{
private int width;
private int height;
private int windowWidth;
private int windowHeight;
private SafeFileHandle h;
private CharInfo[] buf;
private SmallRect 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, CharSet = CharSet.Auto)]
static extern bool WriteConsoleOutput(
SafeFileHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
[StructLayout(LayoutKind.Sequential)]
public struct Coord
{
private short X;
private short Y;
public Coord(short X, short Y)
{
this.X = X;
this.Y = Y;
}
};
[StructLayout(LayoutKind.Explicit)]
public struct CharUnion
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte AsciiChar;
}
[StructLayout(LayoutKind.Explicit)]
public struct CharInfo
{
[FieldOffset(0)]
public CharUnion Char;
[FieldOffset(2)]
public short Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct SmallRect
{
private short Left;
private short Top;
private short Right;
private short Bottom;
public void setDrawCord(short l, short t)
{
Left = l;
Top = t;
}
public void setWindowSize(short r, short b)
{
Right = r;
Bottom = b;
}
}
/// <summary>
/// Consctructor class for the buffer. Pass in the width and height you want the buffer to be.
/// </summary>
/// <param name="Width"></param>
/// <param name="Height"></param>
public buffer(int Width, int Height, int wWidth, int wHeight) // Create and fill in a multideminsional list with blank spaces.
{
if (Width > wWidth || Height > wHeight)
{
throw new System.ArgumentException("The buffer width and height can not be greater than the window width and height.");
}
h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
width = Width;
height = Height;
windowWidth = wWidth;
windowHeight = wHeight;
buf = new CharInfo[width * height];
rect = new SmallRect();
rect.setDrawCord(0, 0);
rect.setWindowSize((short)windowWidth, (short)windowHeight);
Clear();
Console.OutputEncoding = System.Text.Encoding.Unicode;
}
/// <summary>
/// This method draws any text to the buffer with given color.
/// To chance the color, pass in a value above 0. (0 being black text, 15 being white text).
/// Put in the starting width and height you want the input string to be.
/// </summary>
/// <param name="str"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="attribute"></param>
public void Draw(String str, int x, int y, short attribute) //Draws the image to the buffer
{
if (x > windowWidth - 1 || y > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
if (str != null)
{
Char[] temp = str.ToCharArray(); //From testing I know the unicode character is still correct here
int tc = 0;
foreach (Char le in temp)
{
buf[(x + tc) + (y * width)].Char.UnicodeChar = le; //Height * width is to get to the correct spot (since this array is not two dimensions).
System.Console.Out.WriteLine(buf[(x + tc) + (y * width)].Char.UnicodeChar); //once again, a simple test to see if the unicode character is working. Enter debugging and you will see the value is correct.
if (attribute != 0)
buf[(x + tc) + (y * width)].Attributes = attribute;
tc++;
}
}
}
/// <summary>
/// Prints the buffer to the screen.
/// </summary>
public void Print() //Paint the image
{
if (!h.IsInvalid)
{
bool b = WriteConsoleOutput(h, buf, new Coord((short)width, (short)height), new Coord((short)0, (short)0), ref rect); //This is the point where I think it is messing up, but I am at a loss at to what is happening.
}
}
/// <summary>
/// Clears the buffer and resets all character values back to 32, and attribute values to 1.
/// </summary>
public void Clear()
{
for (int i = 0; i < buf.Length; i++)
{
buf[i].Attributes = 1;
buf[i].Char.UnicodeChar = '\u0020';
}
}
/// <summary>
/// Pass in a buffer to change the current buffer.
/// </summary>
/// <param name="b"></param>
public void setBuf(CharInfo[] b)
{
if (b == null)
{
throw new System.ArgumentNullException();
}
buf = b;
}
/// <summary>
/// Set the x and y cordnants where you wish to draw your buffered image.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public void setDrawCord(short x, short y)
{
rect.setDrawCord(x, y);
}
/// <summary>
/// Clear the designated row and make all attribues = 1.
/// </summary>
/// <param name="row"></param>
public void clearRow(int row)
{
for (int i = (row * width); i < ((row * width + width)); i++)
{
if (row > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
buf[i].Attributes = 0;
buf[i].Char.UnicodeChar = '\u0020';
}
}
/// <summary>
/// Clear the designated column and make all attribues = 1.
/// </summary>
/// <param name="col"></param>
public void clearColumn(int col)
{
if (col > windowWidth - 1)
{
throw new System.ArgumentOutOfRangeException();
}
for (int i = col; i < windowHeight * windowWidth; i += windowWidth)
{
buf[i].Attributes = 0;
buf[i].Char.UnicodeChar = '\u0020';
}
}
/// <summary>
/// This function return the character and attribute at given location.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns>
/// byte character
/// byte attribute
/// </returns>
public KeyValuePair<byte, byte> getCharAt(int x, int y)
{
if (x > windowWidth || y > windowHeight)
{
throw new System.ArgumentOutOfRangeException();
}
return new KeyValuePair<byte, byte>((byte)buf[((y * width + x))].Char.UnicodeChar, (byte)buf[((y * width + x))].Attributes);
}
}
}
运行 的示例是:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DoubleBuffer
{
class ExampleClass
{
static int width = 80;
static int height = 30;
public static void Main(string[] args)
{
Console.CursorVisible = false;
Console.Title = "Double buffer example";
System.Console.SetBufferSize(width, height);
System.Console.SetWindowSize(width, height);
Console.Clear();
buffer myBuf = new buffer(width, height, width, height);
backgroundbuf.Draw("\u2580", 0, 0, 2);
myBuf.Print();
Console.ReadLine();
}
}
}
如果我需要提供更多信息,请告诉我!
(注意:此代码直接来自我在 msdn 上的代码示例。直到现在才意识到这个错误,我想确保它已修复!!!)
以下链接帮助我解决了这个问题!
此外,Microsoft 开发中心也有一些很好的资源来解决此类问题。
好的!我找到了解决方案!这实际上很简单。首先,我需要摆脱 "CharUnion" 并将其放在 "CharInfo" 中(不知道为什么,但它有效 - 如果您知道如何修复 CharUnion,请 post!) .此外,我需要声明CharSet = CharSet.Auto,否则它默认为ascii。 (向 post 编辑了他们的答案但删除了它的人大声喊叫 - 你的想法是对的!)。 -- 新结构 CharInfo(取代结构 CharUnion):
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
public struct CharInfo
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte bAsciiChar;
[FieldOffset(2)]
public short Attributes;
}
接下来,我认为它不会改变任何东西,但我现在没有创建新文件,而是获得了当前输出缓冲区的句柄。这消除了对 SafeFileHandle CreateFile 的需要。
public const Int32 STD_OUTPUT_HANDLE = -11;
[DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern ConsoleHandle GetStdHandle(Int32 nStdHandle);
为了便于使用,对于任何未来的读者,这是我目前使用的代码。请注意,可能有一些随机片段对缓冲区工作没有用,我直接从我正在工作的项目中取出它。不过你应该明白这一点。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;
using System.Text;
namespace DoubleBuffer
{
/*
* Copyright [2012] [Jeff R Baker]
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* v 1.2.0
*/
///<summary>
///This class allows for a double buffer in Visual C# cmd promt.
///The buffer is persistent between frames.
///</summary>
class buffer
{
private int width;
private int height;
private int windowWidth;
private int windowHeight;
private ConsoleHandle h;
private CharInfo[] buf;
private SmallRect rect;
public const Int32 STD_OUTPUT_HANDLE = -11;
[DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern ConsoleHandle GetStdHandle(Int32 nStdHandle);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool WriteConsoleOutput(
ConsoleHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
[StructLayout(LayoutKind.Sequential)]
public struct Coord
{
private short X;
private short Y;
public Coord(short X, short Y)
{
this.X = X;
this.Y = Y;
}
};
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
public struct CharInfo
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte bAsciiChar;
[FieldOffset(2)]
public short Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct SmallRect
{
private short Left;
private short Top;
private short Right;
private short Bottom;
public void setDrawCord(short l, short t)
{
Left = l;
Top = t;
}
public short DrawCordX()
{
return Left;
}
public short DrawCordY()
{
return Top;
}
public void setWindowSize(short r, short b)
{
Right = r;
Bottom = b;
}
}
/// <summary>
/// Consctructor class for the buffer. Pass in the width and height you want the buffer to be.
/// </summary>
/// <param name="Width"></param>
/// <param name="Height"></param>
public buffer(int Width, int Height, int wWidth, int wHeight) // Create and fill in a multideminsional list with blank spaces.
{
if (Width > wWidth || Height > wHeight)
{
throw new System.ArgumentException("The buffer width and height can not be greater than the window width and height.");
}
h = GetStdHandle(STD_OUTPUT_HANDLE);
width = Width;
height = Height;
windowWidth = wWidth;
windowHeight = wHeight;
buf = new CharInfo[width * height];
rect = new SmallRect();
rect.setDrawCord(0, 0);
rect.setWindowSize((short)windowWidth, (short)windowHeight);
Console.OutputEncoding = System.Text.Encoding.Unicode;
Clear();
}
/// <summary>
/// This method draws any text to the buffer with given color.
/// To chance the color, pass in a value above 0. (0 being black text, 15 being white text).
/// Put in the starting width and height you want the input string to be.
/// </summary>
/// <param name="str"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="attribute"></param>
public void Draw(String str, int x, int y, short attribute) //Draws the image to the buffer
{
if (x > windowWidth - 1 || y > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
if (str != null)
{
Char[] temp = str.ToCharArray();
int tc = 0;
foreach (Char le in temp)
{
buf[(x + tc) + (y * width)].UnicodeChar = le; //Height * width is to get to the correct spot (since this array is not two dimensions).
if (attribute != 0)
buf[(x + tc) + (y * width)].Attributes = attribute;
tc++;
}
}
}
/// <summary>
/// Prints the buffer to the screen.
/// </summary>
public void Print() //Paint the image
{
if (!h.IsInvalid)
{
bool b = WriteConsoleOutput(h, buf, new Coord((short)width, (short)height), new Coord((short)0, (short)0), ref rect);
}
}
/// <summary>
/// Clears the buffer and resets all character values back to 32, and attribute values to 1.
/// </summary>
public void Clear()
{
for (int i = 0; i < buf.Length; i++)
{
buf[i].Attributes = 1;
buf[i].UnicodeChar = '\u0020';
}
}
/// <summary>
/// Pass in a buffer to change the current buffer.
/// </summary>
/// <param name="b"></param>
public void setBuf(CharInfo[] b)
{
if (b == null)
{
throw new System.ArgumentNullException();
}
buf = b;
}
/// <summary>
/// Set the x and y cordnants where you wish to draw your buffered image.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public void setDrawCord(short x, short y)
{
rect.setDrawCord(x, y);
}
/// <summary>
/// Clear the designated row and make all attribues = 1.
/// </summary>
/// <param name="row"></param>
public void clearRow(int row)
{
for (int i = (row * width); i < ((row * width + width)); i++)
{
if (row > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
buf[i].Attributes = 0;
buf[i].UnicodeChar = '\u0020';
}
}
/// <summary>
/// Clear the designated column and make all attribues = 1.
/// </summary>
/// <param name="col"></param>
public void clearColumn(int col)
{
if (col > windowWidth - 1)
{
throw new System.ArgumentOutOfRangeException();
}
for (int i = col; i < windowHeight * windowWidth; i += windowWidth)
{
buf[i].Attributes = 0;
buf[i].UnicodeChar = '\u0020';
}
}
/// <summary>
/// This function return the character and attribute at given location.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns>
/// byte character
/// byte attribute
/// </returns>
public KeyValuePair<byte, byte> getCharAt(int x, int y)
{
if (x > windowWidth || y > windowHeight)
{
throw new System.ArgumentOutOfRangeException();
}
return new KeyValuePair<byte, byte>((byte)buf[((y * width + x))].UnicodeChar, (byte)buf[((y * width + x))].Attributes);
}
public class ConsoleHandle : SafeHandleMinusOneIsInvalid
{
public ConsoleHandle() : base(false) { }
protected override bool ReleaseHandle()
{
return true; //releasing console handle is not our business
}
}
public int X
{
get { return width; }
}
public int Y
{
get { return height; }
}
public int dX
{
get { return rect.DrawCordX(); }
}
public int dY
{
get { return rect.DrawCordY(); }
}
}
}
对此的任何更改都可以在 MSDN 上的 "Console Double Buffer C#" 上更新。
我在将 unicode 代码传递到我的双缓冲区时遇到了问题。现在我已经尝试单步执行代码,我会指出 unicode 字符在哪里仍然是正确的。 (看画法)
我认为问题出在最后的 "print" 方法中。
快速概述发生的情况:
- 创建缓冲区,允许绘制函数将字符插入缓冲区
- 打印功能会将缓冲区发送到控制台,以便显示
在我提供的示例中,我传入了 unicode 字符“\u2580”,但它打印了 ascii 字符“80”。
使用这个 link:
http://www.kreativekorp.com/charset/font.php?font=Consolas
我可以正确打印出 Basic Latin 和 Latin 1,但没有别的。
经过进一步研究,我认为问题不在于控制台代码页。除了测试切换代码页(没有效果),我仍然可以做 Console.Out.WriteLine("\u2580") 并获得正确的 unicode 字符。
提供一些附加信息...下面是调用的最终函数(作为 print 方法的结果)
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteConsoleOutput(
SafeFileHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
可在此处找到相关文档:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx
现在我可以保证当传入 lpbuffer 时,它有一个多维数组,其中的第一个值(在这种情况下)由属性和字符组成——此时它仍然正确,使用调试器检查过。
我已经提供了完整的代码以使其成为运行。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;
using System.Text;
namespace DoubleBuffer
{
///<summary>
///This class allows for a double buffer in Visual C# cmd promt.
///The buffer is persistent between frames.
///</summary>
class buffer
{
private int width;
private int height;
private int windowWidth;
private int windowHeight;
private SafeFileHandle h;
private CharInfo[] buf;
private SmallRect 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, CharSet = CharSet.Auto)]
static extern bool WriteConsoleOutput(
SafeFileHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
[StructLayout(LayoutKind.Sequential)]
public struct Coord
{
private short X;
private short Y;
public Coord(short X, short Y)
{
this.X = X;
this.Y = Y;
}
};
[StructLayout(LayoutKind.Explicit)]
public struct CharUnion
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte AsciiChar;
}
[StructLayout(LayoutKind.Explicit)]
public struct CharInfo
{
[FieldOffset(0)]
public CharUnion Char;
[FieldOffset(2)]
public short Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct SmallRect
{
private short Left;
private short Top;
private short Right;
private short Bottom;
public void setDrawCord(short l, short t)
{
Left = l;
Top = t;
}
public void setWindowSize(short r, short b)
{
Right = r;
Bottom = b;
}
}
/// <summary>
/// Consctructor class for the buffer. Pass in the width and height you want the buffer to be.
/// </summary>
/// <param name="Width"></param>
/// <param name="Height"></param>
public buffer(int Width, int Height, int wWidth, int wHeight) // Create and fill in a multideminsional list with blank spaces.
{
if (Width > wWidth || Height > wHeight)
{
throw new System.ArgumentException("The buffer width and height can not be greater than the window width and height.");
}
h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
width = Width;
height = Height;
windowWidth = wWidth;
windowHeight = wHeight;
buf = new CharInfo[width * height];
rect = new SmallRect();
rect.setDrawCord(0, 0);
rect.setWindowSize((short)windowWidth, (short)windowHeight);
Clear();
Console.OutputEncoding = System.Text.Encoding.Unicode;
}
/// <summary>
/// This method draws any text to the buffer with given color.
/// To chance the color, pass in a value above 0. (0 being black text, 15 being white text).
/// Put in the starting width and height you want the input string to be.
/// </summary>
/// <param name="str"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="attribute"></param>
public void Draw(String str, int x, int y, short attribute) //Draws the image to the buffer
{
if (x > windowWidth - 1 || y > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
if (str != null)
{
Char[] temp = str.ToCharArray(); //From testing I know the unicode character is still correct here
int tc = 0;
foreach (Char le in temp)
{
buf[(x + tc) + (y * width)].Char.UnicodeChar = le; //Height * width is to get to the correct spot (since this array is not two dimensions).
System.Console.Out.WriteLine(buf[(x + tc) + (y * width)].Char.UnicodeChar); //once again, a simple test to see if the unicode character is working. Enter debugging and you will see the value is correct.
if (attribute != 0)
buf[(x + tc) + (y * width)].Attributes = attribute;
tc++;
}
}
}
/// <summary>
/// Prints the buffer to the screen.
/// </summary>
public void Print() //Paint the image
{
if (!h.IsInvalid)
{
bool b = WriteConsoleOutput(h, buf, new Coord((short)width, (short)height), new Coord((short)0, (short)0), ref rect); //This is the point where I think it is messing up, but I am at a loss at to what is happening.
}
}
/// <summary>
/// Clears the buffer and resets all character values back to 32, and attribute values to 1.
/// </summary>
public void Clear()
{
for (int i = 0; i < buf.Length; i++)
{
buf[i].Attributes = 1;
buf[i].Char.UnicodeChar = '\u0020';
}
}
/// <summary>
/// Pass in a buffer to change the current buffer.
/// </summary>
/// <param name="b"></param>
public void setBuf(CharInfo[] b)
{
if (b == null)
{
throw new System.ArgumentNullException();
}
buf = b;
}
/// <summary>
/// Set the x and y cordnants where you wish to draw your buffered image.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public void setDrawCord(short x, short y)
{
rect.setDrawCord(x, y);
}
/// <summary>
/// Clear the designated row and make all attribues = 1.
/// </summary>
/// <param name="row"></param>
public void clearRow(int row)
{
for (int i = (row * width); i < ((row * width + width)); i++)
{
if (row > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
buf[i].Attributes = 0;
buf[i].Char.UnicodeChar = '\u0020';
}
}
/// <summary>
/// Clear the designated column and make all attribues = 1.
/// </summary>
/// <param name="col"></param>
public void clearColumn(int col)
{
if (col > windowWidth - 1)
{
throw new System.ArgumentOutOfRangeException();
}
for (int i = col; i < windowHeight * windowWidth; i += windowWidth)
{
buf[i].Attributes = 0;
buf[i].Char.UnicodeChar = '\u0020';
}
}
/// <summary>
/// This function return the character and attribute at given location.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns>
/// byte character
/// byte attribute
/// </returns>
public KeyValuePair<byte, byte> getCharAt(int x, int y)
{
if (x > windowWidth || y > windowHeight)
{
throw new System.ArgumentOutOfRangeException();
}
return new KeyValuePair<byte, byte>((byte)buf[((y * width + x))].Char.UnicodeChar, (byte)buf[((y * width + x))].Attributes);
}
}
}
运行 的示例是:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DoubleBuffer
{
class ExampleClass
{
static int width = 80;
static int height = 30;
public static void Main(string[] args)
{
Console.CursorVisible = false;
Console.Title = "Double buffer example";
System.Console.SetBufferSize(width, height);
System.Console.SetWindowSize(width, height);
Console.Clear();
buffer myBuf = new buffer(width, height, width, height);
backgroundbuf.Draw("\u2580", 0, 0, 2);
myBuf.Print();
Console.ReadLine();
}
}
}
如果我需要提供更多信息,请告诉我! (注意:此代码直接来自我在 msdn 上的代码示例。直到现在才意识到这个错误,我想确保它已修复!!!)
以下链接帮助我解决了这个问题!
此外,Microsoft 开发中心也有一些很好的资源来解决此类问题。
好的!我找到了解决方案!这实际上很简单。首先,我需要摆脱 "CharUnion" 并将其放在 "CharInfo" 中(不知道为什么,但它有效 - 如果您知道如何修复 CharUnion,请 post!) .此外,我需要声明CharSet = CharSet.Auto,否则它默认为ascii。 (向 post 编辑了他们的答案但删除了它的人大声喊叫 - 你的想法是对的!)。 -- 新结构 CharInfo(取代结构 CharUnion):
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
public struct CharInfo
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte bAsciiChar;
[FieldOffset(2)]
public short Attributes;
}
接下来,我认为它不会改变任何东西,但我现在没有创建新文件,而是获得了当前输出缓冲区的句柄。这消除了对 SafeFileHandle CreateFile 的需要。
public const Int32 STD_OUTPUT_HANDLE = -11;
[DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern ConsoleHandle GetStdHandle(Int32 nStdHandle);
为了便于使用,对于任何未来的读者,这是我目前使用的代码。请注意,可能有一些随机片段对缓冲区工作没有用,我直接从我正在工作的项目中取出它。不过你应该明白这一点。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;
using System.Text;
namespace DoubleBuffer
{
/*
* Copyright [2012] [Jeff R Baker]
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* v 1.2.0
*/
///<summary>
///This class allows for a double buffer in Visual C# cmd promt.
///The buffer is persistent between frames.
///</summary>
class buffer
{
private int width;
private int height;
private int windowWidth;
private int windowHeight;
private ConsoleHandle h;
private CharInfo[] buf;
private SmallRect rect;
public const Int32 STD_OUTPUT_HANDLE = -11;
[DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern ConsoleHandle GetStdHandle(Int32 nStdHandle);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool WriteConsoleOutput(
ConsoleHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
[StructLayout(LayoutKind.Sequential)]
public struct Coord
{
private short X;
private short Y;
public Coord(short X, short Y)
{
this.X = X;
this.Y = Y;
}
};
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
public struct CharInfo
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte bAsciiChar;
[FieldOffset(2)]
public short Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct SmallRect
{
private short Left;
private short Top;
private short Right;
private short Bottom;
public void setDrawCord(short l, short t)
{
Left = l;
Top = t;
}
public short DrawCordX()
{
return Left;
}
public short DrawCordY()
{
return Top;
}
public void setWindowSize(short r, short b)
{
Right = r;
Bottom = b;
}
}
/// <summary>
/// Consctructor class for the buffer. Pass in the width and height you want the buffer to be.
/// </summary>
/// <param name="Width"></param>
/// <param name="Height"></param>
public buffer(int Width, int Height, int wWidth, int wHeight) // Create and fill in a multideminsional list with blank spaces.
{
if (Width > wWidth || Height > wHeight)
{
throw new System.ArgumentException("The buffer width and height can not be greater than the window width and height.");
}
h = GetStdHandle(STD_OUTPUT_HANDLE);
width = Width;
height = Height;
windowWidth = wWidth;
windowHeight = wHeight;
buf = new CharInfo[width * height];
rect = new SmallRect();
rect.setDrawCord(0, 0);
rect.setWindowSize((short)windowWidth, (short)windowHeight);
Console.OutputEncoding = System.Text.Encoding.Unicode;
Clear();
}
/// <summary>
/// This method draws any text to the buffer with given color.
/// To chance the color, pass in a value above 0. (0 being black text, 15 being white text).
/// Put in the starting width and height you want the input string to be.
/// </summary>
/// <param name="str"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="attribute"></param>
public void Draw(String str, int x, int y, short attribute) //Draws the image to the buffer
{
if (x > windowWidth - 1 || y > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
if (str != null)
{
Char[] temp = str.ToCharArray();
int tc = 0;
foreach (Char le in temp)
{
buf[(x + tc) + (y * width)].UnicodeChar = le; //Height * width is to get to the correct spot (since this array is not two dimensions).
if (attribute != 0)
buf[(x + tc) + (y * width)].Attributes = attribute;
tc++;
}
}
}
/// <summary>
/// Prints the buffer to the screen.
/// </summary>
public void Print() //Paint the image
{
if (!h.IsInvalid)
{
bool b = WriteConsoleOutput(h, buf, new Coord((short)width, (short)height), new Coord((short)0, (short)0), ref rect);
}
}
/// <summary>
/// Clears the buffer and resets all character values back to 32, and attribute values to 1.
/// </summary>
public void Clear()
{
for (int i = 0; i < buf.Length; i++)
{
buf[i].Attributes = 1;
buf[i].UnicodeChar = '\u0020';
}
}
/// <summary>
/// Pass in a buffer to change the current buffer.
/// </summary>
/// <param name="b"></param>
public void setBuf(CharInfo[] b)
{
if (b == null)
{
throw new System.ArgumentNullException();
}
buf = b;
}
/// <summary>
/// Set the x and y cordnants where you wish to draw your buffered image.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public void setDrawCord(short x, short y)
{
rect.setDrawCord(x, y);
}
/// <summary>
/// Clear the designated row and make all attribues = 1.
/// </summary>
/// <param name="row"></param>
public void clearRow(int row)
{
for (int i = (row * width); i < ((row * width + width)); i++)
{
if (row > windowHeight - 1)
{
throw new System.ArgumentOutOfRangeException();
}
buf[i].Attributes = 0;
buf[i].UnicodeChar = '\u0020';
}
}
/// <summary>
/// Clear the designated column and make all attribues = 1.
/// </summary>
/// <param name="col"></param>
public void clearColumn(int col)
{
if (col > windowWidth - 1)
{
throw new System.ArgumentOutOfRangeException();
}
for (int i = col; i < windowHeight * windowWidth; i += windowWidth)
{
buf[i].Attributes = 0;
buf[i].UnicodeChar = '\u0020';
}
}
/// <summary>
/// This function return the character and attribute at given location.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns>
/// byte character
/// byte attribute
/// </returns>
public KeyValuePair<byte, byte> getCharAt(int x, int y)
{
if (x > windowWidth || y > windowHeight)
{
throw new System.ArgumentOutOfRangeException();
}
return new KeyValuePair<byte, byte>((byte)buf[((y * width + x))].UnicodeChar, (byte)buf[((y * width + x))].Attributes);
}
public class ConsoleHandle : SafeHandleMinusOneIsInvalid
{
public ConsoleHandle() : base(false) { }
protected override bool ReleaseHandle()
{
return true; //releasing console handle is not our business
}
}
public int X
{
get { return width; }
}
public int Y
{
get { return height; }
}
public int dX
{
get { return rect.DrawCordX(); }
}
public int dY
{
get { return rect.DrawCordY(); }
}
}
}
对此的任何更改都可以在 MSDN 上的 "Console Double Buffer C#" 上更新。