第181章 MCコントロール


MCコントロールとはMonth Calendar Controlsの略です。 早い話がDTPコントロールの時出てきたカレンダー部分です。 扱い方はDTPと非常によく似ています。



カレンダーの日付をクリックするとその下に「選択=**」 と表示されます。 本日の日付を表示することもできます。 また、左の図のように1月1日を0週とした、週数を 表示することもできます。

親ウィンドウの大きさは手動で調整しています。



作り方は簡単です。 まず新しいコモンコントロールの準備をします。 INITCOMMONCONTROLSEX構造体のdwICCメンバにICC_DATE_CLASSESを 設定してInitCommonControlsEx関数を実行します。 次にCreateWindowEx関数でウィンドウクラスをMONTHCAL_CLASS にして、ウィンドウスタイルにMCS_DAYSTATEを加えてMCコントロールを 作ります。MCコントロールのスタイルには次のような ものがあります。

MCS_DAYSTATE MCN_GETDAYSTATEメッセージを送って
どの日をボールドにするかを指定できます。
MCS_MULTISELECT日付の範囲を指定できます。
MCS_NOTODAYコントロールの下のほうに
「本日」を表示しません。
MCS_NOTODAYCIRCLE今日の日付を丸で囲みません。
MCS_WEEKNUMBERS週数を表示します。

このときウィンドウの大きさは0*0にしておきます。 次にこのコントロールに対してMCM_GETMINREQRECTメッセージを送って 必要な最小のウィンドウの大きさを求めます。MCコントロールの 大きさをMoveWindow関数などで調整します。

では、プログラムを見てみましょう。

// newctl04.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "表示(&O)" BEGIN MENUITEM "週数を表示(&W)", IDM_WEEKNO MENUITEM "本日を表示しない(&T)", IDM_NOTODAY END END

ごく普通のメニューです。これは、初期メニューで プログラム中でメニュー項目、IDなどが変更されます。

// newctl04.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <commctrl.h> #include "resource.h" #define IDM_TODAY 40004 #define IDM_NOWEEKNO 40005 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void MyModifyMenu(HMENU, int, int, char *); void MyJustifyWindow(HWND); char szClassName[] = "newctl04"; //ウィンドウクラス HINSTANCE hInst;

プログラム中で「本日を表示する」(IDM_TODAY)、 「週数を表示しない」(IDM_NOWEEKNO)というように メニューが状況に応じて変化します。そのため、初期状態 でリソーススクリプトに登場しないメニューのIDは ここでdefineしています。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM 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)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるカレンダー", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

このへんはいつもと同じです。WinMain関数の中でインスタンスハンドルを グローバル変数にコピーしている点に注意して下さい。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; int wy; //MCコントロールの高さ static HWND hMC; INITCOMMONCONTROLSEX ic; RECT rc; char szDate[256]; LPNMSELCHANGE lpSChange; static SYSTEMTIME st; HDC hdc; PAINTSTRUCT ps; LONG lStyle; static HMENU hMenu; switch (msg) { case WM_CREATE: ic.dwSize = sizeof(INITCOMMONCONTROLSEX); ic.dwICC = ICC_DATE_CLASSES; InitCommonControlsEx(&ic); hMC = CreateWindowEx(0, MONTHCAL_CLASS, "", WS_VISIBLE | WS_CHILD | WS_BORDER | MCS_DAYSTATE, 0, 0, 0, 0, hWnd, NULL, hInst, NULL); MyJustifyWindow(hMC); MonthCal_GetCurSel(hMC, &st); hMenu = GetMenu(hWnd); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); wsprintf(szDate, "選択=%d/%02d/%02d", st.wYear, st.wMonth, st.wDay); GetWindowRect(hMC, &rc); wy = rc.bottom - rc.top; TextOut(hdc, 5, wy + 5, szDate, strlen(szDate)); EndPaint(hWnd, &ps); break; case WM_NOTIFY: lpSChange = (LPNMSELCHANGE)lp; if (lpSChange->nmhdr.hwndFrom != hMC || lpSChange->nmhdr.code != MCN_SELCHANGE) return DefWindowProc(hWnd, msg, wp, lp); MonthCal_GetCurSel(hMC, &st); InvalidateRect(hWnd, NULL, TRUE); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_NOTODAY: lStyle = GetWindowLong(hMC, GWL_STYLE); lStyle |= MCS_NOTODAY; SetWindowLong(hMC, GWL_STYLE, lStyle); MyModifyMenu(hMenu, IDM_NOTODAY, IDM_TODAY, "本日を表示する(&T)"); MyJustifyWindow(hMC); InvalidateRect(hWnd, NULL, TRUE); break; case IDM_WEEKNO: lStyle = GetWindowLong(hMC, GWL_STYLE); lStyle |= MCS_WEEKNUMBERS; SetWindowLong(hMC, GWL_STYLE, lStyle); MyModifyMenu(hMenu, IDM_WEEKNO, IDM_NOWEEKNO, "週数を表示しない(&W)"); MyJustifyWindow(hMC); InvalidateRect(hWnd, NULL, TRUE); break; case IDM_TODAY: lStyle = GetWindowLong(hMC, GWL_STYLE); lStyle &= ~MCS_NOTODAY; SetWindowLong(hMC, GWL_STYLE, lStyle); MyModifyMenu(hMenu, IDM_TODAY, IDM_NOTODAY, "本日を表示しない(&T)"); MyJustifyWindow(hMC); InvalidateRect(hWnd, NULL, TRUE); break; case IDM_NOWEEKNO: lStyle = GetWindowLong(hMC, GWL_STYLE); lStyle &= ~MCS_WEEKNUMBERS; SetWindowLong(hMC, GWL_STYLE, lStyle); MyModifyMenu(hMenu, IDM_NOWEEKNO, IDM_WEEKNO, "週数を表示(&W)"); MyJustifyWindow(hMC); InvalidateRect(hWnd, NULL, TRUE); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hMC); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

