「Windows API/図形の描画」の版間の差分

出典: フリー教科書『ウィキブックス(Wikibooks)』
削除された内容 追加された内容
編集の要約なし
訂正
153 行 153 行




== 構造体 ==
== 構造体は無印C言語とやや異なる ==
WindowsAPIでは、構造体の宣言場所(いわゆる「グローバル領域」、コード冒頭の変数宣言などの場所)と、構造体の代入場所(wWinMain関数ブロック内など)とは、離れざるを得ない。
構造体を定義する場合、(C言語ではmain関数より上でも構造体の型宣言が出来たのと同様に)WindowsAPIプログラミングでもwWinMain関数よりも上の位置でも構造体の型の宣言はできる。そして、C言語と同様に、構造体の各要素への代入はmain関数など個別の関数側でしか行えない制約がある。

なぜなら (WindowsAPIの)wWinMain 関数と (無印C言語の)main関数との仕組みが、違うからである。

wWinMain 関数ブロックで構造体の型を宣言しても、wWinMain 関数はアプリ起動イベント専用の'''ローカル'''な関数なので、他のイベント(たとえば画面描画の関数など)の関数ブロックからは原則的に、その構造体の型を呼び出せなくなってしまうので、まったく実用的では無くなる。

しかし、一方でもしもグローバル領域で宣言した構造体ならば、その構造体は画像描画の関数などの全てのイベントで(構造体を)呼び出せる。


しかし、グローバル領域では、代入などの操作が出来ないので、構造体の各要素の初期値の代入すら出来ない。

このため、代入などは wWinMain 関数ブロック内で行う必要があるという制約がある。



そして、この制約は、WinodowsAPIにおいては、wWinMain関数周辺にコードが膨大にある状況では、構造体の宣言と初期値代入が離れるのは、とても構造体のコードが一覧しづらくなり不便である。
そして、この制約は、WinodowsAPIにおいては、wWinMain関数周辺にコードが膨大にある状況では、構造体の宣言と初期値代入が離れるのは、とても構造体のコードが一覧しづらくなり不便である。


なので、特別理由の無いかぎり、構造体の宣言はwWinMain関数側一括して宣言と初期値代入などをしてしまうのが簡便る。
しかし、不便あるが他に簡便な方法が少ないので、したなく、構造体の宣言は、グローバル領域行い、構造体の各要素への初期値代入などはwWinMain関数ブロックなど行うことになる。

2019年9月23日 (月) 01:06時点における版

図形を塗りつぶすには?

図形を塗りつぶしたい場合には、図形描画命令の直前に、下記のように描画のモードをブラシに設定します。

塗りつぶし専用の命令は、無いのです。

Windows API でいう「ブラシ」とは、図形の塗りつぶしの色を設定するためのツールのことです。画像作成ソフトで例えるなら、Windows API でいうブラシとは、画像作成ソフトの「バケツ」ツールに近いです。世間一般の画材でいう「ブラシ」とは、意味が違います。

			HBRUSH brasi_buhin_1;
			brasi_buhin_1 = CreateSolidBrush(RGB(255, 100, 100)); // ピンク色のブラシを作成
			SelectObject(hdc, brasi_buhin_1); // ウィンドウhdcと、さきほど作成したブラシを関連づけ
			Rectangle(hdc, 20, 30, 50, 70); // 図形の描画

上記の2段階のブラシ作成の作業は、CreateSolidBrush はブラシの色作成のためのアドレス確保を意味し、もう片方 SelectObject は、どのウィンドウでどのブラシ色を使うかを定義するための情報を保存するアドレスの確保を意味しています。

「Rectangle」は、単に、四角形を描くための命令です。これが塗りつぶしか、それとも輪郭線だけをペンで描くのかは、このRectangle命令だけでは指定できず、この命令が来る前の設定に依存します。


当然、これらの命令は、WM_PAINT ケースにある

「// TODO: HDC を使用する描画コードをここに追加してください...」

のあるブロックで書く必要があります。


つまり、下記のコードのようになります。

case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

			HBRUSH brasi_buhin_1;
			brasi_buhin_1 = CreateSolidBrush(RGB(255, 100, 100)); // ピンク色のブラシを作成
			SelectObject(hdc, brasi_buhin_1); // ウィンドウhdcと、さきほど作成したブラシを関連づけ
			Rectangle(hdc, 20, 30, 50, 70); // 図形の描画

            EndPaint(hWnd, &ps);
        }
        break;


さて、上記コードを実際に描画すると、四角形の輪郭が黒色で描画されます。これは、ペンの設定が黒色になってるからです。

図形をピンク色の長方形だけにしたい場合、ペンの色もピンク色にします。

Rectangle 命令の前に、あらかじめ

			HPEN pen_buhin_1;
			pen_buhin_1 = CreatePen (PS_SOLID, 0, RGB(255, 100, 100));
			SelectObject(hdc, pen_buhin_1);

