第131章 リッチエディットコントロールと通知メッセージ


前回はフォントを変更するのにわざわざメニューバーから 「編集」「フォントの変更」を選択していました。 右クリックをしたら、その場にメニューが出ると便利ですね。 今回は、右クリックすると「終了」と「フォントの変更」メニューが 出るように改良します。

右クリックでメニューが出てくるプログラムは 第44章でやりました。 そのときは親ウィンドウのWM_RBUTTONDOWNメッセージを処理しました。 しかし、今回右クリックされるのは親ウィンドウではなく、その子供の リッチテキストエディットコントロールです。

すぐに思いつく方法は通知メッセージの処理とウィンドウのサブクラス化です。 今回は前者の通知メッセージについてやります。通知メッセージについては すでにいろいろなところで出てきました。 (第65章第108章、 など)コントロールにイベントが発生したら親にも知らせることでしたね。

例によって通知メッセージはWM_NOTIFYメッセージとしてやってきます。

リッチテキストエディットコントロールではコントロール内にキーボードや マウスのイベントが発生したらEN_MSGFILTERを親に通知します。

EN_MSGFILTER wParam = (WPARAM) (UINT) uID; lParam = (LPARAM) (MSGFILTER) lpMsgFilter;

uIDはリッチエディットコントロールのID番号です。 lpMsgFilterはMSGFILTER構造体へのポインタです。

MSGFILTER構造体はどうなっているかというと

typedef struct _msgfilter { NMHDR nmhdr; UINT msg; _WPAD _wPad1; WPARAM wParam; _WPAD _wPad2; LPARAM lParam; } MSGFILTER;

nmhdrは例によってNMHDR構造体です。

というように定義されています。32ビット版では _WPADは無視します。

msgはエディットコントロールの(見えない)プロシージャに 行くメッセージです。(当然キーボード、マウス関係の) このときの副メッセージがwParam, lParamです。

通知メッセージは何回やってもわかりにくいと思いますので具体的に どうするか見てみましょう。

まず、親のプロシージャでWM_NOTIFYメッセージを捕まえるところは 良いでしょう。

.... case WM_NOTIFY: ..... break;

このメッセージのlParamがNMHDR構造体へのポインタになります。 したがってこの構造体のcodeメンバがEN_MSGFILTERとなるときの 処理を記述すればよいことになります。

case WM_NOTIFY: switch (((NMHDR *)lParam)->code) { case EN_MSGFILTER: ...... break; } break; }

という形になります。
さて、このなかで今回は右クリックのときを知りたいわけです。 EN_MSGFILTERメッセージのlParamはMSGFILTER構造体への ポインタでした。この構造体のメンバの中にmsgというのがあるので これがWM_RBUTTONDOWNかどうかを調べればよいということに なります。またこのときの副メッセージのlParamはクリックされた ところの座標を表していましたね。

