第141章 通知メッセージの追加


リッチエディットコントロールの通知メッセージについては 第131章で簡単に説明しました。 今回はそれのちょっと補足です
今まで作ってきたRTFエディタで「切り取り」とか 「コピー」とか「貼りつけ」処理ができない時でも、ボタンが 使用可能でした。これを何とかしましょう。また、ステータスバーも付けてみました。



現在選択されている文字列がないので「コピー」「切り取り」ボタンは 使用不能です。また、クリップボードに利用可能データがあるので 「貼りつけボタン」は有効です。

ステータスバーには現在のフォント名とポイント表示がされていて便利になりました。 また、おまけで(というか仕方なく)時計も付けてみました。



さて、まずどうするかというと文字列の選択状態が変化したときに 選択文字列が空であるかどうかを調べて、空でなければ「コピー」 「切り取り」ボタンを有効にします。空なら無効にします。 選択範囲が変更なるとリッチエディットコントロールはその親に WM_NOTIFYの形でEN_SELCHANGEを送ってきます。これを捕まえて 処理をすれば良いですね。

EN_SELCHANGE wParam = (WPARAM) (UINT) uID; lParam = (LPARAM) (SELCHANGE FAR *) lpSelChange;

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

typedef struct _selchange { NMHDR nmhdr; CHARRANGE chrg; WORD seltyp; } SELCHANGE;

nmhdrはNMHDR構造体です。

chrgは新しい選択範囲を指定するCHARRANGE構造体です。

seltypは選択範囲のタイプです。
SEL_EMPTYは選択範囲が空です。
SEL_MULTICHARは複数の文字が選択されています。
SEL_MULTIOBJECTは複数のOLEオブジェクトが選択されています。
SEL_OBJECTはOLEオブジェクトが選択されています。
SEL_TEXTはテキストが選択されています。

ということでまず、EN_SELCHANGEを捕まえて CHARRANGE構造体のcpMinとcpMaxが同じかどうかを調べることにします。 もちろん、seltypeを調べてもかまいません。

ところで、リッチエディットコントロールの通知メッセージを 処理するときちょっとした注意が必要です。

そのままでは、メッセージか来ないのです。

第131章でも少し触れていますが リッチエディットコントロールのイベントマスクを変更しなくてはいけません。 多分,必要もない通知メッセージがどんどん来たらパフォーマンスが悪くなるので イベントマスクで制御しているのだと思います。しかし、これが忘れやすいのです。

あれ!?通知メッセージが来ないぞ!!!!

ということになって結構わかりにくいバグになります。
エディットコントロールを作ったらすぐに EM_GETEVENTMASKを送って現在のイベントマスクを取得します。 それにこれから捕まえたい通知メッセージのフラグを加えます。 そして、EN_SETEVENTMASKメッセージを送ってイベントマスクの 状態を変えます。

EM_GETEVENTMASK wParam = 0; // 使いません lParam = 0; // 使いません

戻り値としてDWORDのイベントマスクが取得できます。

EM_SETEVENTMASK wParam = 0; lParam = (LPARAM) (DWORD) dwMask;

dwMaskに新しいイベントマスクを指定します。

イベントマスクフラグには次のようなものがあります。

ENM_CHANGE, ENM_CORRECTTEXT, ENM_DROPFILES, ENM_KEYEVENTS, ENM_MOUSEEVENTS, ENM_PROTECTED, ENM_RESIZEREQUEST, ENM_SCROLL, ENM_SELCHANGE, ENM_UPDATE

さて、このうちENM_KEYEVENTSとENM_MOUSEEVENTSはEN_MSGFILTERを 送るようにします。それ以外はENMをENに変えた通知を送るようになります。

次に、クリップボードに利用可能メッセージがあるかどうかは どのタイミングで調べれば良いのでしょうか。 クリップボードには自分のところからだけデータが行くわけではありません。 自分のところで何も変化がなくても、他のアプリケーションが クリップボードにデータを送っているかもしれません。 これでは、通知メッセージを監視していても多分だめでしょう。 ここでは、安直にSetTimer関数を使って一定時間ごとに調べることにしました。 (本来はきっとクリップボードビューアを作るときのような操作が必要なのでしょう。) さて、Windows95/98に付属のクリップボードビューアを出してクリップボードの データを削除してみます。VC++の「貼りつけ」ボタンを観察していると データを削除してかなり時間がたってからボタンが無効になります。 今回作るプログラムではデータが削除されると0.5秒以内に ボタンが無効になります。

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

