リッチエディットコントロールを作成してみる ACT-1

リッチエディットコントロール

今回は、リッチエディットコントロールに挑戦してみたいと思います。

このリッチエディットコントロールも、Windows が提供するコントロールの1つになります。機能は、通常のエディットボックス(HSPの mesbox 命令や input 命令で作成されるオブジェクト)に似ていますが、文字の一部だけのフォントの種類や大きさ、色を変えたり、段落ごとに書式設定したりもできます。

今回はとりあえずコントロールを作成して、文字書式を設定してみることにします。コントロールの作成手順は以下のようになります。

  1. ll_libload 命令で RICHED32.DLL をロードする。
  2. CreateWindowEx 関数で、ウィンドウクラス名を "RichEdit" としてコントロールを作成する。
  3. CHARFORMAT 構造体に書式情報を格納して、コントロールに EM_SETCHARFORMAT メッセージを送信する。

RICHED32.DLL のロード

まずは RICHED32.DLL をロードする必要があります。リッチエディットコントロールはこのDLLから提供されている機能であり、DLLをロードすることで自動的にコントロールのウィンドウクラスがシステムに登録されるようになっています。DLLをロードするには LOADLIB.DLL の ll_libload 命令を使います。この操作は最初に1度だけ実行しておけば OK です。

コントロールの作成

次は CreateWindowEx 関数を呼び出して実際にウィンドウを作成します。リッチエディットコントロールはクラス名 "RichEdit" として登録されているので、これをクラス名に指定する必要があります。

リッチエディットコントロールのスタイルはさまざまなものがあります。縦書きすら行なうことができたりします。今回作成するコントロールには以下のスタイルを適用することにします。

スタイル コード 意味
WS_CHILD 0x40000000 子ウィンドウ
WS_VISIBLE 0x10000000 可視
WS_BORDER 0x00800000 境界線あり
WS_VSCROLL 0x00200000 垂直スクロールバーを持つ
ES_MULTILINE 0x0004 複数行のエディットコントロール
ES_AUTOVSCROLL 0x0040 テキストを自動的に縦向きにスクロール

今回作成するものでは、テキストが右端まできたら次の行に移るようにします。右端まできても次の行には移らずに、そのまま横へスクロールするようにするには、上のスタイルにさらに WS_HSCROLL (0x00100000) と ES_AUTOHSCROLL (0x0080) を加えてみましょう。他にもいろいろなスタイルを試してみるといいでしょう。

リッチエディットコントロールを作成する際に注意しなくてはならないのは、 CreateWindowEx 関数の10番目の引数として指定されるコントロールIDです。HSPウィンドウ上に、コントロールIDがある値以下のリッチエディットコントロールを作成すると、操作中に動作がおかしくなったり強制終了してしまったりすることが判明しています。筆者の環境では、コントロールIDを 1536 ($600) 未満にした場合に、コントロールにフォーカスを合わせると強制終了してしまいます。したがって、コントロールIDはある程度大きな値をとる必要があります。

このため、古いバージョンのLLMODモジュールでは _makewnd 命令でコントロールを作成することはできず、 dllproc などで直接 CreateWindowEx 関数を呼び出さなければなりません。

新しいバージョンのLLMOD モジュール(ver 1.11b 以降) では、この問題に対しての対策がとられたようで、 _makewnd 命令でリッチエディットコントロールを作成しても大丈夫なようになっています。

文字書式の設定

コントロールの作成ができたら次は文字の書式を設定します。まずは CHARFORMAT 構造体に書式情報を格納します。

typedef struct _charformat {
    UINT  cbSize;           // 構造体のサイズ(=60)
    DWORD dwMask;           // 有効メンバ
    DWORD dwEffects;        // 文字の効果
    LONG  yHeight;          // 文字の高さ
    LONG  yOffset;          // ベースラインからのオフセット
    COLORREF  crTextColor;  // 文字の色
    BYTE  bCharSet;         // キャラクタセット
    BYTE  bPitchAndFamily;  // フォントファミリとピッチ
    TCHAR szFaceName[32];   // フォント名
} CHARFORMAT;

cbSize メンバにはこの構造体のサイズである60を指定します。

dwMask メンバは、この構造体のどのメンバや属性設定を有効にするかを指定します。ここで指定しないとその属性の設定が変更されないので注意してください。

yHeight メンバおよび yOffset メンバにはそれぞれ文字の高さ、ベースラインからのオフセットを指定しますが、このときの数値は twip という単位で指定しなければなりません。これに付いては後で述べることにします。

crTextColor メンバに設定する色は、RGB値(赤の輝度を0xRR、緑の輝度を0xGG、青の輝度を0xBBとしたとき、0x00BBGGRR で表される値。COLORREF値)で指定します。

bCharSet メンバはキャラクタセットの値を設定します。日本語フォントでは普通 Shift-JIS を表す 128 (SHIFTJIS_CHARSET) を指定します。

szFaceName メンバにはフォント名を格納します。このメンバのデータは poke または wpoke で以下のようにして格納することができます。

(w)poke cfm, 26, "MS ゴシック"

ところで、この構造体、UINTDWORDLONGCOLORREF はそれぞれ4バイト、BYTECHAR はそれぞれ1バイトのデータ型だから、計算すれば構造体のサイズは58になるはず、と思うかもしれませんが、実際のサイズは60になっています。これは、構造体のアライメント(境界の整列)処理のために、構造体の境界が4バイト単位に配置されていることによって起こります。実際に、58を指定すると後でメッセージを送信したときにエラーになってしまいます。


さて、構造体に情報をセットしたら、リッチエディットコントロールに EM_SETCHARFORMAT メッセージを送信します。

