轻松玩转windows控制台(八)阴影效果的彩色文字和字体大小设置
zhezhongyun 2025-04-29 06:46 2 浏览
写在前面
这两天在网上看到有视频回顾国内计算机发展的历史,其中UCDOS6.0、wps1.0等经典软件界面,一下子让我回到了学生时代。
我是从96年接触电脑,97年学习计算机,98年进入计算机专业,那时候学的数据库还是foxbase,后来我严重怀疑之所以学Foxbase,是因为教材是我们老师编写的缘故......那时候学的最好的就是汇编语言和c语言了。最后悔没认真学的就是数据结构和freeBSD,当时教FreeBSD的老师,现在想来当时真的算是个大神了,只可惜年轻不懂事,没有认真听课......
正好这段时间在写《轻松玩转windows控制台》系列教程,今天就来找一找DOS界面下的“图形界面”的感觉吧。
先贴上来一张程序运行的效果图:
在命令行时代,为了使界面有立体感,就是通过图形的重叠,以及颜色搭配,制造出立体感的。我的美术功底非常的差,差到中学时代参加美术考试,苹果画成了“方形”,不是夸张。所以,这个效果就将就看吧,本来还想再模拟一些效果,想想还是作罢。
这个效果图的原理分成几部分实现,首先画一个灰色的矩形,然后再错位画一个红色的矩形,然后再输出一段文字,其中这段文字进行字体大小的调整。
我们先对这个程序的代码功能分段讲解,最后再发布完整的程序源码。
准备工作
准备工作不是必须要做的,只是为了让显示效果更好。比如,我们可以将屏幕缓冲区的行数和列数设置好,以获得一个合适的控制台窗体的显示尺寸。
当然,之前文章中我们已经说过,屏幕缓冲区尺寸不等同于窗口显示尺寸,所以我们需要再将窗口尺寸设置为和屏幕缓冲区相同,这样就不会出现滚动条。
首先,我们需要先获取当前控制台程序的句柄,代码如下:
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
设置屏幕缓冲区的代码如下:
COORD scr_size = {80,30};
SetConsoleScreenBufferSize(hConsole,scr_size);
设置控制台窗口尺寸的代码如下:
SMALL_RECT wnd_size = {0,0,79,29};
SetConsoleWindowInfo(hConsole,TRUE,&wnd_size);
注意,屏幕缓冲区的COORD结构中的坐标x和y分别表示行数和列数。窗口尺寸中的SMALL_RECT结构表示窗口相对于屏幕缓冲区的位置坐标,含义不同。
矩形背景
矩形背景实际上是由
FillConsoleOutputAttribute函数绘制的。函数原型如下:
BOOL FillConsoleOutputAttribute(
HANDLE hConsoleOutput,
WORD wAttribute,
DWORD nLength,
COORD dwWriteCoord,
LPDWORD lpNumberOfAttrsWritten
);
第一个参数传入窗口句柄,第二个参数是字符的前景色和背景色,第三个参数要设置字符单元的个数,也就是设置连续的字符个数(重要!超过一行,自动换行到下一行),第四个参数是COORD 结构,传入了一个坐标,作为指定的起始位置,最后一个参数是输出型参数,如果打算向外传输数据,理论上可以设置为NULL(空指针),但是实际开发中不建议这样做,赋值一个DWORD类型的指针即可,不需要赋初值。
我们搞清楚了
FillConsoleOutputAttribute函数的用法,现在看看在程序中如何使用的。
继续下一段代码:
//区域的背景色和文字颜色
WORD bgColor_shadow,bgColor;
//1.阴影区域用灰色填充
bgColor_shadow = BACKGROUND_INTENSITY;
//2.文字的背景色用红色填充,文字用黄色填充
bgColor = BACKGROUND_RED |FOREGROUND_RED | FOREGROUND_GREEN| FOREGROUND_INTENSITY;
这段代码定义了两个背景区域的颜色。bgColor_shadow 定义的是灰色背景。bgColor 定义的红色背景,准备显示的文字用黄色,并且增强了明亮度。
FOREGROUND_INTENSITY和BACKGROUND_INTENSITY,一个是前景色的INTENSITY,一个是背景色的INTENSITY,单独使用时,表示灰色,如何和同类型的颜色混用时,表示增加颜色,使这个颜色更明亮。
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(hConsole, &csbi);
这两行代码的主要作用就是获取屏幕缓冲区中当前字符的属性,即csbi.wAttributes成员的值。后面字符属性被更改后,当想恢复到现在的字符属性时,可以随时通过csbi.wAttributes来恢复默认属性。
SMALL_RECT display_rect,shadow_rect;
display_rect.Left = 25;
display_rect.Top = 7;
display_rect.Right = 55;
display_rect.Bottom = 22;
shadow_rect.Left = display_rect.Left + 1;
shadow_rect.Top = display_rect.Top + 1;
shadow_rect.Right = display_rect.Right + 1;
shadow_rect.Bottom = display_rect.Bottom + 1;
这段代码中,display_rect 表示文字的背景区域面积,shadow_rect 表示阴影的区域面积,所谓阴影,就是被display_rect遮挡的区域面积。
为了实现背遮挡,只要将第二个区域和第一个区域错位就可以了,效果逼真度要看美术的,错多少位置,阴影部分的颜色如何调色等。然后再用代码实现。
DWORD col_num = display_rect.Right - display_rect.Left + 1;
SHORT line_num = display_rect.Bottom - display_rect.Top + 1;
DWORD dword1;
for(SHORT i = shadow_rect.Top; i <= line_num;i++){
FillConsoleOutputAttribute(hConsole, bgColor_shadow, col_num, {shadow_rect.Left, i}, &dword1);
}
DWORD dword2;
for(SHORT i = display_rect.Top; i < line_num;i++){
FillConsoleOutputAttribute(hConsole, bgColor, col_num, {display_rect.Left, i}, &dword2);
}
col_num 表示区域面积每行填充的字符单元(列数),line_num 表示要填充多少行。注意,虽然我们定义了SMALL_RECT结构,表示矩形面积,但实际上,控制台不支持一次性填充屏幕缓冲区中的某个局部矩形面积,只能逐行的填充。所以要用for循环实现。
另外,
FillConsoleOutputAttribute的最后一个参数,时输出型参数,用来记录由多少个字符单元受到函数操作的影响,在本程序中我们并未用到,理论上可以设为NULL,但根据实际经验,不要设为NULL。
这段代码实现的效果如下:
字体大小
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof(cfi);
cfi.nFont = 0;
cfi.dwFontSize.X = 10;
cfi.dwFontSize.Y = 20;
cfi.FontFamily = FF_DONTCARE;
cfi.FontWeight = FW_BOLD;
SetCurrentConsoleFontEx(hConsole, FALSE, &cfi);
这段代码的作用时对后面要输出的文字进行字体大小的设置。使用了一个CONSOLE_FONT_INFOEX结构体,注意,不要和CONSOLE_FONT_INFO结构体混淆了。
CONSOLE_FONT_INFO结构体的定义如下:
typedef struct _CONSOLE_FONT_INFO {
DWORD nFont;
COORD dwFontSize;
} CONSOLE_FONT_INFO;
CONSOLE_FONT_INFOEX结构体的定义如下:
typedef struct _CONSOLE_FONT_INFOEX {
ULONG cbSize;
DWORD nFont;
COORD dwFontSize;
UINT FontFamily;
UINT FontWeight;
WCHAR FaceName[LF_FACESIZE];
} CONSOLE_FONT_INFOEX;
这2个结构体其实能能通过dwFontSize成员来获得当前字体的大小,但要设置字体大小,需要调用SetCurrentConsoleFontEx函数来实现,而这个函数使用的结构体就是 CONSOLE_FONT_INFOEX类型,而不是CONSOLE_FONT_INFO类型。我们来看下这个函数的原型:
BOOL SetCurrentConsoleFontEx(
HANDLE hConsoleOutput,
BOOL bMaximumWindow,
PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx
);
第一个参数没什么可讲的,就是控制台窗体句柄,第二个参数如果为 TRUE,则设置当前窗口最大化后的最大窗口情况下对应的字体信息。 如果为 FALSE,则设置当前窗口时的字体信息。最后一个参数就是CONSOLE_FONT_INFOEX的指针,这是一个典型的输入型参数。(输入型参数和输出型参数可以看我的。。。文章链接)
如何使用CONSOLE_FONT_INFOEX类型变量?
第一个参数必须要这样使用: cfi.cbSize = sizeof(cfi);第二个参数nFont一般默认为0,第三个参数dwFontSize是一个COORD结构体,注意,这个结构体不是表示坐标的,而是表示后面输出的文字的字符宽度和高度。 X 成员包含宽度,而 Y 成员包含高度。
第四个参数FontFamily表示字体间距和系列。 有关此成员可能值的信息,可以看 TEXTMETRIC 结构的 tmPitchAndFamily 成员的说明。参数FontWeight表示字体粗细。 粗细范围为 100 到 1000,按 100 的倍数表示。 例如,正常粗细为 400,而 700 为粗体。FaceName表示要使用的字体名称(如 Courier 或 Arial)。为了降低学习的复杂度,此处暂时不展开,后期的文章中会详细的讲解这几个参数的用法,不影响的本程序的功能实现。
文本输出
输出文本内容,可以有很多钟方法,比如
FillConsoleOutputCharacter 函数和
WriteConsoleOutputCharacter函数,前者是允许批量输出字符到屏幕缓冲区,后者是单个字符串的输出。本例程序钟用的就是后者。
WriteConsoleOutputCharacter函数原型如下:
BOOL WriteConsoleOutputCharacter(
HANDLE hConsoleOutput,
LPCTSTR lpCharacter,
DWORD nLength,
COORD dwWriteCoord,
LPDWORD lpNumberOfCharsWritten
);
第一个参数不用多言,一定是窗口句柄,第二个参数是要写入屏幕缓冲区的字符或字符串。第三个参数是要写入的字符个数(不是字节个数,是字符个数)。第四个参数是
COORD 结构,表示的是坐标,指定第一个字符在屏幕缓冲区的起始坐标。
最后一个参数和刚才的
FillConsoleOutputAttribute函数的最后一个参数的用法类似,第二个参数的字符串当函数执行完毕后,实际输出到屏幕缓冲区的字符个数将被存放到这个参数所指向的区域。这个实际上就是指向DWORD的指针。按照经验,即使在程序中没起作用,也不建议设为NULL。
本例中程序代码如下:
const char* str = "致 敬 经 典";
//const char *可以替换成LPCTSTR
//LPCTSTR str = "...";
//LPCTSTR是windows编程风格
//const char *是c语言风格
DWORD written;
if (!WriteConsoleOutputCharacter(hConsole, str, strlen(str),{35,11 },&written))
{
printf("%d\n", GetLastError());
return 1;
}
输出要显示的文字,所在的字符单元自动使用刚才设置好的字符属性。直到调用下面这行代码,才恢复之前默认的字符属性:
SetConsoleTextAttribute(hConsole, csbi.wAttributes);
下面这行代码,是为了让对屏幕缓冲区所做的修改能够立即生效:
fflush(stdout);
这是c语言的用法,C++可以这样用:std::cout << std::flush;在刚才的代码中,const char*是c语言的编程风格,可以替换成LPCTSTR类型,这是windows编程风格。关于windows编程风格的自定义数据类型,在我的这篇文章里由详细讲解轻松玩转windows控制台(一):窗口标题
下面是完整的程序源码,可以直接编译运行。所用的环境为CLion2023,默认的clang编译器,64位win10最新版本。实际上,在devC++和vs2022里也可以编译通过。如果有任何问题,欢迎随时交流。
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main() {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
// 准备工作:
//1.设置屏幕缓冲区尺寸
COORD scr_size = {80,30};
SetConsoleScreenBufferSize(hConsole,scr_size);
//2.设置窗体尺寸
SMALL_RECT wnd_size = {0,0,79,29};
SetConsoleWindowInfo(hConsole,TRUE,&wnd_size);
//区域的背景色和文字颜色
WORD bgColor_shadow,bgColor;
//1.阴影区域用灰色填充
bgColor_shadow = BACKGROUND_INTENSITY;
//2.文字的背景色用红色填充,文字用黄色填充
bgColor = BACKGROUND_RED |FOREGROUND_RED | FOREGROUND_GREEN| FOREGROUND_INTENSITY;
//为了获取屏幕缓冲区的默认字符属性,在需要时候用以恢复默认属性。
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(hConsole, &csbi);
//display_rect 表示文字的背景区域面积
//shadow_rect 表示阴影的区域面积
SMALL_RECT display_rect,shadow_rect;
display_rect.Left = 25;
display_rect.Top = 7;
display_rect.Right = 55;
display_rect.Bottom = 22;
shadow_rect.Left = display_rect.Left + 1;
shadow_rect.Top = display_rect.Top + 1;
shadow_rect.Right = display_rect.Right + 1;
shadow_rect.Bottom = display_rect.Bottom + 1;
//col_num 表示区域面积每行填充的字符单元(列数)
//line_num 表示要填充多少行
//注意,控制台不支持一次性填充屏幕缓冲区中的某个局部矩形面积
//只能连续填充
DWORD col_num = display_rect.Right - display_rect.Left + 1;
SHORT line_num = display_rect.Bottom - display_rect.Top + 1;
//for循环实现区域面积填充
//1.先填充底部的阴影面积
//dword1在本程序中未用到,理论上可以设为NULL,但根据实际经验,不要设为NULL
DWORD dword1;
for(SHORT i = shadow_rect.Top; i <= line_num;i++){
FillConsoleOutputAttribute(hConsole, bgColor_shadow, col_num, {shadow_rect.Left, i}, &dword1);
}
//2.再填充文本的背景区域
DWORD dword2;
for(SHORT i = display_rect.Top; i < line_num;i++){
FillConsoleOutputAttribute(hConsole, bgColor, col_num, {display_rect.Left, i}, &dword2);
}
//对要输出的文本内容,提前设置好字体大小
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof(cfi);
cfi.nFont = 0;
cfi.dwFontSize.X = 10;
cfi.dwFontSize.Y = 20;
cfi.FontFamily = FF_DONTCARE;
cfi.FontWeight = FW_BOLD;
//wcscpy_s(cfi.FaceName, L"Courier New");
//wcscpy_s(cfi.FaceName, L"");
SetCurrentConsoleFontEx(hConsole, FALSE, &cfi);
//输出要显示的文本内容
//调整好起始显示位置,所在的字符单元自动使用刚才设置好的字符属性
//const char* str = "致 敬 经 典";
LPCTSTR str = "致 敬 经 典";
DWORD written;
if (!WriteConsoleOutputCharacter(hConsole, str, strlen(str),{35,11 },&written))
{
printf("%d\n", GetLastError());
return 1;
}
//恢复默认的字符属性
SetConsoleTextAttribute(hConsole, csbi.wAttributes);
//强制刷新屏幕缓冲区,让所做的更改立刻生效
fflush(stdout);
getchar();
return 0;
}
相关推荐
- JPA实体类注解,看这篇就全会了
-
基本注解@Entity标注于实体类声明语句之前,指出该Java类为实体类,将映射到指定的数据库表。name(可选):实体名称。缺省为实体类的非限定名称。该名称用于引用查询中的实体。不与@Tab...
- Dify教程02 - Dify+Deepseek零代码赋能,普通人也能开发AI应用
-
开始今天的教程之前,先解决昨天遇到的一个问题,docker安装Dify的时候有个报错,进入Dify面板的时候会出现“InternalServerError”的提示,log日志报错:S3_USE_A...
- 用离散标记重塑人体姿态:VQ-VAE实现关键点组合关系编码
-
在人体姿态估计领域,传统方法通常将关键点作为基本处理单元,这些关键点在人体骨架结构上代表关节位置(如肘部、膝盖和头部)的空间坐标。现有模型对这些关键点的预测主要采用两种范式:直接通过坐标回归或间接通过...
- B 客户端流RPC (clientstream Client Stream)
-
客户端编写一系列消息并将其发送到服务器,同样使用提供的流。一旦客户端写完消息,它就等待服务器读取消息并返回响应gRPC再次保证了单个RPC调用中的消息排序在客户端流RPC模式中,客户端会发送多个请...
- 我的模型我做主02——训练自己的大模型:简易入门指南
-
模型训练往往需要较高的配置,为了满足友友们的好奇心,这里我们不要内存,不要gpu,用最简单的方式,让大家感受一下什么是模型训练。基于你的硬件配置,我们可以设计一个完全在CPU上运行的简易模型训练方案。...
- 开源项目MessageNest打造个性化消息推送平台多种通知方式
-
今天介绍一个开源项目,MessageNest-可以打造个性化消息推送平台,整合邮件、钉钉、企业微信等多种通知方式。定制你的消息,让通知方式更灵活多样。开源地址:https://github.c...
- 使用投机规则API加快页面加载速度
-
当今的网络用户要求快速导航,从一个页面移动到另一个页面时应尽量减少延迟。投机规则应用程序接口(SpeculationRulesAPI)的出现改变了网络应用程序接口(WebAPI)领域的游戏规则。...
- JSONP安全攻防技术
-
关于JSONPJSONP全称是JSONwithPadding,是基于JSON格式的为解决跨域请求资源而产生的解决方案。它的基本原理是利用HTML的元素标签,远程调用JSON文件来实现数据传递。如果...
- 大数据Doris(六):编译 Doris遇到的问题
-
编译Doris遇到的问题一、js_generator.cc:(.text+0xfc3c):undefinedreferenceto`well_known_types_js’查找Doris...
- 网页内嵌PDF获取的办法
-
最近女王大人为了通过某认证考试,交了2000RMB,官方居然没有给线下教材资料,直接给的是在线教材,教材是PDF的但是是内嵌在网页内,可惜却没有给具体的PDF地址,无法下载,看到女王大人一点点的截图保...
- 印度女孩被邻居家客人性骚扰,父亲上门警告,反被围殴致死
-
微信的规则进行了调整希望大家看完故事多点“在看”,喜欢的话也点个分享和赞这样事儿君的推送才能继续出现在你的订阅列表里才能继续跟大家分享每个开怀大笑或拍案惊奇的好故事啦~话说只要稍微关注新闻的人,应该...
- 下周重要财经数据日程一览 (1229-0103)
-
下周焦点全球制造业PMI美国消费者信心指数美国首申失业救济人数值得注意的是,下周一希腊还将举行第三轮总统选举需要谷歌日历同步及部分智能手机(安卓,iPhone)同步日历功能的朋友请点击此链接,数据公布...
- PyTorch 深度学习实战(38):注意力机制全面解析
-
在上一篇文章中,我们探讨了分布式训练实战。本文将深入解析注意力机制的完整发展历程,从最初的Seq2Seq模型到革命性的Transformer架构。我们将使用PyTorch实现2个关键阶段的注意力机制变...
- 聊聊Spring AI的EmbeddingModel
-
序本文主要研究一下SpringAI的EmbeddingModelEmbeddingModelspring-ai-core/src/main/java/org/springframework/ai/e...
- 前端分享-少年了解过iframe么
-
iframe就像是HTML的「内嵌画布」,允许在页面中加载独立网页,如同在画布上叠加另一幅动态画卷。核心特性包括:独立上下文:每个iframe都拥有独立的DOM/CSS/JS环境(类似浏...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- HTML 参考手册 (28)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML中如何键入空格 (27)
- HTML常用标签 (29)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)