在一篇在控制台窗口中监听前台窗口的博客中,我在控制台里以表格的形式输出了每一个前台窗口的信息。在控制台里编写一个字符表格其实并不难,毕竟 ASCII 中就已经提供了制表符。不过要在合适的位置输出合适的制表符,要写一些打杂式的代码了;另外,如果还要考虑表格列的宽度自适应,再考虑中英文在控制台中的对齐,还要考虑文字超出单元格时是裁剪/省略/换行。当把所有这些麻烦加到一起之后,写一个这样的辅助类来顶替那些麻烦事儿还是很有必要的。
效果预览
以下是我在前台窗口监视程序中的运行效果:
代码组织
我写了三个类来完成这样的事情:
ConsoleTableBuilder<T>
用于构建表格ConsoleTableColumnDefinition<T>
用于定义表格的列ConsoleStringExtensions
由于在控制台中做中英文对齐不能使用string
原有的与长度相关的方法,所以我们需要一个静态类来扩展string
对控制台的特殊处理
详细的代码,可以在我的 GitHub 仓库中找到:
其中,ConsoleStringExtensions
类的设计,我参考了 D 的个人博客,不过原文的一部分关键实现其实是不正确的,有一些本不必要的循环浪费性能,还有不能直观看出含义的缩写命名,所以这个类的实际代码是我完全重写之后,请 GPT-4 帮我润色,以及请 GitHub Copilot 帮我写完注释之后的版本。
开源
这个类库我已经开源到我的 GitHub 仓库中,并可直接以 NuGet 形式引用。
项目地址:
NuGet 包:
用法
如下,我们获取控制台的字符宽度,然后 -1
后作为表格的宽度,随后定义每一列,这就完成了表格的初始化:
1
2
3
4
5
6
7
8
9
10
11
var consoleWidth = Console.WindowWidth;
var table = new ConsoleTableBuilder<Win32Window>(consoleWidth - 1, new ConsoleTableColumnDefinition<Win32Window>[]
{
(8, "time", _ => $"{DateTime.Now:hh:mm:ss}"),
(8, "hwnd", w => $"{w.Handle:X8}"),
(0.5, "title", w => w.Title),
(0.25, "class name", w => w.ClassName),
(6, "pid", w => $"{w.ProcessId}"),
(0.25, "process name", w => $"{w.ProcessName}"),
});
Console.WriteLine(table.BuildHeaderRows());
- 需要
-1
是因为大多数情况下,输出的行都刚好能在控制台中排得下,但有小部分控制台会在输出完后额外换一行,于是会看到每输出一行都有一个空白行出现(虽然我现在仍不知道原因) - 定义列时,每个参数都是一个
ConsoleTableColumnDefinition<Win32Window>
的实例,为了方便,我允许隐式从元组转换- 整数列宽的元组,定义的是这一列可用的字符数
- 小数列的元组,是将整数列宽和表格划线用的字符除外后,剩余总列宽的百分比
- 元组的第二项是表头中的列名
- 元组的第三项是这一列的值的获取和格式化方法
接下来,在每一次有新数据需要输出时,都可以通过 BuildRow
方法,传入数据实例和字符串换行方法,得到一行的字符串。
1
2
3
4
5
6
7
8
9
10
// 当前前台窗口变化时,输出新的前台窗口信息。
void WinEventProc(HWINEVENTHOOK hWinEventHook, uint @event, HWND hwnd, int idObject, int idChild, uint idEventThread, uint dwmsEventTime)
{
var current = GetForegroundWindow();
var w = new Win32Window(current);
var rowText = table.BuildRow(w, StringDisplayMode.Wrap);
Console.WriteLine(rowText);
}
StringDisplayMode
是一个枚举,指定当字符串超过指定长度时,应如何处理此字符串:Truncate
截断字符串TruncateWithEllipsis
截断字符串,并在末尾添加省略号Wrap
将字符串换行
注意,当选择 Wrap
换行时,通过 BuildRow
方法得到的字符串其实是多行的(可以看本文开头的效果图了解)。
关于表格输出类的完整使用示例,可参考我监听前台窗口的博客,或直接查看我的 GitHub 仓库中的示例代码。
参考资料
本文会经常更新,请阅读原文: https://blog.walterlv.com/post/format-data-as-a-table-in-console ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected]) 。