現在選択されている文字列がないので「コピー」「切り取り」ボタンは
使用不能です。また、クリップボードに利用可能データがあるので
「貼りつけボタン」は有効です。
ステータスバーには現在のフォント名とポイント表示がされていて便利になりました。 また、おまけで(というか仕方なく)時計も付けてみました。
さて、まずどうするかというと文字列の選択状態が変化したときに
選択文字列が空であるかどうかを調べて、空でなければ「コピー」
「切り取り」ボタンを有効にします。空なら無効にします。
選択範囲が変更なるとリッチエディットコントロールはその親に
WM_NOTIFYの形でEN_SELCHANGEを送ってきます。これを捕まえて
処理をすれば良いですね。
uIDはリッチエディットコントロールのIDです。EN_SELCHANGE wParam = (WPARAM) (UINT) uID; lParam = (LPARAM) (SELCHANGE FAR *) lpSelChange;
nmhdrはNMHDR構造体です。typedef struct _selchange { NMHDR nmhdr; CHARRANGE chrg; WORD seltyp; } SELCHANGE;
chrgは新しい選択範囲を指定するCHARRANGE構造体です。
seltypは選択範囲のタイプです。
SEL_EMPTYは選択範囲が空です。
SEL_MULTICHARは複数の文字が選択されています。
SEL_MULTIOBJECTは複数のOLEオブジェクトが選択されています。
SEL_OBJECTはOLEオブジェクトが選択されています。
SEL_TEXTはテキストが選択されています。
ということでまず、EN_SELCHANGEを捕まえて CHARRANGE構造体のcpMinとcpMaxが同じかどうかを調べることにします。 もちろん、seltypeを調べてもかまいません。
ところで、リッチエディットコントロールの通知メッセージを 処理するときちょっとした注意が必要です。
戻り値としてDWORDのイベントマスクが取得できます。EM_GETEVENTMASK wParam = 0; // 使いません lParam = 0; // 使いません
dwMaskに新しいイベントマスクを指定します。EM_SETEVENTMASK wParam = 0; lParam = (LPARAM) (DWORD) dwMask;
イベントマスクフラグには次のようなものがあります。
さて、このうちENM_KEYEVENTSとENM_MOUSEEVENTSはEN_MSGFILTERを 送るようにします。それ以外はENMをENに変えた通知を送るようになります。ENM_CHANGE, ENM_CORRECTTEXT, ENM_DROPFILES, ENM_KEYEVENTS, ENM_MOUSEEVENTS, ENM_PROTECTED, ENM_RESIZEREQUEST, ENM_SCROLL, ENM_SELCHANGE, ENM_UPDATE
次に、クリップボードに利用可能メッセージがあるかどうかは どのタイミングで調べれば良いのでしょうか。 クリップボードには自分のところからだけデータが行くわけではありません。 自分のところで何も変化がなくても、他のアプリケーションが クリップボードにデータを送っているかもしれません。 これでは、通知メッセージを監視していても多分だめでしょう。 ここでは、安直にSetTimer関数を使って一定時間ごとに調べることにしました。 (本来はきっとクリップボードビューアを作るときのような操作が必要なのでしょう。) さて、Windows95/98に付属のクリップボードビューアを出してクリップボードの データを削除してみます。VC++の「貼りつけ」ボタンを観察していると データを削除してかなり時間がたってからボタンが無効になります。 今回作るプログラムではデータが削除されると0.5秒以内に ボタンが無効になります。
では、プログラムを見てみましょう。
今回はリソーススクリプトに変更はないので省略します。
今回作るステータスバーのIDとか 関数のプロトタイプです。// 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";//ウィンドウクラス 省略
これは,今までと同じなので省略します。int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) BOOL InitApp(HINSTANCE hInst) BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
前回はInitCommonControls()関数はMakeMyToolbar関数の中で 呼んでいましたが、今回はステータスバーも作るので WM_CREATEの最初のところで呼ぶことにしました。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; }
リッチエディットコントロールを作るとすぐにイベントマスクを 変えてENM_SELCHANGEを加えました。
SetStatusFontInfo, IsEditButtonAvailable, IsPasteButtonAvailable は自作関数でステータスバーにフォントを表示したり、 ボタンが使用可能かどうか調べて処理をする関数です(後述)
そして、SetTimer関数でWM_TIMERメッセージを発生させます。
WM_TIMERメッセージが来たらIsPasteButtonAvailable関数で ペースト可能かどうか調べてボタンを有効にしたり無効にしたりしています。 また、せっかくタイマーを使うのでステータスバーにSetStatusClock 関数(自作)で時計表示をさせます。
さて、EN_SELCHANGEが来たら自作のSetStatusFontInfo関数で ステータスバーにフォント情報を表示します。 また、「切り取り」「コピー」ボタンの使用可否を決めます。 「貼りつけ」ボタンについてはWM_TIMERで監視しているので ここでは調べなくても良いのですが一応ここでも調べてみることにしました。
さて、前回まではIDM_UNDOのところでWM_UNDOメッセージを送った後
EM_SETMODIFYメッセージを送っていましたが、次のような不都合が起こるので
やめました(コメントにしてあります)。
文書に変更があっても、たとえば「揃え」を変化させてUNDOを
実行。このあと終了しようとしても保存を促すメッセージが出ません。
アプリケーション終了時に忘れずにタイマーを殺してください。
次に変更のあった関数と新しく作った関数のみを示します。
最初にも書いたようにInitCommonControls関数は WM_CREATEのところで直接呼んでいるのでここでは コメントにしました。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; }
ステータスバーを作る関数です。 ステータスバーについては第57章を参照してください。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; }
EM_GETCHARFORMATメッセージを送ってフォントを調べています。 そしてステータスバーに表示しています。 CHARFORMAT構造体のyHeightを20で割った値がポイントになります。 (第130章参照)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; }
これは,特に説明の必要はないですね。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; } }
これは、SetTimerを使うので急遽おまけで作った関数です。 GetLocalTime関数関係については 第10章を見てください。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; }
Update 31/Aug/1998 By Y.Kumei