我在前一篇随笔“浅谈 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 枚举,如上面右边所示。