银河

SKYIV STUDIO

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: 订阅 订阅 :: 管理 ::

我在前一篇随笔“浅谈 ConsoleColor”中把 ConsoleColor 枚举元素与同名的 KnownColor 枚举元素联系起来,发现 ConsoleColor.DarkYollew 在 KnownColor 找不到对应的元素。

Console 类中相关的源程序代码

实际上,ConsoleColor 枚举用于 Console 类的 ForegroundColorBackgroundColor 属性,下面就是 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 枚举的十六个元素的值是经过仔细挑选的,正好和这些标志位对应:

名称二进制IntensityRedGreenBlue
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 枚举,如上面右边所示。

参考资料

  1. MSDN: ConsoleColor 枚举
  2. MSDN: KnownColor 枚举
  3. MSDN: Console 类
  4. MSDN: Console.ForegroundColor 属性
  5. MSDN: Console.BackgroundColor 属性
  6. MSDN: Color 结构
  7. MSDN: Color.FromName 方法
  8. MSDN: FlagsAttribute 类
  9. MSDN: ObsoleteAttribute 类
  10. MSDN: CHAR_INFO Structure (Windows)
  11. Wikipedia: RGB color model
  12. Wikipedia: HSL and HSV
posted on 2011-01-18 16:59  银河  阅读(2411)  评论(4编辑  收藏  举报