のように、ペンを指定します。 CreatePen 命令の第2引数の「0」は、ペン幅の追加の線幅です。「0」に指定しても、最低でも1ドットぶんの線幅はあります。

よって、今までの話をまとめると、

			HPEN pen_buhin_1; // ペンの設定用
			pen_buhin_1 = CreatePen (PS_SOLID, 0, RGB(255, 100, 100));
			SelectObject(hdc, pen_buhin_1);

			HBRUSH brasi_buhin_1; // ブラシの設定用
			brasi_buhin_1 = CreateSolidBrush(RGB(255, 100, 100));
			SelectObject(hdc, brasi_buhin_1);

			Rectangle(hdc, 20, 30, 50, 70); // 図形の描画

のように、なります。

関数的な処理の活用

複数個の描画を関数にしたい場合は

ソースの冒頭で関数を宣言する際、通常の関数と同じように、

void kansuu(HDC hdc){
    // ここに描画命令を書く
}

のように宣言する必要があります。引数の型と変数名が、単にハンドル型(HDC)とハンドル変数(hdc)になっただけです。通常のC言語の関数と同じです。


また、呼び出し側(親側)で、

kansuu(hdc);

のように、hdcの値を渡す必要があります(通常のC言語の関数と同じです)。

もし、上述の引数定義の手間を面倒くさがって、hdc の定義を static に変更すると、コードの他の部分の動作結果がバグります。なので、hdc は static に変更せず、ウィザードの初期設定のままにしておいてください。


当然、呼び出し側(親側)の関数のコードのある位置は、

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

// TODO: HDC を使用する描画コードをここに追加してください...

より下の部分です。


つまり、

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

            (中略)
            kansuu(hdc);
            (中略)

のように書かれます。

For文による代用

WindowsAPIで関数を使う場合、関数の内容を記述する場所が、呼び出し元の場所から、大きく離れた場所に記述しなければならない場合があります。

このような場合、いっそ関数をつかわずに、For文で記述するという方法もあります。

WindowsAPI でも、標準C言語と同様にFor文を使えます。

For文の繰り返しの数(たとえば for (int j = 1; j <= 20; ++j) の20の部分)を十分に大きな自然数にしておいて、使い終わったら break でそのブロックを脱出すればいいだけです。

たとえば、5種類の違う作業の前に、共通する前段階の作業を5回呼び出したい場合、

for (int j = 1; j <= 20; ++j) {

	ここに共通する前段階の作業を記述 ;

	if (j == 1) { 1回目にしたい処理内容 ; }
	if (j == 2) { 2回目にしたい処理内容 ; }
	if (j == 3) { 3回目にしたい処理内容 ; }
	if (j == 4) { 4回目にしたい処理内容 ; }
	if (j == 5) { 5回目にしたい処理内容 ; 
					 break;}
}

のように書くのです。

このFor文による代用の方法は、有限の回数しか呼び出しをしない処理にしか使えません。何度使うのか不特定な場合や、ずっとループ的に処理させたい場合には、むいてない方法です。

しかし、有限の回数しか呼び出さない処理ならば、この方法でも充分なのです。


また、もし関数を使う場合でも、For文で書いた処理を関数に置き換えるのはラクです。なので、関数を作る前に、とりあえず、For文で試しに似たような処理を書くという方法もあります。


構造体は無印C言語とやや異なる

WindowsAPIでは、構造体の宣言場所(いわゆる「グローバル領域」、コード冒頭の変数宣言などの場所)と、構造体の代入場所(wWinMain関数ブロック内など)とは、離れざるを得ない。

なぜなら (WindowsAPIの)wWinMain 関数と (無印C言語の)main関数との仕組みが、違うからである。

wWinMain 関数ブロックで構造体の型を宣言しても、wWinMain 関数はアプリ起動イベント専用のローカルな関数なので、他のイベント(たとえば画面描画の関数など)の関数ブロックからは原則的に、その構造体の型を呼び出せなくなってしまうので、まったく実用的では無くなる。

しかし、一方でもしもグローバル領域で宣言した構造体ならば、その構造体は画像描画の関数などの全てのイベントで(構造体を)呼び出せる。


しかし、グローバル領域では、代入などの操作が出来ないので、構造体の各要素の初期値の代入すら出来ない。

このため、代入などは wWinMain 関数ブロック内で行う必要があるという制約がある。


そして、この制約は、WinodowsAPIにおいては、wWinMain関数周辺にコードが膨大にある状況では、構造体の宣言と初期値代入が離れるのは、とても構造体のコードが一覧しづらくなり不便である。

しかし、不便であるが、他に簡便な方法が少ないので、しかたなく、構造体の宣言は、グローバル領域で行い、構造体の各要素への初期値代入などはwWinMain関数ブロックなどで行うことになる。