エディタを作る-1の4 APIで文字色 制御
せっかく エスケープ シーケンスが Windowsのコマンドプロンプトでも使えるようになる ANSICON という優秀なソフトを見つけたのに、Visual Studioのデバッグ出力画面で使えないという 「大どんでん返し」を食らった。
コマンドラインで使える colorコマンドも 目的としている用途とは 違っているし、動作が安定しない。 仕方ないので、素直にWindowsのAPIを使って、文字色と背景色を変えるコードを書く事にした。
とりあえず移植性は考えずに、WindowsのVisual Studioでビルドできるコードを書き、後々 Mac OSや Linuxでも そのままソースが使えるように、後で改変する事とする。
その 後の移植が少しでも楽になる事を考慮して、この文字色を変えるコードは(他のコンソールアプリでも使えそうなので)、別ファイルに分けて作成する事にする。
複数ファイルでの 分割開発 の良いサンプルにもなると思う。
1)ファイルの構成
ファイルの構成は、次の通り。
① main.c ・・・ 今回はテストプログラムを入れる。
文字色変更の関数を 利用する側のコード。
② color.c ・・・ 文字色変更 関連の関数が入るファイル。
③ color.h ・・・ 上のファイル color.c のヘッダーファイル
(②③はペア)
作成した関数は、3つ (この関数名は当方で命名したもの)
① clearScreen() ・・・ コンソール画面を消去して、
カーソルを画面左上に移動させる
② textColor( 色コード ) ・・・ 文字の色を変更する
③ setColor(文字色, 背景色) ・・・ 文字と背景色を変更
(文字色、背景色 共に同じ色コードを使う)
文字だけの色を変える関数名を color() としてもいいのだが、将来 グラフィックの色を変える時の関数名にcolor() を使いたくなることもあるだろう。 あまりに汎用的でシンプルな単語は避けた方がいいだろうから、textColor() としました。
これも colorText() と すべきか? textColor() とすべきか? 迷うところですが LATEXを見習って textColor としました。 (この方が自然かなと…)
C言語ではなく、C++なら(拡張子が .Cppなら)、②③とも同じ関数名 setColor() にして(引数の違いで区別できる)しまうことも出来るが、C言語では 同じ関数名は使えないので、このような別名に分けました。
では、実際のコードを見て行こう。
まずは、ヘッダーファイル: color.h の中身 ↓ から、
# ifndef _COLOR_H_
# define _COLOR_H_
# include <windows.h> //system()に必要
//文字色の定義
# define T_黒 0x00
# define T_暗い青 0x01
# define T_暗い緑 0x02
# define T_暗い水色 0x03
# define T_暗い赤 0x04
# define T_暗い紫 0x05
# define T_暗い黄色 0x06
# define T_暗い白 0x07
# define T_灰色 0x08
# define T_青 0x09
# define T_緑 0x0a
# define T_水色 0x0b
# define T_赤 0x0c
# define T_紫 0x0d
# define T_黄色 0x0e
# define T_白 0x0f
//
# define T_高輝度 0x08 // 高輝度マスクbit
# define T_赤_MASK 0x04 // 赤色マスクビット
# define T_緑_MASK 0x02 // 緑色マスクビット
# define T_青_MASK 0x01 // 青色マスクビット
// 画面を消去
void clearScreen(void);
// 文字色の変更
void textColor(int col);
void setColor(int fg, int bg); // 文字色、背景色の変更
# endif //End of color.h
#define で 色の固定値 定義をここで行っている。(頭の T_ はテキスト(Text)の T のつもり)
2)色コード
ヘッダーファイルの中で 色コードを define宣言しているのだが、この命名も悩ましい所だ。
ネットで探し当てた (APIを使って) 文字に色を付けられる 色の定数の一例は、次の通り。 ただ、これも色々な命名例があって混乱している。
コマンド プロンプトで使えるcolorコマンドのヘルプ ↓ を表示させてみた。
上の例と このヘルプでの表現がすでに異なる。 ヘルプの方を 標準として これに従うのも 一つの手だが、「輝く白」などという変な表現もあるので使いづらい。
悩んだあげく、以下 ↓ の表現を使うことにした。 1~7の下位番号を「青~白」にするのではなく、「暗い~」という表現にし、8~Fの明るい色をシンプルな色名(灰色、青~白)とした。 (明るい~ という表現を無くした)
define宣言による色コードも 英語を使うのが一般的だが、私は積極的に日本語(全角漢字)をそのまま使うことにしている。 この方が 日本人には可読性が上がると思う。
この色コードの命名方法に問題があるようなら、英単語の color code を自分で定義して 使えば良い。
3)API プログラムの解説
次に color.c ↓ の関数定義 部分。
/*++
*
* Module Name: Color.c
*
--*/
# include "color.h"
WORD gw背景色 = 0;
HANDLE hStdHandle = NULL;
# define GET_STDHANDLE() hStdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
//--- 画面を消去
void clearScreen(void)
{
# if defined(_WIN32) || defined(_WIN64)
system("cls");
GET_STDHANDLE();
# else
printf("\x1B[2J\x1B[1;1H"); //画面をクリアし、キャレット位置を左上にx,y=1,1
# endif
}
//--- 文字色の変更
void textColor(int col)
{
if (hStdHandle != NULL){
if (col <= 0x0F){
col |= gw背景色;
SetConsoleTextAttribute(hStdHandle, col);
}
}
}
//--- 文字と背景色の変更
void setColor(int fg, int bg)
{
WORD attr = 0;
if (hStdHandle == NULL){
GET_STDHANDLE();
}
//背景
if (bg & T_高輝度) {
attr |= BACKGROUND_INTENSITY;
}
if (bg & T_赤_MASK) {
attr |= BACKGROUND_RED;
}
if (bg & T_緑_MASK) {
attr |= BACKGROUND_GREEN;
}
if (bg & T_青_MASK) {
attr |= BACKGROUND_BLUE;
}
gw背景色 = attr; //一時記憶
//文字
if (fg & T_高輝度) {
attr |= FOREGROUND_INTENSITY;
}
if (fg & T_赤_MASK) {
attr |= FOREGROUND_RED;
}
if (fg & T_緑_MASK) {
attr |= FOREGROUND_GREEN;
}
if (fg & T_青_MASK) {
attr |= FOREGROUND_BLUE;
}
SetConsoleTextAttribute(hStdHandle, attr);
}
//--- End of Color.c
「画面の消去」clearScreen()関数だけは、開発環境が Windowsの場合、
system("cls"); を実行し、
Mac-OS や Linux の場合、これまで通り エスケープシーケンスを使うよう移植性を保ってある。
そのために、_WIN32 もしくは _WIN64 が定義されたいたら Windowsだと#if definedで 判断している。
textColo()関数や setColor()関数は、まったく移植性を考慮していないので、今後の課題とする。
使っているAPI 関数の解説:
① GetStdHandle(STD_OUTPUT_HANDLE); で 画面出力のハンドルを取得する。
② SetConsoleTextAttribute(ハンドル, attr)関数で 色を変更する
・ ハンドルの所には①で得た ハンドル値をセット
・attrの部分に 文字色の値をセットする
(背景色も この attr にセットする)
4)動作確認 と 使い方のサンプル
textColor()関数を使う前に、必ず clearScreen() か setColor()を1回は実行しないといけません。 (ハンドルを取得するために)
main.c 内の 動作テストの為のコード が 以下の通り。
/* ソリューション名 プロジェクト
* main.cpp (5_colorSet → colorSet)
*
* 画面消去&画面の文字に色を付ける。 APIで制御
*/
# include "color.h"
extern gw背景色; //グローバル変数:背景色にアクセスできるようにする
# include <stdio.h>
# include <conio.h>
int main(void)
{
clearScreen();
setColor(T_赤, T_灰色); printf("赤, BG:GRAY\n");
setColor(T_黒, T_白); printf("黒 , BG:WHITE\n");
setColor(T_暗い青, T_白); printf("暗い青 BG:WHITE\n");
setColor(T_暗い緑, T_白); printf("暗い緑 BG:WHITE\n");
setColor(T_暗い水色, T_白); printf("暗い水色 BG:WHITE\n");
setColor(T_暗い赤, T_白); printf("暗い赤 BG:WHITE\n");
setColor(T_暗い紫, T_白); printf("暗い紫 BG:WHITE\n");
setColor(T_暗い黄色, T_白); printf("暗い黄色 BG:WHITE\n");
setColor(T_暗い白, T_白); printf("暗い白 BG:WHITE\n");
textColor(T_灰色); printf("glay\n"); //背景色=白は引き継がれる
textColor(T_青); printf("blue\n");
gw背景色 = T_黒; //← 背景色を強制的に黒に!
textColor(T_緑); printf("green\n");
textColor(T_水色); printf("cyan\n");
textColor(T_赤); printf("red\n");
textColor(T_緑); printf("green\n");
textColor(T_黄色); printf("yellow\n");
textColor(T_白); printf("white\n");
setColor(T_白, T_黒); printf("BG: black\n");
printf("文字色=白\n");
int i = getchar(); //キー入力 待ち
return 0;
}
これを実行すると、↓ こんな画面になる。
参考まで、
背景色と文字色の組み合わせ一覧は、次の通り。
。
この記事が気に入ったらサポートをしてみませんか?