WM_CREATEメッセージで初期化処理をします。コモンコントロールを 初期化して、MCコントロールを作ります。自作関数の MyJustifyWindow関数はMCコントロールの大きさを調整します。また、 この時点でメニューハンドルを取得しておきます。

WM_PAINTメッセージが来たら、szDataを表示します。 表示位置はy座標でMCコントロールの最下端より5ピクセル 下がったところです。

ユーザーがMCコントロールをクリックして日付を選択したら その親に通知メッセージが届きます。ここでは、選択された 日付が変更された時処理を行います。前章とやり方は同じですが 構造体とかマクロが異なります。

MCN_SELCHANGE lpNMSelChange = (LPNMSELCHANGE) lParam;

日付とか範囲が変更になった時、MCコントロールより送られてくる 通知メッセージです。

typedef struct tagNMSELCHANGE{ NMHDR nmhdr; SYSTEMTIME stSelStart; SYSTEMTIME stSelEnd; } NMSELCHANGE, FAR * LPNMSELCHANGE;

nmhdrは説明は不要ですね。

stSelStartはユーザーが選択した範囲の最初の日、 stSelEndは最後の日のSYSTEMTIME構造体です。

BOOL MonthCal_GetCurSel( HWND hwndMC, LPSYSTEMTIME lpSysTime );

MCコントロールから選択されている日付をSYSTEMTIME 構造体に取得するマクロです。MCS_MULTISELECTスタイルが 設定されていると失敗します。

日付を取得したらInvalidateRect関数で親ウィンドウにWM_PAINTメッセージを 発生させてクライアント領域を書きなおします。

メニューで「本日を表示しない」(IDM_NOTODAY)が選択されたら MCコントロールのウィンドウスタイルを取得します。 そして、これにMCS_NOTODAYを加えて、 ウィンドウスタイルを再設定します。 メニューも「本日を表示する」(IDM_TODAY)に変えます。 ModifyMenu関数を使うと簡単なのですが現在では SetMenuItemInfo関数に取って代わられました。 SetMenuItemInfo関数は第173章で名前だけ 出てきました。

ここでは処理が長ったらしくなるので、ModifyMenuに似せたMyModifyMenu 関数を付くって、メニュー項目を変更しています。さて、MCコントロールの スタイルが変わるとMCコントロールの大きさも変わるのでここでも InvalidateRect関数を呼んで親ウィンドウのクライアント領域を 書きなおしています。

BOOL SetMenuItemInfo( HMENU hMenu, UINT uItem, BOOL fByPosition, LPMENUITEMINFO lpmii );

メニュー項目の情報を変える関数です。

hMenuはメニューハンドルです。

uItemは変更するメニュー項目のIDまたは位置です。

fByPositionがFALSEならuItemはIDを表し、それ以外なら位置を表します。

lpmiiはMENUITEMINFO構造体へのポインタです。詳細は 第179章を参照して下さい。

ウィンドウスタイルに関する他のメニュー項目でも処理は同様です。

void MyModifyMenu(HMENU hMenu, int oldid, int newid, char *item) { MENUITEMINFO mi; ZeroMemory(&mi, sizeof(MENUITEMINFO)); mi.cbSize = sizeof(MENUITEMINFO); mi.fMask = MIIM_TYPE | MIIM_ID; mi.fType = MFT_STRING; mi.fState = MFS_ENABLED; mi.wID = newid; mi.dwTypeData = (LPTSTR)item; mi.cch = strlen(item); if (SetMenuItemInfo(hMenu, oldid, FALSE, &mi) == 0) MessageBox(NULL, "Error", "OK", MB_OK); return; }

メニュー項目の内容を変更する自作関数です。 変更後のメニュー項目の内容をMENUITEMINFO構造体に セットしてSetMenuItemInfo関数で変更します。

VOID ZeroMemory( PVOID Destination, DWORD Length );

メモリーブロックを0で埋める関数です。memsetでも代用できます。

Destinationは0で埋めるメモリーブロックの先頭アドレスです。

Lengthはメモリーブロックの大きさです。

void MyJustifyWindow(HWND hMC) { int wx, wy; RECT rc; SendMessage(hMC, MCM_GETMINREQRECT, 0, (LPARAM)&rc); wx = rc.right - rc.left; wy = rc.bottom - rc.top; MoveWindow(hMC, 0, 0, wx, wy, TRUE); return; }

MCコントロールを表示するのに必要なウィンドウの大きさを求めて 、MCコントロールをその大きさにする関数です。

MCM_GETMINREQRECT wParam = 0; lParam = (LPARAM) (LPRECT) lpRectInfo;

lpRectInfoに大きさが取得されます。

今回もたいして難しくはありませんでした。さて、今回作った プログラムは親ウィンドウの大きさそのものは考慮していません。 コントロールに合わせて親ウィンドウの大きさも調整するように 改良してみて下さい。


[SDK第2部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 08/Mar/1999 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。