case WM_NOTIFY: switch (((NMHDR*)lp)->code ) { case EN_MSGFILTER: pmf = (MSGFILTER *)lp; if (pmf->msg == WM_RBUTTONDOWN){ x = LOWORD(pmf->lParam); y = HIWORD(pmf->lParam); //やりたい処理 } break; } break;

という感じになります。ちょっと考えるとややこやしいのですが パターン化してしまいましょう。WM_RBUTTONDOWNメッセージ以外の キーボード・マウスメッセージをたくさん処理したいときは pfm->msgをswitch文で場合分けすればよいでしょう。

さらに注意すべきことはリッチエディットコントロールでは イベントマスクで通知メッセージがコントロールされています。 リッチエディットコントロールを作ったらすぐに

dwEvent = SendMessage(hEdit, EM_GETEVENTMASK, 0, 0); dwEvent |= ENM_MOUSEEVENTS; SendMessage(hEdit, EM_SETEVENTMASK, 0, (LPARAM)dwEvent);

のようにイベントマスクにENN_MOUSEEVENTを加えてください。 これについては後の章でもう少し詳しく解説します。

さて、前置きがすっかり長くなりましたが、今回のプログラムを 見てみましょう。

// rich02.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "編集(&E)" BEGIN MENUITEM "フォントの変更(&F)", IDM_FONT END END MYPOPUP MENU DISCARDABLE BEGIN POPUP "ダミーです" BEGIN MENUITEM "フォントの変更", IDM_FONT MENUITEM "終了", IDM_END END END

右クリックしたときに出てくるメニューを新規に作りました。 "MYPOPUP"というメニューです。これの「フォントの変更」 「終了」のシンボル値はクラスメニューと同じにしてある点に 注意してください。

// rich02.cpp #define STRICT #include <windows.h> #include <richedit.h>> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); BOOL SetInitialFont(HWND hWnd); BOOL SetMyFont(HWND); char szClassName[] = "rich02"; //ウィンドウクラス HINSTANCE hInst; DWORD dwMyMask; //CHARFORMATで使うマスク値 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)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; hWnd = CreateWindow(szClassName, "猫でもわかるRichEdit", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW,//ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL,//親ウィンドウのハンドル、親を作るときはNULL NULL,//メニューハンドル、クラスメニューを使うときはNULL hInstance,//インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

これも同じです。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static HINSTANCE hRtLib; static HWND hEdit; DWORD dwEvent; MSGFILTER *pmf; HMENU hMenu, hSub; int x, y; POINT pt; switch (msg) { case WM_CREATE: hRtLib = LoadLibrary("RICHED32.DLL"); hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "RICHEDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | WS_VSCROLL | WS_HSCROLL | ES_AUTOVSCROLL | ES_AUTOHSCROLL, 0, 0, 0, 0, //とりあえず幅、高さ0のウィンドウを作る hWnd, (HMENU)1, hInst, NULL); dwEvent = SendMessage(hEdit, EM_GETEVENTMASK, 0, 0); dwEvent |= ENM_MOUSEEVENTS; SendMessage(hEdit, EM_SETEVENTMASK, 0, (LPARAM)dwEvent); SetInitialFont(hEdit); break; case WM_NOTIFY: switch (((NMHDR*)lp)->code ) { case EN_MSGFILTER: pmf = (MSGFILTER *)lp; if (pmf->msg == WM_RBUTTONDOWN){ x = LOWORD(pmf->lParam); y = HIWORD(pmf->lParam); hMenu = LoadMenu(hInst, "MYPOPUP"); hSub = GetSubMenu(hMenu, 0); pt.x = (LONG)x; pt.y = (LONG)y; ClientToScreen(hEdit, &pt); TrackPopupMenu(hSub, TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, NULL); DestroyMenu(hMenu); } break; } break; case WM_SIZE: MoveWindow(hEdit, 0, 0, LOWORD(lp), HIWORD(lp), TRUE); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_FONT: SetMyFont(hEdit); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hEdit); DestroyWindow(hWnd); } break; case WM_DESTROY: if(FreeLibrary(hRtLib) == 0) MessageBox(NULL, "ライブラリ開放失敗", "Error", MB_OK); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

WM_NOTIFYのところを注意してみてください。 TrackPopupMenu関数については 第44章を参照してください。

BOOL SetInitialFont(HWND hEdit)//最初のフォントの設定 { CHARFORMAT cfm; memset(&cfm, 0, sizeof(CHARFORMAT)); cfm.cbSize = sizeof(CHARFORMAT); cfm.dwMask = CFM_BOLD | CFM_CHARSET | CFM_COLOR | CFM_FACE | CFM_ITALIC | CFM_SIZE | CFM_STRIKEOUT | CFM_UNDERLINE; dwMyMask = cfm.dwMask;//以後dwMyMaskの値を使う cfm.bCharSet = SHIFTJIS_CHARSET; strcpy(cfm.szFaceName, "MS ゴシック"); if (SendMessage(hEdit, EM_SETCHARFORMAT, SCF_SELECTION | SCF_WORD, (LPARAM)&cfm) == 0) { MessageBox(hEdit, "EM_SETCHARFORMAT失敗です", "Error", MB_OK); return FALSE; } return TRUE; } BOOL SetMyFont(HWND hEdit) { CHARFORMAT cfm; CHOOSEFONT cf; LOGFONT lf; HDC hDC; memset(&cf, 0, sizeof(CHOOSEFONT)); memset(&lf, 0, sizeof(LOGFONT)); cfm.cbSize = sizeof(CHARFORMAT); //今までの設定を取得してそれをCHOOSEFONT構造体に渡す SendMessage(hEdit, EM_GETCHARFORMAT, TRUE, (LPARAM)&cfm); hDC = GetDC(hEdit); lf.lfHeight = MulDiv(cfm.yHeight, GetDeviceCaps(hDC, LOGPIXELSY), -1440); ReleaseDC(hEdit, hDC); cfm.dwMask = dwMyMask; if (cfm.dwEffects & CFE_BOLD) lf.lfWeight = FW_BOLD; else lf.lfWeight = FW_NORMAL; if (cfm.dwEffects & CFE_ITALIC) lf.lfItalic = TRUE; if (cfm.dwEffects & CFE_UNDERLINE) lf.lfUnderline = TRUE; if (cfm.dwEffects & CFE_STRIKEOUT) lf.lfStrikeOut = TRUE; lf.lfCharSet = cfm.bCharSet; lf.lfQuality = DEFAULT_QUALITY; lf.lfPitchAndFamily = cfm.bPitchAndFamily; strcpy(lf.lfFaceName, cfm.szFaceName); cf.rgbColors = cfm.crTextColor; cf.lStructSize = sizeof(CHOOSEFONT); cf.hwndOwner = hEdit; cf.lpLogFont = &lf; cf.Flags = CF_SCREENFONTS | CF_EFFECTS | CF_INITTOLOGFONTSTRUCT; //フォント選択ダイアログを出して新しい設定を取得する if (ChooseFont(&cf)) { cfm.cbSize = sizeof(CHARFORMAT); cfm.dwMask = dwMyMask; cfm.yHeight = 2 * cf.iPointSize; cfm.dwEffects = 0; if (lf.lfWeight >= FW_BOLD) cfm.dwEffects |= CFE_BOLD; if (lf.lfItalic) cfm.dwEffects |= CFE_ITALIC; if (lf.lfUnderline) cfm.dwEffects |= CFE_UNDERLINE; if (lf.lfStrikeOut) cfm.dwEffects |= CFE_STRIKEOUT; cfm.crTextColor = (COLORREF)cf.rgbColors; cfm.bPitchAndFamily = lf.lfPitchAndFamily; cfm.bCharSet = lf.lfCharSet; strcpy(cfm.szFaceName, lf.lfFaceName); if(SendMessage(hEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cfm) == 0) { MessageBox(hEdit, "失敗です", "Error", MB_OK); return FALSE; } } SetFocus(hEdit); return TRUE; }

これは、前章と全く同じです。今回は通知メッセージの 復習でした!
エディットコントロールをサブクラス化して同様のプログラムを 作ってみてください。
[SDK第2部 Index] [総合Index] [Previous Chapter] [Next Chapter]

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