今回はリソーススクリプトに変更はないので省略します。

// rich12.cpp 省略 #define ID_STATUS 101 #define ID_MYTIMER 1000 省略 HWND MakeMyStatusbar(HWND hWnd); void SetStatusFontInfo(HWND, HWND); BOOL IsEditButtonAvailable(HWND, HWND); BOOL IsPasteButtonAvailable(HWND, HWND); void SetStatusClock(HWND); 省略 char szClassName[] = "rich12";//ウィンドウクラス 省略

今回作るステータスバーのIDとか 関数のプロトタイプです。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) BOOL InitApp(HINSTANCE hInst) BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)

これは,今までと同じなので省略します。

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static HINSTANCE hRtLib; static HWND hEdit, hTool, hStatus; DWORD dwEvent; MSGFILTER *pmf; HMENU hMenu, hSub; int x, y; POINT pt; int nToolH, nStatusH; RECT rc; switch (msg) { case WM_CREATE: InitCommonControls(); hTool = MakeMyToolbar(hWnd); hStatus = MakeMyStatusbar(hWnd); CheckButtonState(hTool); hRtLib = LoadLibrary("RICHED32.DLL"); hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "RICHEDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | WS_HSCROLL | WS_VSCROLL | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_NOHIDESEL, 0, 0, 0, 0, //とりあえず幅、高さ0のウィンドウを作る hWnd, (HMENU)1, hInst, NULL); dwEvent = SendMessage(hEdit, EM_GETEVENTMASK, 0, 0); dwEvent |= ENM_MOUSEEVENTS | ENM_SELCHANGE; SendMessage(hEdit, EM_SETEVENTMASK, 0, (LPARAM)dwEvent); SetInitialFont(hEdit); //リッチエディットコントロールをWYSIWYGの幅にする //要するにプリンタにセットしてある用紙の幅に合わせる RTF_SetWYSIWYG(hEdit); SetStatusFontInfo(hStatus, hEdit); IsEditButtonAvailable(hEdit, hTool); IsPasteButtonAvailable(hEdit, hTool); SetTimer(hWnd, ID_MYTIMER, 500, NULL); break; case WM_TIMER: IsPasteButtonAvailable(hEdit, hTool); SetStatusClock(hStatus); break; case WM_NOTIFY: switch (((NMHDR*)lp)->code ) { case EN_SELCHANGE: SetStatusFontInfo(hStatus, hEdit); //「切り取り」「コピー」が可能かどうか調査 IsEditButtonAvailable(hEdit, hTool); // 「張りつけ」可能かどうか IsPasteButtonAvailable(hEdit, hTool); break; 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: GetWindowRect(hTool, &rc); nToolH = rc.bottom - rc.top; GetWindowRect(hStatus, &rc); nStatusH = rc.bottom - rc.top; MoveWindow(hEdit, 0, nToolH, LOWORD(lp), HIWORD(lp) - (nToolH + nStatusH), TRUE); SendMessage(hTool, WM_SIZE, wp, lp); SendMessage(hStatus, WM_SIZE, wp, lp); SetFocus(hEdit); break; case WM_INITMENUPOPUP: RTF_CheckMenu(hEdit, (HMENU)wp); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_ALL: RTF_All(hEdit); break; case IDM_FONT: SetMyFont(hEdit); SetStatusFontInfo(hStatus, hEdit); break; case IDM_CENTER: SetCenter(hEdit); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_CENTER, (LPARAM)MAKELONG(TBSTATE_PRESSED, 0)); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_LEFT, (LPARAM)MAKELONG(TBSTATE_ENABLED, 0)); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_RIGHT, (LPARAM)MAKELONG(TBSTATE_ENABLED, 0)); break; case IDM_LEFT: SetLeft(hEdit); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_LEFT, (LPARAM)MAKELONG(TBSTATE_PRESSED, 0)); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_CENTER, (LPARAM)MAKELONG(TBSTATE_ENABLED, 0)); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_RIGHT, (LPARAM)MAKELONG(TBSTATE_ENABLED, 0)); break; case IDM_RIGHT: SetRight(hEdit); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_RIGHT, (LPARAM)MAKELONG(TBSTATE_PRESSED, 0)); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_LEFT, (LPARAM)MAKELONG(TBSTATE_ENABLED, 0)); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_CENTER, (LPARAM)MAKELONG(TBSTATE_ENABLED, 0)); break; case IDM_NEW: RTF_New(hEdit); break; case IDM_SAVE: RTF_Save(hEdit); break; case IDM_SAVEAS: RTF_SaveAs(hEdit); break; case IDM_OPEN: RTF_Open(hEdit); break; case IDM_COPY: SendMessage(hEdit, WM_COPY, 0, 0); break; case IDM_CUT: SendMessage(hEdit, WM_CUT, 0, 0); break; case IDM_PASTE: SendMessage(hEdit, WM_PASTE, 0, 0); break; case IDM_UNDO: SendMessage(hEdit, WM_UNDO, 0, 0); // SendMessage(hEdit, EM_SETMODIFY, (WPARAM)FALSE, 0); break; case IDM_PRINT: RTF_Print(hEdit); break; case IDM_PRNSET: PrinterSet(hEdit); RTF_SetWYSIWYG(hEdit); break; case IDM_VERTICAL: RTF_Vertical(hEdit); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_VERTICAL, (LPARAM)MAKELONG(TBSTATE_PRESSED, 0)); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_HORIZONTAL, (LPARAM)MAKELONG(TBSTATE_ENABLED, 0)); break; case IDM_HORIZONTAL: RTF_Horizontal(hEdit); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_HORIZONTAL, (LPARAM)MAKELONG(TBSTATE_PRESSED, 0)); SendMessage(hTool, TB_SETSTATE, (WPARAM)IDM_VERTICAL, (LPARAM)MAKELONG(TBSTATE_ENABLED, 0)); break; case IDM_BACKCOLOR: RTF_BackColor(hEdit); break; } break; case WM_CLOSE: if (SendMessage(hEdit, EM_GETMODIFY, 0, 0)) { id = MessageBox(hWnd, "文書が変更されています。保存しますか。", "注意!", MB_YESNO); if (id == IDYES) RTF_Save(hEdit); } id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hEdit); DestroyWindow(hWnd); } else SetFocus(hEdit); break; case WM_DESTROY: if(KillTimer(hWnd, ID_MYTIMER) == 0) MessageBox(hWnd, "タイマーを正常に殺せませんでした", "Error", MB_OK | MB_ICONHAND); if(FreeLibrary(hRtLib) == 0) MessageBox(NULL, "ライブラリ開放失敗", "Error", MB_OK); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

前回はInitCommonControls()関数はMakeMyToolbar関数の中で 呼んでいましたが、今回はステータスバーも作るので WM_CREATEの最初のところで呼ぶことにしました。

リッチエディットコントロールを作るとすぐにイベントマスクを 変えてENM_SELCHANGEを加えました。

SetStatusFontInfo, IsEditButtonAvailable, IsPasteButtonAvailable は自作関数でステータスバーにフォントを表示したり、 ボタンが使用可能かどうか調べて処理をする関数です(後述)

そして、SetTimer関数でWM_TIMERメッセージを発生させます。

WM_TIMERメッセージが来たらIsPasteButtonAvailable関数で ペースト可能かどうか調べてボタンを有効にしたり無効にしたりしています。 また、せっかくタイマーを使うのでステータスバーにSetStatusClock 関数(自作)で時計表示をさせます。

さて、EN_SELCHANGEが来たら自作のSetStatusFontInfo関数で ステータスバーにフォント情報を表示します。 また、「切り取り」「コピー」ボタンの使用可否を決めます。 「貼りつけ」ボタンについてはWM_TIMERで監視しているので ここでは調べなくても良いのですが一応ここでも調べてみることにしました。

さて、前回まではIDM_UNDOのところでWM_UNDOメッセージを送った後 EM_SETMODIFYメッセージを送っていましたが、次のような不都合が起こるので やめました(コメントにしてあります)。
文書に変更があっても、たとえば「揃え」を変化させてUNDOを 実行。このあと終了しようとしても保存を促すメッセージが出ません。

アプリケーション終了時に忘れずにタイマーを殺してください。

次に変更のあった関数と新しく作った関数のみを示します。

HWND MakeMyToolbar(HWND hWnd) { HWND hTool; HINSTANCE hInst; TBADDBITMAP tab; HBITMAP hBmp; int nIndex, i; hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); // InitCommonControls(); hTool = CreateToolbarEx(hWnd, WS_CHILD | WS_BORDER | WS_VISIBLE, ID_TOOLBAR, 8, (HINSTANCE)HINST_COMMCTRL, IDB_STD_SMALL_COLOR, tbBut, 8, 0, 0, 0, 0, sizeof(TBBUTTON)); hBmp = CreateMappedBitmap(hInst, ID_MYTOOLBAR, 0, NULL, 0); tab.hInst = NULL; tab.nID = (UINT)hBmp; nIndex = SendMessage(hTool, TB_ADDBITMAP, 7, (LPARAM)&tab); for (i = 0; i <= 6; i++) tbBmp[i].iBitmap += nIndex; SendMessage(hTool, TB_ADDBUTTONS, 7, (LPARAM)&tbBmp[0]); SendMessage(hTool, TB_INSERTBUTTON, 0, (LPARAM)&tbSep); SendMessage(hTool, TB_INSERTBUTTON, 4, (LPARAM)&tbSep); SendMessage(hTool, TB_INSERTBUTTON, 6, (LPARAM)&tbSep); SendMessage(hTool, TB_INSERTBUTTON, 10, (LPARAM)&tbSep); SendMessage(hTool, TB_INSERTBUTTON, 12, (LPARAM)&tbSep); SendMessage(hTool, TB_INSERTBUTTON, 15, (LPARAM)&tbSep); SendMessage(hTool, TB_INSERTBUTTON, 19, (LPARAM)&tbSep); return hTool; }

最初にも書いたようにInitCommonControls関数は WM_CREATEのところで直接呼んでいるのでここでは コメントにしました。

HWND MakeMyStatusbar(HWND hWnd) { HWND hStatus; static int nParts[4] = {100, 170, 250, -1}; hStatus = CreateStatusWindow(WS_CHILD | WS_VISIBLE | CCS_BOTTOM | SBARS_SIZEGRIP, "test", hWnd, ID_STATUS); SendMessage(hStatus, SB_SETPARTS, (WPARAM)4, (LPARAM)nParts); return hStatus; }

ステータスバーを作る関数です。 ステータスバーについては第57章を参照してください。

void SetStatusFontInfo(HWND hStatus, HWND hEdit) { CHARFORMAT cf; char szFontSize[32]; memset(&cf, 0, sizeof(CHARFORMAT)); cf.cbSize = sizeof(CHARFORMAT); SendMessage(hEdit, EM_GETCHARFORMAT, (WPARAM)TRUE, (LPARAM)&cf); SendMessage(hStatus, SB_SETTEXT, (WPARAM)0, (LPARAM)cf.szFaceName); wsprintf(szFontSize, "%d", cf.yHeight / 20); strcat(szFontSize, " point"); SendMessage(hStatus, SB_SETTEXT, (WPARAM)1, (LPARAM)szFontSize); return; }

EM_GETCHARFORMATメッセージを送ってフォントを調べています。 そしてステータスバーに表示しています。 CHARFORMAT構造体のyHeightを20で割った値がポイントになります。 (第130章参照)

BOOL IsEditButtonAvailable(HWND hEdit, HWND hTool) { CHARRANGE cr; SendMessage(hEdit, EM_EXGETSEL, 0, (LPARAM)&cr); if (cr.cpMin == cr.cpMax){ SendMessage(hTool, TB_ENABLEBUTTON, IDM_CUT, FALSE); SendMessage(hTool, TB_ENABLEBUTTON, IDM_COPY, FALSE); return FALSE; } else { SendMessage(hTool, TB_ENABLEBUTTON, IDM_CUT, TRUE); SendMessage(hTool, TB_ENABLEBUTTON, IDM_COPY, TRUE); return TRUE; } }

これは,特に説明の必要はないですね。

BOOL IsPasteButtonAvailable(HWND hEdit, HWND hTool) { if(SendMessage(hEdit, EM_CANPASTE, 0, 0)) { SendMessage(hTool, TB_ENABLEBUTTON, IDM_PASTE, TRUE); return TRUE; } else { SendMessage(hTool, TB_ENABLEBUTTON, IDM_PASTE, FALSE); return FALSE; } }

これも読めばわかるでしょう。

void SetStatusClock(HWND hStatus) { SYSTEMTIME st; char szClock[256]; char *str_org = "%02d : %02d : %02d"; GetLocalTime(&st); wsprintf(szClock, str_org, st.wHour, st.wMinute, st.wSecond); SendMessage(hStatus, SB_SETTEXT, (WPARAM)2, (LPARAM)szClock); return; }

これは、SetTimerを使うので急遽おまけで作った関数です。 GetLocalTime関数関係については 第10章を見てください。
[SDK第2部 Index] [総合Index] [Previous Chapter] [Next Chapter]

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