我在前一篇随笔“浅谈 ConsoleColor”中把 ConsoleColor 枚举元素与同名的 KnownColor 枚举元素联系起来,发现 ConsoleColor.DarkYollew 在 KnownColor 找不到对应的元素。
Console 类中相关的源程序代码
实际上,ConsoleColor 枚举用于 Console 类的 ForegroundColor 和 BackgroundColor 属性,下面就是 Console 类中相关的源程序代码:
namespace System
{
public static class Console
{
public static ConsoleColor BackgroundColor
{
[SecuritySafeCritical]
get
{
bool flag;
Win32Native.CONSOLE_SCREEN_BUFFER_INFO bufferInfo = GetBufferInfo(false, out flag);
if (!flag)
{
return ConsoleColor.Black;
}
Win32Native.Color c = (Win32Native.Color) ((short) (bufferInfo.wAttributes & 240));
return ColorAttributeToConsoleColor(c);
}
[SecuritySafeCritical]
set
{
bool flag;
new UIPermission(UIPermissionWindow.SafeTopLevelWindows).Demand();
Win32Native.Color color = ConsoleColorToColorAttribute(value, true);
Win32Native.CONSOLE_SCREEN_BUFFER_INFO bufferInfo = GetBufferInfo(false, out flag);
if (flag)
{
short attributes = (short) (bufferInfo.wAttributes & -241);
attributes = (short) (((ushort) attributes) | ((ushort) color));
Win32Native.SetConsoleTextAttribute(ConsoleOutputHandle, attributes);
}
}
}
public static ConsoleColor ForegroundColor
{
[SecuritySafeCritical]
get
{
bool flag;
Win32Native.CONSOLE_SCREEN_BUFFER_INFO bufferInfo = GetBufferInfo(false, out flag);
if (!flag)
{
return ConsoleColor.Gray;
}
Win32Native.Color c = (Win32Native.Color) ((short) (bufferInfo.wAttributes & 15));
return ColorAttributeToConsoleColor(c);
}
[SecuritySafeCritical]
set
{
bool flag;
new UIPermission(UIPermissionWindow.SafeTopLevelWindows).Demand();
Win32Native.Color color = ConsoleColorToColorAttribute(value, false);
Win32Native.CONSOLE_SCREEN_BUFFER_INFO bufferInfo = GetBufferInfo(false, out flag);
if (flag)
{
short attributes = (short) (bufferInfo.wAttributes & -16);
attributes = (short) (((ushort) attributes) | ((ushort) color));
Win32Native.SetConsoleTextAttribute(ConsoleOutputHandle, attributes);
}
}
}
[SecurityCritical]
private static Win32Native.Color ConsoleColorToColorAttribute(ConsoleColor color, bool isBackground)
{
if ((color & ~ConsoleColor.White) != ConsoleColor.Black)
{
throw new ArgumentException(Environment.GetResourceString("Arg_InvalidConsoleColor"));
}
Win32Native.Color color2 = (Win32Native.Color) ((short) color);
if (isBackground)
{
color2 = (Win32Native.Color) ((short) (((short) color2) << 4));
}
return color2;
}
[SecurityCritical]
private static ConsoleColor ColorAttributeToConsoleColor(Win32Native.Color c)
{
if (((short) (c & (Win32Native.Color.BackgroundYellow | Win32Native.Color.BackgroundIntensity | Win32Native.Color.BackgroundBlue))) != 0)
{
c = (Win32Native.Color) ((short) (((short) c) >> 4));
}
return (ConsoleColor) c;
}
}
}
从上述源程序代码可以看出,ConsoleColor 枚举最终是和 Microsoft.Win32.Win32Native 内部类中的 Color 枚举相联系的。通过 Console 类的以下两个私有静态方法互相转换:
- ConsoleColorToColorAttribute,第 65 行到第 78 行。
- ColorAttributeToConsoleColor,第 80 行到第 88 行。
对 ForegroundColor 属性来说,基本上是直接赋值,对 BackgroundColor 属性来说,就是左移或者右移 4 bits 后再赋值。
Win32Native.Color 枚举
这个 Win32Native.Color 枚举如下所示:
namespace Microsoft.Win32
{
[SuppressUnmanagedCodeSecurity, SecurityCritical]
internal static class Win32Native
{
[Serializable, Flags]
internal enum Color : short
{
BackgroundBlue = 0x10,
BackgroundGreen = 0x20,
BackgroundIntensity = 0x80,
BackgroundMask = 240,
BackgroundRed = 0x40,
BackgroundYellow = 0x60,
Black = 0,
ColorMask = 0xff,
ForegroundBlue = 1,
ForegroundGreen = 2,
ForegroundIntensity = 8,
ForegroundMask = 15,
ForegroundRed = 4,
ForegroundYellow = 6
}
}
}
注意上面的 Win32Native.Color 枚举被指定有 FlagsAttribute 特性,它将作为位域(一组标志)进行处理。它可以同时指定前景色(ForegroundColor)和背景色(BackgroundColor)。主要的标志有:
- ForegroundBlue = 1 和 BackgroundBue = 0x10
- ForegroundGreen = 2 和 BackgroundGreen = 0x20
- ForegroundRed = 4 和 BackgroundRed = 0x40
- ForegroundIntensity = 8 和 BackgroundIntensity = 0x80
前面三组对应 Blue、Green、Red 三原色,最后一个 Intensity 指示更亮的颜色。注意,ForegroundYellow = 6 只不过是 ForegroundGreen = 2 和 ForegroundRed = 4 的组合而已。ForegroundMask = 15 和 ColorMask = 0xff 也不是用来指定颜色的标志,只不过是一些方便使用的 mask 而已。
CHAR_INFO 结构的 Attributes 字段
实际上,这个 Win32Native.Color 最终对应到 Windows SDK 中 CHAR_INFO 结构中的 Attributes 字段,下面是 Attributes 字段的相关部分:
名称 | 值 | 描述 |
---|---|---|
FOREGROUND_BLUE | 0X0001 | Text color contains blue. |
FOREGROUND_GREEN | 0X0002 | Text color contains green. |
FOREGROUND_RED | 0X0004 | Text color contains red. |
FOREGROUND_INTENSITY | 0X0008 | Text color is intensified. |
BACKGROUND_BLUE | 0X0010 | Background color contains blue. |
BACKGROUND_GREEN | 0X0020 | Background color contains green. |
BACKGROUND_RED | 0X0040 | Background color contains red. |
BACKGROUND_INTENSITY | 0X0080 | Background color is intensified. |
可以看出,这里是没有 YELLOW 的。而 ConsoleColor 枚举的十六个元素的值是经过仔细挑选的,正好和这些标志位对应:
名称 | 值 | 二进制 | Intensity | Red | Green | Blue |
---|---|---|---|---|---|---|
Black | 0 | 0000 | 0 | 0 | 0 | 0 |
DarkBlue | 1 | 0001 | 0 | 0 | 0 | 1 |
DarkCreen | 2 | 0010 | 0 | 0 | 1 | 0 |
DarkCyan | 3 | 0011 | 0 | 0 | 1 | 1 |
DarkRed | 4 | 0100 | 0 | 1 | 0 | 0 |
DarkMagenta | 5 | 0101 | 0 | 1 | 0 | 1 |
DarkYellow | 6 | 0110 | 0 | 1 | 1 | 0 |
Gray | 7 | 0111 | 0 | 1 | 1 | 1 |
DarkGray | 8 | 1000 | 1 | 0 | 0 | 0 |
Blue | 9 | 1001 | 1 | 0 | 0 | 1 |
Green | 10 | 1010 | 1 | 0 | 1 | 0 |
Cyan | 11 | 1011 | 1 | 0 | 1 | 1 |
Red | 12 | 1100 | 1 | 1 | 0 | 0 |
Magenta | 13 | 1101 | 1 | 1 | 0 | 1 |
Yellow | 14 | 1110 | 1 | 1 | 1 | 0 |
White | 15 | 1111 | 1 | 1 | 1 | 1 |
上表中 ConsoleColor 枚举的这十六个元素的值正好用于设定 Win32Native.Color 枚举的相关标志位,最终对应到 Windows SDK 中 CHAR_INFO 结构中的 Attributes 字段的相关标志位,指示在 Windows 操作系统中应该显示的颜色。
KnownColor 枚举和 Color 结构
对应到 .NET Framework Base Class Library 中的 KnownColor 枚举和 Color 结构,就如下图所示:
这是通过修改上一篇随笔中的 ConsoleColorTester.cs 程序后得到的运行结果,修改后的 ConsoleColorTester.cs 程序如下所示:
using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
namespace Skyiv.Tester
{
sealed class ConsoleColorTester : Form
{
DataGridView dgv;
ConsoleColorTester()
{
Text = "ConsoleColor - " + Environment.OSVersion;
Size = new Size(600, 380);
dgv = new DataGridView();
dgv.Dock = DockStyle.Fill;
Controls.Add(dgv);
}
protected override void OnLoad(EventArgs e)
{
dgv.DataSource = GetConsoleColors();
dgv.RowHeadersVisible = false;
dgv.AllowUserToAddRows = false;
dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
dgv.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
dgv.ReadOnly = true;
foreach (DataGridViewColumn column in dgv.Columns)
column.SortMode = DataGridViewColumnSortMode.NotSortable;
foreach (DataGridViewRow row in dgv.Rows)
row.Cells[10].Style.BackColor = (Color)row.Cells[2].Value;
base.OnLoad(e);
}
DataTable GetConsoleColors()
{
var dt = GetDataTable();
foreach (ConsoleColor consoleColor in Enum.GetValues(typeof(ConsoleColor)))
{
var color = GetKnownColor(consoleColor);
var dr = dt.NewRow();
dr[0] = consoleColor;
dr[1] = consoleColor;
dr[2] = color;
dr[3] = color.A;
dr[4] = color.R;
dr[5] = color.G;
dr[6] = color.B;
dr[7] = color.GetHue();
dr[8] = color.GetSaturation();
dr[9] = color.GetBrightness();
dt.Rows.Add(dr);
}
return dt;
}
DataTable GetDataTable()
{
var dt = new DataTable();
dt.Columns.Add("值", typeof(int));
dt.Columns.Add("名称", typeof(string));
dt.Columns.Add("Color", typeof(Color));
dt.Columns.Add("Alpha", typeof(byte));
dt.Columns.Add("Red", typeof(byte));
dt.Columns.Add("Green", typeof(byte));
dt.Columns.Add("Blue", typeof(byte));
dt.Columns.Add("色调", typeof(float));
dt.Columns.Add("饱和度", typeof(float));
dt.Columns.Add("亮度", typeof(float));
dt.Columns.Add(" 颜色 ", typeof(string));
return dt;
}
Color GetKnownColor(ConsoleColor color)
{
if (color == ConsoleColor.DarkBlue) return Color.Navy;
if (color == ConsoleColor.DarkGreen) return Color.Green;
if (color == ConsoleColor.DarkCyan) return Color.Teal;
if (color == ConsoleColor.DarkRed) return Color.Maroon;
if (color == ConsoleColor.DarkMagenta) return Color.Purple;
if (color == ConsoleColor.DarkYellow) return Color.Olive;
if (color == ConsoleColor.Green) return Color.Lime;
return Color.FromName(color.ToString());
}
static void Main()
{
Application.Run(new ConsoleColorTester());
}
}
}
在上述程序中,主要是增加了第 75 行到第 85 行的 GetKnownColor 方法,将 System.ConsoleColor 枚举的值按照前述方案(即根据 Red、Green、Blue 的值)转换为 System.Drawing.KnownColor 枚举相应的值。这十六个值中,有七个需要手工指定转换关系,其余九个枚举元素的名称一致,可以直接使用 System.Drawing.Color 类的 FromName 静态方法转换。第 82 行将 ConsoleColor.DarkYellow 转换为 Color.Olive,正好和我在上一篇随笔中根据颜色的相近程度所猜想的一致。
给 Microsoft 的建议
虽然 System.ConsleColor 枚举只用在 Console 类的 ForegroundColor 和 BackgroundColor 属性中,最终是和 Win32Native.Color 相联系的。但是,Microsoft 在设计 ConsoleColor 枚举时,要是让其元素的名称和 KnownColor 枚举中相应元素的名称一致的话,就显得更协调了。
[Serializable, Obsolete] public enum ConsoleColor { Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White } | [Serializable] public enum Console2Color { Black, Navy, Green, Teal, Maroon, Purple, Olive, Gray, DarkGray, Blue, Lime, Cyan, Red, Magenta, Yellow, White } |
上面左边是现有的方案。当然,为了向后兼容,是不可能修改 ConsoleColor 了。但是,可以建议 Microsoft 给 ConsoleColor 枚举加上 ObsoleteAttribute 特性。而新增加一个 Console2Color 枚举,如上面右边所示。