第130章 リッチテキストエディットコントロールの基礎


コモンコントロールのひとつに「リッチテキストエディットコントロール」 というのがあります。普通のエディットコントロールの機能強化版です。 いろいろなフォントを設定したり、段落書式の設定などができます。



今回はとりあえずフォントを変えるだけのプログラムを 作ってみます。

メニューの「編集」「フォントの変更」でフォントを変えます。

さて、リッチテキストエディットコントロールも他のコントロールのように 子供ウィンドウとして作ります。しかし、他のコモンコントロール と違ってComctl32.dllをリンクする必要はありません。 したがってInitCommonControls関数やcommctrl.hも不要です。

1.richedit.hをインクルードする 2.LoadLibrary("RICHED32.DLL");でRICHED32.DLLをロードする 3.CreateWindowEx関数で子供ウィンドウとしてコントロールを貼り付ける

ま、作るのは簡単です。が、これをコントロールするのは結構 面倒くさいです。とりあえず今回はフォントの変更のみできる ようにします。

リッチテキストエディットコントロールから現在の書式を 取得するにはEM_GETCHARFORMATメッセージを送ります。 セットするにはEM_SETCHARFORMATメッセージを送ります。

EM_GETCHARFORMAT wParam = (WPARAM) (BOOL) fSelection; lParam = (LPARAM) (CHARFORMAT FAR *) lpFmt;

fSelectionが0のときはデフォルトの文字書式が返されます。 それ以外のときは現在の選択範囲の書式が返されます。

lpFmtはCHARFORMAT構造体へのポインタで、これに 情報が返されます。

EM_SETCHARFORMAT wParam = (WPARAM) (UINT) uFlags; lParam = (LPARAM) (CHARFORMAT FAR *) lpFmt;

uFlags文字書式の適用範囲を示します。SCF_SELECTIONは 現在の選択範囲に書式を適用します。SCF_WORDは選択されている 単語に書式を適用します。これらから0個以上を選択します。

lpFmt文字書式を指定するCHARFORMAT構造体へのポインタです。

typedef struct _charformat { UINT cbSize; _WPAD _wPad1; DWORD dwMask; DWORD dwEffects; LONG yHeight; LONG yOffset; COLORREF crTextColor; BYTE bCharSet; BYTE bPitchAndFamily; CHAR szFaceName[LF_FACESIZE]; _WPAD _wPad2; } CHARFORMAT;

ヘルプによるとCHARFORMAT構造体の定義は上のようになっています。 しかし_wPad1と_wPad2メンバについてヘルプには何の解説もありません。 さらに_WPAD型もいったい何物なのか解説がありません。 それともこの構造体の定義が間違っていて最初からこのようなメンバは ないのでしょうか。

実は、richedit.hをよく読むと

#ifdef _WIN32 # define _WPAD /##/ #else # define _WPAD WORD #endif

というように定義されています。 32ビット版では_WPADは単に「//」(コメント)で意味はありません。 (##はトークン連結演算子です)

cbSizeはこの構造体のサイズです。

dwMaskはどのメンバを有効にするかを設定します。
CFM_BOLDはdwEffectsメンバにCFE_BOLDが含まれています。
CFM_COLORはcrTextColorおよび、dwEffectsのCFE_AUTOCOLORが有効です。
CFM_FACEはszFaceNameが有効です。
CFM_ITALICはdwEfftctsにCFE_ITALICが含まれています。
CFM_OFFSETはyOffsetが有効です。
CFM_PROTECTEDはdwEffectのCFE_PROTECTEDが有効です。
CFM_SIZEはyHeightが有効です。
CFM_STRIKEOUTはdwEffectsのCFE_STRIKEOUTが有効です。
CFM_UNDERLINEはdwEffectsのCFE_UNDERLINEが有効です。

dwEffectは文字の特殊効果を指定します。
CFE_AUTOCOLOR(色がCOLOR_WINDOWTEXTになる)、CFE_BOLD,CFE_ITALIC, CFE_STRIKEOUT,CFE_UNDERLINEはその名のとおりです。
CFE_PROTECTEDは文字が保護されていることを表します。

yHeightは文字の高さです。

yOffsetはベースラインからの文字のオフセットです。

crTextColorは文字の色です。

bCharSetはキャラクタセットです。

bPitchAndFamilyはフォントファミリととピッチを指定します。

szFaceNameはフォントの書体名を指定します。

これらのメンバを設定してEM_SETCHARFORMATメッセージを送ってやれば 文字書式が設定されます。EM_GETCHARFORMATを送るとこの構造体に 答えが返ってきます。

では、プログラムを見てみることにします。

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

ごく普通のメニューリソースです。

// rich01.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[] = "rich01"; //ウィンドウクラス 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; }

richedit.hをインクルードするのを忘れないでください。 今回はhInstをグローバル変数にしてあります。

//ウィンドウ・クラスの登録 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; 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); SetInitialFont(hEdit); 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_CREATEメッセージが着たらリッチテキストエディットコントロールを 作ります。この手のプログラムではよくある手法ですが まずコントロールを幅、高さとも0にしておきます。 どうせすぐにWM_SIZEメッセージが来るのでここで 大きさの調整をする必要はありません。 そして、最初のフォントを指定しておきます。 (自作関数SetInitialFont)

WM_SIZEメッセージが着たらエディットコントロールの 大きさを親のクライアント領域にぴったり重ねます。 GetClientRect関数を使っても良いのですが、 ここではWM_SIZEメッセージのLPARAMを利用しています。

メニューから「フォントの変更」(IDM_FONT)が選択されたら 自作関数SetMyFontを呼び出します。

ところでこのプログラムではリッチテキストエディット コントロールを親の子供として作っているので 親を終了すると自動的に子供も終了します。 ところが、実際に作ってみればわかりますが このコントロールは終了するのに時間がかかります。 そこで親のウィンドウを破棄する前にこのコントロール ウィンドウを破棄するようにすればすんなり終了 することができます。

さらに終了前にロードしたライブラリを開放します。 (FreeLibrary)

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; }

最初の文字書式を設定する関数です。cfm.dwMaskを一度設定したら この値をグローバル変数にコピーして以後この値を使うことにします。

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; }

現在の文字書式を取得して、フォント選択ダイアログボックスを 表示し、ユーザーの設定を反映させる関数です。

かなり面倒くさいですが中身はそんなに難しくないです。

cfm.dwEffectsにCFE_XXが含まれているかどうか 調べるにはcfm.dwEffects & CFE_XXの値を調べるとわかります。 含まれているとTRUE(0以外)含まれていないとFALSE(0)となります。

最もわかりにくいのがMulDivのところでしょう。

int MulDiv( int nNumber, int nNumerator, int nDenominator );

早い話がMulDiv(a, b, c)とあれば(a * b) / cのことです。

さてLOGFONT構造体のヘルプを見るとこの構造体に ポイント単位で高さを指定するには

lfHeight = -MulDiv(PintSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);

とせよ、と書いてあります。ところがリッチテキストエディットコントロール ではtwip単位を使います。(1point=20twip)したがって プログラムのような式になります。(まじめに考えると難しいので決まりきった 式として覚える)

ChooseFont関数については 第38章を参照してください。


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

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