控制台魔法,第一部分:生动的彩色消息






4.67/5 (7投票s)
本文介绍了一个为控制台程序显示添加颜色的类。
引言
本文是名为“控制台魔法”系列的第一篇,该系列涵盖了我在过去十年左右开发的一些技术,旨在简化和加速 C# 和 VB.NET 中实用程序的编写。
我在 NetWare 控制台前花费了相当长的时间,也编写了我的 QuickBASIC 实用程序。我知道可以将文本输出为除了控制台窗口默认文本和背景颜色之外的颜色。虽然这样做并不难,但我决定弄清楚如何使这项任务变得透明。图 1(下方)展示了 MessageInColor
类的实际应用。
图 1 是使用四组类似方法在控制台窗口中写入颜色的四个屏幕中的第一个。这个第一个屏幕演示了 Console.WriteLine
的彩色版本,我称之为 MessageInColor.RGBWriteLine
,它是静态的,但需要两个额外的参数来指定前景色和背景色。对于眼尖的读者,我在确定静态和实例方法的最终命名方案之前截取了这个屏幕截图。
MessageInColor
类旨在作为 System.Console.Write
和 System.Console.WriteLine
方法的直接替代品,并提供方便的功能,以简化在非默认屏幕颜色下显示消息。它旨在添加“局部颜色”,例如当您需要引起对重要事物的注意时,例如错误消息。这个类所做的一切都可以使用 System.Console
类的内置属性和方法来实现,但这会多做一点工作。
同样地,我希望我的程序能够连续显示进度消息,而无需滚动其上方的文本,例如大多数命令行归档程序在要求压缩大文件时报告进度的方式。我毫不惊讶地发现这需要更多的工作。尽管如此,结果是一个小型类,易于应用于新项目或改造现有项目。本系列的下一篇文章将解释 FixedConsoleWriter
类。
未来的文章将讨论用于控制台程序的其他辅助类,它们增加了定时暂停功能(可选择中断或不中断),替换了蹩脚的“按任意键继续”消息,异常日志记录以及其他功能,所有这些都由相同的一些类库公开。
背景
这里介绍的类利用了我所知道的每个支持彩色显示的控制台的四个长期存在的功能。这些功能在 1981 年 IBM PC-DOS 上市时就已经建立,并从第一个版本开始一直延续到 Windows,最后两个功能则继承自打字机。
- 每个字符都有一个颜色属性。严格来说,每个字符都有两个颜色属性,一个文本(前景色)和一个背景色。尽管现代显示器支持 24 位颜色,但本文仅限于
System.Console
类的ConsoleColor
枚举定义的原始 16 种颜色。 - 控制台是一个矩形数组,通常为 80 列 x 25 行或 132 列 x 43 行。像所有好的数组一样,坐标系从零开始。为了清晰地记录代码,我将
LEFT_EDGE
定义为一个值为零的整数常量。控制台的实际宽度是用户可配置的值,由Console.WindowWidth
属性跟踪。 - 一个未经修饰的回车符字符的行为与打字机的回车符完全相同。如果您仔细观察过打字机的行为,回车和换行是两个独立的操作,当您想将某一行或全部行加粗时,您可以利用它们。相同的行为也延续到行式打印机,并且使用相同的技巧使其打印粗体文本。
- 光标在每个字母写入后前进到下一个位置。因此,当您在控制台上写入“hello”时,光标停在位置 6。因此,当您在一行上写入第 80 个字符时,文本会换行,您会得到一个隐式换行,这样第 81 个字符就会进入*下一行*的第 1 列。
这些行为并非 Console.Write
和 Console.WriteLine
方法所独有;C 运行时库和 Windows 平台 SDK 中的相应例程也表现出相同的行为。
使用代码
随附的包包含两个演示程序,它们演示了这些文章中讨论的所有方法和类。
- Console_Color_Writer\TestStand\bin\Release\TestStand.exe 演示了静态方法
MessageInColor.RGBWriteLine()
和MessageInColor.RGBWrite()
的 18 个重载、实例方法MessageInColor.WriteLine()
和MessageInColor.Write()
、AppExceptionLogger
类的ErrorMessageColors
属性以及FixedConsoleWriter
类。 - DLLServices2\DLLServices2TestStand\bin\Release\DLLServices2TestStand.exe 是
MessageInColor
类的一个基本演示,以及ErrorMessagesInColor
类的一个彻底处理,该类为致命和非致命错误消息实现了默认颜色,这些颜色可以按应用程序进行覆盖。
MessageInColor 类公开了两组功能上相同的方法,这两组方法都严格遵循 System.Console
类的静态 Write
和 WriteLine
方法。
- MessageInColor 实例具有
MessageForegroundColor
和MessageBackgroundColor
属性,它们存储您在调用构造函数时指定的前景色(文本)和背景色。您可以通过定义一组适当命名的 MessageInColor 实例来实现配色方案。 - 我将实例方法命名为
Write
和WriteLine
,以明确它们的签名与 System.Console 中同名静态方法的签名类似。 - 相应的静态方法是 RGBWriteLine 和 RGBWrite,以提醒您在每次调用时都必须指定前景色和背景色。
为什么有两个相同的类?
从表面上看,MessageInColor 和 ErrorMessagesInColor 是相同的。然而,仔细检查后,您会发现后者使用 Console.Error
属性写入标准错误(STDERR
)流,而 MessageInColor 则通过 Console.Out
属性写入标准输出(STDOUT
)。将错误消息写入 STDERR 流使它们即使在 STDOUT 被重定向到文件时也能可见。
这种行为的一个重要结果是,错误消息不会在正常的重定向输出中被捕获。通常,这是可取的,因为重定向输出旨在捕获报告,而您希望错误消息可见。然而,请放心,因为 AppExceptionLogger 类可以配置为将错误记录到您选择的 Windows 事件日志中,这是其最初的动机,并且存在额外的、但文档不完善的功能,用于单独捕获标准错误流。
关于默认颜色?
我坚信,只要实际可行,程序就应该将可能永远改变的值作为数据存储,以便在运行时读取。为此,我为致命和可恢复(非致命)错误消息建立了默认颜色,这些颜色是从配置文件中读取的。由于我的目的是让这些默认值或多或少地具有全局性,我希望它们能随 DLL 一起提供,并自动提供给任何引用它的应用程序。
图 2 展示了 ErrorMessagesInColor
类的实际应用,使用了 WizardWrx.DLLServices2.dll.config
中指定的默认颜色。
托管彩色消息写入器的库 WizardWrx.DLLServices2.dll
拥有自己的配置文件 WizardWrx.DLLServices2.dll.config
,只要有一份副本随其一起,它就知道如何找到它。为此,其 CopyToOutputDirectory
属性在项目配置文件中设置为 Always
。
配置文件的有效部分非常简单明了。
<configuration>
<appSettings>
<add key="FatalExceptionTextColor" value="White"/>
<add key="FatalExceptionBackgroundColor" value="DarkRed"/>
<add key="RecoverablelExceptionTextColor" value="Black"/>
<add key="RecoverableExceptionBackgroundColor" value="Yellow"/>
</appSettings>
</configuration>
为了简单起见,颜色以字符串字面量的形式存储,它们对应于 ConsoleColors
枚举的字符串表示,键名对应于属性名。
这些颜色是我在研究了许多组合并征求了计算机用户关于易读性的意见后做出的选择。然而,它们并非神圣不可侵犯;请随意替换您自己的偏好。您可以通过设置 AppExceptionLogger
对象的 ErrorMessageColors
属性来覆盖默认颜色,如下所示。
s_theApp.BaseStateManager.AppExceptionLogger.ErrorMessageColors = new ErrorMessagesInColor (
ConsoleColor.DarkRed ,
ConsoleColor.White );
图 3 展示了当上面所示的设置生效时,报告异常时发生的情况。
图 3 展示了使用应用程序定义颜色的 ErrrorMessagesInColor
类。
关注点
默认的控制台颜色必须始终保存和恢复。为此,MessageInColor
构造函数将当前控制台颜色保存到一对私有的 ConsoleColor
变量 _clrOrigForeColor
和 _clrOrigForeColor
中。这些是引用值,除了通过一对公共属性进行读取访问之外,不直接用于其他目的。它们的存在是为了为您提供一种在程序退出之前恢复原始控制台颜色的方法。
实例写入器使用另一对 _clrSaveForeColor
和 _clrSaveBackColor
,在每次操作之前和之后保存和恢复当前颜色。因此,对 Console.WriteLine
或 Console.Write
的中间调用会以当前控制台颜色显示文本。此功能允许您自由混合和匹配此类的方法和属性以及 System.Console
的方法和属性。
在写入任何内容之前,每个实例方法都会调用 SetMessageColors
来保存当前控制台颜色,然后设置该实例定义的颜色。
private void SetMessageColors ( )
{
_clrSaveBackColor = Console.BackgroundColor;
_clrSaveForeColor = Console.ForegroundColor;
Console.BackgroundColor = _clrTextBackColor;
Console.ForegroundColor = _clrTextForeColor;
} // private void SetMessageColors
一个类似但更简单的伴随方法 RestoreMessageColors
,在写入器完成写入后恢复原始颜色。
private void RestoreMessageColors ( )
{
Console.BackgroundColor = _clrSaveBackColor;
Console.ForegroundColor = _clrSaveForeColor;
} // private void RestoreMessageColors
WriteLine
方法还需要执行最后一项任务,即调用带有空参数列表的 Console.WriteLine
来完成该行,如写入单个字符串的 WriteLine
重载所示。
public void WriteLine (
string value )
{
SetMessageColors ( );
Console.Write ( value );
RestoreMessageColors ( );
Console.WriteLine ( );
} // public void WriteLine (10 of 18)
相应的 Console.Write
方法将彩色文本输出到控制台,并在 RestoreMessageColors
之后立即调用 Console.WriteLine
来推进光标。这两个操作必须按此顺序发生,否则调用 Console.Write 或 Console.WriteLine 将至少将下一行文本渲染成错误的颜色。
为了支持后续方法调用追加文本,Write 方法不调用 Console.WriteLine。但是,由于默认颜色已恢复,如果下一个写入控制台的例程是 Console.Write 或 Console.WriteLine,则文本将以预期的控制台颜色呈现。
静态方法采用类似的技术来保存和恢复控制台颜色。
历史
本文发布日期为 2015 年 5 月 10 日,星期日。