クライアント領域に「星形」の穴があいて、下にある
デスクトップの一部が見えています。その他穴の形はメニューから
「長方形」「丸」「三角形」が選べます。
ウィンドウをよく観察してみると 「最大化ボタン」がついており、ウィンドウ枠も大きさの変えられる 枠のようです。しかし、ここではウィンドウの大きさを変えられては 都合が悪いこともあるのでプログラムで大きさを変えられないように してあります。
では、プログラムを見てみましょう。
これは、普通のメニューリソースです。// round02.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "穴の種類(&A)" BEGIN MENUITEM "長方形(&R)", IDM_RECT MENUITEM "三角形(&T)", IDM_TRI MENUITEM "丸(&C)", IDM_RND MENUITEM "星形(&S)", IDM_STR END END
ウィンドウの大きさを300*200にします。WINX, WINYという定数を 定義してみました。// round02.cpp #define STRICT #include <windows.h> #include "resource.h" #define WINX 300 #define WINY 200 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); char szClassName[] = "round02"; //ウィンドウクラス int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
これは、いつもと同じです。メニュー名の指定を忘れないでください。//ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); }
これもいつもとほぼ同じです。ウィンドウの大きさをWINX*WINYに 指定しています。//ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかる穴あきウィンドウ", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW,//ウィンドウの種類 CW_USEDEFAULT,//X座標 CW_USEDEFAULT,//Y座標 WINX,//幅 WINY,//高さ NULL,//親ウィンドウのハンドル、親を作るときはNULL NULL,//メニューハンドル、クラスメニューを使うときはNULL hInst,//インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
ちょっと長いのですがメッセージごとに見ていくと簡単です。 ウィンドウができたらすぐにリージョンハンドルを 初期化しておきます。なぜこんなことをするかというと 起動してすぐに終了するとき、無効なリージョンハンドルがあると DeleteObjectが失敗するからです。 (別に失敗しても差し支えはありません。はじめからリージョンが 存在しないのでDeleteObjectに失敗しても大丈夫です。)//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static HRGN hRgn1, hRgn2, hRgn3, hRgn, hRgnX; WINDOWPLACEMENT wndpl; RECT rc; static int def; //メニュー、タイトルバーの高さ POINT pta[3], ptb[3]; switch (msg) { case WM_CREATE: def = GetSystemMetrics(SM_CYMENUSIZE) + GetSystemMetrics(SM_CYCAPTION); hRgn = CreateRectRgn(0, 0, 1, 1); hRgn1 = CreateRectRgn(0, 0, 1, 1); hRgn2 = CreateRectRgn(0, 0, 1, 1); hRgn3 = CreateRectRgn(0, 0, 1, 1); hRgnX = CreateRectRgn(0, 0, 1, 1); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_RECT: hRgn = CreateRectRgn(0, 0, 1, 1); hRgn1 = CreateRectRgn(0, 0, WINX, WINY); hRgn2 = CreateRectRgn(30, 30 + def, WINX - 30, WINY - 30); CombineRgn(hRgn, hRgn1, hRgn2, RGN_DIFF); SetWindowRgn(hWnd, hRgn, TRUE); break; case IDM_RND: hRgn = CreateRectRgn(0, 0, 1, 1); hRgn1 = CreateRectRgn(0, 0, WINX, WINY); hRgn2 = CreateEllipticRgn(30, 30+def, WINX -30, WINY - 30); CombineRgn(hRgn, hRgn1, hRgn2, RGN_DIFF); SetWindowRgn(hWnd, hRgn, TRUE); break; case IDM_STR: pta[0].x = 70; pta[0].y = WINY - 50; pta[1].x = WINX / 2; pta[1].y = 10 + def; pta[2].x = WINX - 70; pta[2].y = WINY - 50; ptb[0].x = 70; ptb[0].y = 50 + def; ptb[1].x = WINX - 70; ptb[1].y = 50 + def; ptb[2].x = WINX / 2; ptb[2].y = WINY - 10; hRgn = CreateRectRgn(0, 0, 1, 1); hRgnX = CreateRectRgn(0, 0, 1, 1); hRgn1 = CreateRectRgn(0, 0, WINX, WINY); hRgn2 = CreatePolygonRgn(pta, 3, WINDING); hRgn3 = CreatePolygonRgn(ptb, 3, WINDING); CombineRgn(hRgnX, hRgn2, hRgn3, RGN_OR); CombineRgn(hRgn, hRgn1, hRgnX, RGN_DIFF); SetWindowRgn(hWnd, hRgn, TRUE); break; case IDM_TRI: pta[0].x = 30; pta[0].y = WINY - 30; pta[1].x = WINX - 30; pta[1].y = WINY - 30; pta[2].x = WINX / 2; pta[2].y = def + 30; hRgn = CreateRectRgn(0, 0, 1, 1); hRgnX = CreateRectRgn(0, 0, 1, 1); hRgn1 = CreateRectRgn(0, 0, WINX, WINY); hRgn2 = CreatePolygonRgn(pta, 3, WINDING); CombineRgn(hRgn, hRgn1, hRgn2, RGN_DIFF); SetWindowRgn(hWnd, hRgn, TRUE); break; } break; case WM_SIZE: wndpl.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(hWnd, &wndpl); rc = wndpl.rcNormalPosition; MoveWindow(hWnd, rc.left, rc.top, WINX, WINY, TRUE); break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: DeleteObject(hRgn); DeleteObject(hRgn1); DeleteObject(hRgn2); DeleteObject(hRgn3); DeleteObject(hRgnX); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }
また、defはタイトルバーとメニューバーの高さを合計したものです。 これを求めておくとクライアント領域に美しく?穴をあけるのに 役立ちます。
メニューからIDM_RECT(「長方形」)が選択されると、 ウィンドウ全体と同じ大きさのリージョン(hRgn1)と これより一回り小さいリージョン(hRgn2)を作ります。 そして、この2つをRGN_DIFFで合成します(hRgn)。 これをSetWindowRgnすれば四角い穴のあいたウィンドウができあがります。 つまりhRgnはウィンドウと同じ大きさのリージョンから小さい長方形 のリージョンを差し引いたリージョン、つまり穴あきリージョンと なります。 リージョンの合成については第48章を 参照してください。
メニューからIDM_RND(「丸」)を選択したときも同じ考え方で 処理します。
次にIDM_STR(「星形」)ですが、これは三角形を2つ合成して まず星形のリージョン(hRgnX)を作ります。 そして、ウィンドウと同じ大きさのリージョンからhRhnXを引き算します。
さて、多角形のリージョンの作り方ですが、CreatePolygonRgnを使います。
多角形の各頂点をPOINT構造体の配列に納めます。 そして、lpptに配列のポインタを指定します。HRGN CreatePolygonRgn( CONST POINT *lppt, // POINT構造体配列へのポインタ int cPoints, // 配列の個数 int fnPolyFillMode // 多角形フィリングモード );
cPointsは配列の個数を指定します。
fnPolyFillModeはALTERNATEかWINDINGのどちらかを指定します。
ALTERNATEは辺1と辺2、辺3と辺4の間のように、多角形の奇数番号の辺と
偶数番号の辺の間を塗りつぶします。
WINDINGはワインディング値が0でないリージョンを塗りつぶします。
ワインディング値とは多角形を描画するペンがリージョンの周囲を回る回数です。
(わかりにくいのですが渦巻き型みたいなものを考えてみてください。)
単純なものであればALTERNATEでも、WINDINGでも結果は同じです。
IDM_TRIの処理については星形がわかれば、説明の必要はないですね。
次に、このウィンドウはいつものように、WS_OVERLAPPEDWINDOWで作ったので 大きさを変えることができます。これは都合が悪いですね。 対策としてはWS_OVERLAPPEDWINDOWで作らないのが一番です。
しかし、WM_SIZEメッセージを処理することにより ユーザーが大きさを変えても強制的に元に戻すことができます。 大きさ自体を元に戻すにはMoveWindow関数を使えばよいのですが ウィンドウの位置がわかりません。そこでウィンドウの位置を知るために GetWindowPlacement関数を使ってみました。
ウィンドウハンドルを指定することにより、WINDOWPLACEMENT構造体に 位置などの情報を取得することができます。 注意する点はこの関数を使う前にWINDOWPLACEMENT構造体の lengthメンバにsizeof(WINDOWPLACEMENT)を指定しておくことです。 これを忘れるとおかしなことになります。BOOL GetWindowPlacement( HWND hWnd,// ウィンドウハンドル WINDOWPLACEMENT *lpwndpl//WINDOWPLACEMENT構造体へのポインタ );
lengthはこの構造体のサイズです。typedef struct _WINDOWPLACEMENT { // wndpl UINT length; UINT flags; UINT showCmd; POINT ptMinPosition; POINT ptMaxPosition; RECT rcNormalPosition; } WINDOWPLACEMENT;
さて、WM_DESTROYメッセージが来たらリージョンを破棄します。 WM_CREATEのところでリージョンを作っておかないと、アプリケーションを 起動後すぐに終了した場合DeleteObject関数が失敗します。 最初にも書いたように、リージョンが最初からない場合は DeleteObjectが失敗するのは当然ですし、失敗しても多分 不都合はないと思います。
Update 25/May/1998 By Y.Kumei