#define EM_SETCHARFORMAT       0x0444

EM_SETCHARFORMAT
    wParam = uFlags;
    lParam = lpFormat;

uFlags パラメータには、設定する書式の適用範囲を指定します。デフォルトの書式として設定するには 0 を、選択中の文字列に対して適用するには 0x0001 (SCF_SELECTION) を、コントロール中にあるすべての文字列に対して適用するには 0x0004 (SCF_ALL) を指定します。

pFormat パラメータには、書式情報を格納した CHARFORMAT 構造体のアドレスを指定します。

フォントサイズの単位について

さて、 CHARFORMAT 構造体では、高さなどを指定するのに“twip”という単位を使用しなければならないと説明しました。

通常、フォントの大きさの単位として、“ポイント”というものが使われます。1ポイントは 0.013837インチと等しく、一般的には近似して 1/72 インチとします。1 twip は 1/20 ポイントと等しい長さの単位です。すなわち、

1 point = 1/72 inch
1 twip  = 1/20 point
1 inch  = 72 points = 1440 twips

という関係式が成り立ちます。

1つ注意しておくべきことは、“ポイント”という言葉が「点」という意味を持つことから、何となくこのポイント数を画面におけるピクセル数とみなしてしまうかもしれませんが、それは間違いです。“ポイント”と“ピクセル”とはまったく別の単位になります。

HSPの font 命令を使ってフォントサイズを指定するとき、このサイズはピクセル数で指定しますが、リッチエディットでは直接ピクセル数でサイズ指定することができません。したがって、フォントサイズをピクセル数で指定したい場合には、単位換算をする必要があります。これには、まず、“インチ”と“ピクセル”との間の関係を求めます。この関係は GetDeviceCaps 関数で求めることができます。

int GetDeviceCaps(
    HDC  hDC,     // デバイスコンテキストのハンドル
    int  nIndex   // 項目の種類
);

第1引数(hDC パラメータ)にはデバイスコンテキストのハンドルを、第2引数(nIndex パラメータ)には 90 (LOGPIXELSY) を指定します。関数の戻り値として、垂直方向の1インチあたりのピクセル数が返ります。

ここで、 GetDeviceCaps 関数を使うにはリッチエディットコントロールのデバイスコンテキストのハンドルが必要になるので、 GetDeviceCaps 関数を呼び出す前に GetDC 関数で取得しておく必要があります。また、 GetDeviceCaps 関数呼出し後は ReleaseDC 関数でこのハンドルを解放します。

さて、上記の方法で呼び出した GetDeviceCaps 関数の戻り値(垂直方向の1インチあたりのピクセル数)を u とすると、

1 inch = u pixels

すなわち、

1 pixel = 1/u inch = 1440/u twips

となります。したがって、高さ n ピクセルのフォントは

n pixels = n * 1440 / u twips

となるので、この値を高さとして指定します。

サンプルスクリプト

さて、実際にスクリプトを書いてみます。フォントを“MS ゴシック”に、フォントの高さは 26 ピクセルに設定します。また、文字の色を赤にします。

サンプルスクリプト main.as

    #include "llmod.as"

    ; RICHEDIT.DLLのロード
    ll_libload hRtLib, "RICHED32.DLL"

    ; リッチエディットコントロールの作成
    mref bmscr, 67              ; 描画中ウィンドウのBMSCR構造体
    classname = "RichEdit"
    pm.0 = 0
    getptr pm.1, classname
    pm.2 = 0
    pm.3 = 0x50A00044           ; WS_CHILD | WS_VISIBLE | WS_BORDER
                                ;  | WS_VSCROLL | ES_MULTILINE
                                ;  | ES_AUTOVSCROLL
    pm.4 = 0, 0, winx, winy
    pm.8 = bmscr.13             ; 描画中HSPウィンドウのハンドル
    pm.9 = 0xFF00               ; コントロールID (大きめに指定)
    _get_instance pm.10         ; インスタンスハンドル取得
    pm.11 = 0
    dllproc "CreateWindowExA", pm, 12, D_USER
    hEdit = dllret              ; リッチエディットのハンドル

    ; 文字書式を設定
    ; RichEditのデバイスコンテキスト取得
    pm = hEdit
    dllproc "GetDC", pm, 1, D_USER
    hdcEdit = dllret

    ; RichEditのy方向の1インチ当たりのピクセル数を取得
    pm = hdcEdit, 90             ; LOGPIXELSY
    dllproc "GetDeviceCaps", pm, 2, D_GDI
    pxl = dllret                 ; 1インチ当たりのピクセル数

    ; デバイスコンテキスト解放
    pm = hEdit, hdcEdit
    dllproc "ReleaseDC", pm, 2, D_USER

    ; CHARFORMAT構造体
    cfm.0 = 60
    cfm.1 = 0xE8000000               ; CFM_CHARSET | CFM_FACE
                                     ;  | CFM_SIZE | CFM_COLOR
    cfm.3 = 26 * 1440 / pxl          ; 26ピクセル(twip単位で指定)
    cfm.5 = $0000FF                  ; 文字色(赤)
    poke cfm, 24, 128                ; SHIFTJIS_CHARSET
    poke cfm, 26, "MS ゴシック"    ; フォント名

    ; EM_SETCHARFORMAT メッセージ送信
    pm = hEdit, 0x0444
    pm.2 = 0                     ; SCF_DEFAULT
    getptr pm.3, cfm             ; CHARFORMAT構造体のアドレス
    sendmsg pm
    if dllret == 0 : dialog "書式の設定に失敗しました。", 1, "エラー"

    stop