ホーム >プログラム >Delphi 6 ローテクTips

VCLのTRichEditでUnicode

Unicode対応のRichEditを利用することなくDelphi 6のVCLのTRichEditそのままでUnicodeを扱おうというページ。


参考サイト


始めに

Delphi 6のVCLのTRichEditは実はそのままでUnicodeに対応していたりします。
少なくともXPのSP3では対応になっています。
ということは、Unicode対応のRichEditを利用することなく(かつトリッキーな小手先の技を使うまでもなく)Unicodeの入力ができてしまうということです。
とはいえTRichEditのLinesプロパティはUnicode対応ではないのでテキストの取得や設定にはRichEditにSendMessaageWで直接メッセージを送るという方法が必要になります。

試してみる

  1. フォームにTRichEditを貼り付けて実行。
  2. FF と入力。
  3. FF を選択状態にする。
  4. Alt+Xを押す。

これで選択状態だった FF が ÿ に変わります。

FF と入力

FF を選択状態に。

Alt+Xキーを押す。
選択状態だった FF が ÿ に変わります。
もし変わらなければその環境でのTRichEditはUnicode対応ではないのでこのページの技は使えません。

これはリッチエディットコントロールの持つ変換機能を利用してUnicodeな文字を入力するやり方です。
この他にもIMEパッドの文字一覧などから入力することもできます。
つまりVCLのTRichEditはそのままでUnicodeな文字の入力に対応しているということです。

  1. 変換された ÿ を選択状態にします。
  2. Ctrl+C を押してクリップボードへコピーします。
  3. メモ帳を開きます。
  4. 「編集」→「貼り付け」を行います。

メモ帳に ÿ が貼り付けられたと思います。
Unicodeな文字をクリップボードへ送ることも可能です。

  1. Unicodeな文字を残して適当に編集します。
  2. メモ帳の「ファイル」→「名前を付けて保存」で文字コードを「UTF-8」にして保存します。
  3. RichEdit1.Lines.LoadFromFileで保存したファイルを読み込みます。

適当に「テスト」を付け足してみました。

文字コードをUTF-8にしてで保存します。

RichEdit1.LoadFromFileで保存したUnicode文書を読み込みます。
ちゃんと読み込めているのが分かります。

メモ帳で保存したUnicodeな文書ファイルをTRichEditコントロールはきちんと読み込めるということが分かると思います。
つまりVCLのTRichEditはそのままで(UTF-8のBOMつきの)Unicodeなファイルに対応しているということです。

procedure TForm1.Button1Click(Sender: TObject);
begin
  if (OpenDialog1.Execute) then begin
    RichEdit1.Lines.LoadFromFile(OpenDialog1.FileName);
  end;
end;

UTF-8のBOM付きなファイルであればこれだけでOKです。
ただしBOMなしのUTF-8ファイルはUTF-8としてではなくShift_JISとして処理してしまうようでUnicodeな文字だけでなく漢字などの2バイト文字も化けてしまいます。


問題点

まともな回避策があります。

これで何もかも万々歳かというとそうではありません。
まずクリップボードからのUnicodeな文字の貼り付けは化けます。
またRichEdit1.Lines.SaveToFileを行うとUnicodeな文字はやはり化けます。
もちろんShift-JISな文字は化けませんがUnicodeな文字は代替文字に変わってしまうか「?」に変わってしまいます。

対応策

ファイルへ保存

Unicodeな文字を含んだテキストをSaveToFileでファイルへ書き出すと文字化けしてしまうのですが逃げ道はあります。
クリップボードへはちゃんとUnicodeな文字を送れるのだからクリップボードを介在させてファイルへ書き出せば良いわけです。

まずUnicode対応のクリップボード関数を作ります。

function gfnsStrFromClipboard: WideString;
//クリップボードの文字列を取得して返す。
var
  li_Format: array[0..1] of Integer;
  li_Text: Integer;
  lh_Clip, lh_Data: THandle;
  lp_Clip, lp_Data: Pointer;
begin
  Result := '';
  li_Format[0] := CF_UNICODETEXT;
  li_Format[1] := CF_TEXT;
  li_Text := GetPriorityClipboardFormat(li_Format, 2);
  if (li_Text > 0) then
  begin
    if (OpenClipboard(Application.Handle)) then
    begin
      lh_Clip := GetClipboardData(li_Text);
      if (lh_Clip <> 0) then
      begin
        lh_Data := 0;
        if (GlobalFlags(lh_Clip) <> GMEM_INVALID_HANDLE) then
        begin
          try
            if (li_Text = CF_UNICODETEXT) then
            begin
              //Unicode文字列を優先。
              lh_Data := GlobalAlloc(GHND or GMEM_SHARE, GlobalSize(lh_Clip));
              lp_Clip := GlobalLock(lh_Clip);
              lp_Data := GlobalLock(lh_Data);
              lstrcpyW(lp_Data, lp_Clip);
              Result := WideString(PWideChar(lp_Data));
              GlobalUnlock(lh_Data);
              GlobalFree(lh_Data);
              GlobalUnlock(lh_Clip);  //GlobalFreeはしてはいけない。
            end else
            if (li_Text = CF_TEXT) then
            begin
              lh_Data := GlobalAlloc(GHND or GMEM_SHARE, GlobalSize(lh_Clip));
              lp_Clip := GlobalLock(lh_Clip);
              lp_Data := GlobalLock(lh_Data);
              lstrcpy(lp_Data, lp_Clip);
              Result := AnsiString(PAnsiChar(lp_Data));
              GlobalUnlock(lh_Data);
              GlobalFree(lh_Data);
              GlobalUnlock(lh_Clip);  //GlobalFreeはしてはいけない。
            end;
          finally
            if (lh_Data <> 0) then
            begin
              GlobalUnlock(lh_Data);
            end;
            CloseClipboard;
          end;
        end;
      end;
    end;
  end;
end;

ファイルへの保存はTRichEditのSelectAllメソッドで全選択にしてからCopyToClipboardメソッドでクリップボードへ送ります。
あとはクリップボードからUnicode形式でテキストを取得して、それをUTF-8に変換してTStringListのSaveToFileメソッドでファイルへ保存します
これでUTF-8のファイルの出来上がりです。

procedure TForm1.Button2Click(Sender: TObject);
var
  l_List: TStrings;
begin
  if (SaveDialog1.Execute) then
  begin
    RichEdit1.SelectAll;
    RichEdit1.CopyToClipboard;
    l_List := TStringList.Create;
    try
      l_List.Text := #$EF#$BB#$BF + Utf8Encode(gfnsStrFromClipboard); //BOMをつけて保存
      l_List.SaveToFile(SaveDialog1.FileName);
    finally
      l_List.Free;
    end;
  end;
end;

これでRichEditの内容をBOM付のUTF-8ファイルに保存することができます。

貼り付け

クリップボードからの貼り付けはちょっとややこしいです。
重要なことはTRichEdit同士であれば文字化けすることなくUnicodeな文字を貼り付けられるということです。
恐らくTRichEditはプレーンなテキスト形式以外にリッチテキストフォーマット形式でもクリップボードへデータを送っているのではないかと思います。
で、普通に貼り付けを行った場合はUnicode対応ではないプレーンテキストの貼り付け処理になり、Unicode対応ではないので化けるけれどもリッチテキストフォーマット形式の貼り付け処理の方はUnicode対応なので化けることなくやり取りできるのだろう、と。
そういうことであればクリップボードから貼り付けを行う前に、まずクリップボードからデータを取り出してTRichEditで文字化けしない形式に変換してから再びクリップボードへ送り、その後で貼り付けを行えば良さそうです。
とはいえリッチテキストフォーマットを自力で解析実装するのも面倒なのでいっそそれだけのためのRichEdit2を用意してしまいます。
RichEdit同士でのやり取りはOKなのだし、面倒なことはRichEditにやってもらいましょうということです。

フォームにRichEditを追加します。
NameはRichEdit2になるかと思います。
VisibleプロパティはFalseにしておきます。

procedure TForm1.Button3Click(Sender: TObject);
var
  l_Stream : TStream;
  ls_Str   : Utf8String;
  lp_Buff  : PAnsiChar;
begin
  l_Stream := TMemoryStream.Create;
  try
    ls_Str  := #$EF#$BB#$BF + Utf8Encode(gfnsStrFromClipboard); //BOMを付加
    lp_Buff := PAnsiChar(ls_Str);
    l_Stream.Write(lp_Buff[0], Length(ls_Str));  //ストリームに書き込み
    l_Stream.Position := 0;
    RichEdit2.Lines.LoadFromStream(l_Stream);    //RichEdit2にストリームから読み込ませる
  finally
    l_Stream.Free;
  end;
  RichEdit2.SelectAll;
  RichEdit2.CopyToClipboard;     //RichEdit2の内容をクリップボードへコピー

  RichEdit1.PasteFromClipboard;  //RichEdit1にクリップボードから貼り付け
end;

まずファイルへ保存でやったようにクリップボードからUnicode形式でテキストを取り出し、UTF-8に変換してからBOMをつけて今回はメモリーストリームに書き込みます。
書き込んだメモリーストリームをクリップボードコピー用のRichEdit2にLoadFromStreamで読み込ませます。
あとはRichEdit2で全選択してクリップボードへコピーを行った後RichEdit1で貼り付けを行なって終わりです。

残る問題点

保存や貼り付けだけでなく文字数のカウントや一部の文字列の取り出しなどでもすべてクリップボードを介さなければならないので、そのつどクリップボードの内容が変わってしまいます。


まともな回避策

プログラム掲示板の投稿からVCLのTRichEditでも上記のようなクリップボードを経由することなくUnicodeな文字列の取得と設定が可能なことが分かりました。

ANSI版DelphiでUnicode(RichEdit内容取得編) | Kurumi's TRPG archive

http://www.vampire-blood.net/837.html

上記サイトにソースコードが載っています。

ただしD6ではCTextStreamBufferとPTextStreamBufferが宣言されていないようなので自力で宣言する必要があります。
また上記サイトではMathユニットを使用していますがD6のパーソナル版にはMathユニットがなかったと思うのでその点への対処と、 細かいことですが上記サイトのソースコード中にマイナス記号がShift_JISにはないUnicodeな文字を使って記述されている部分があるためソースコードをコピーしてIDEに貼り付けると文字化けしてしまい構文エラーになってしまう点を書き換えたものを載せます。

type
  //D6では宣言されていないようなので自分で宣言する。
  CTextStreamBuffer = record
    lpszPos   : LPCWSTR;
    dwLeftLen : DWORD;
  end;
  PTextStreamBuffer = ^CTextStreamBuffer;

//http://www.vampire-blood.net/837.html
//下記の関数に必要な処理
function EditStreamCallBack(dwCookie: Integer; pbBuff: PByte; cb: Longint; var pcb: Longint): Longint; stdcall;
var
  MinS : Int64;
  Dat : CTextStreamBuffer;
begin
  Dat := PTextStreamBuffer(dwCookie)^;
  try
    Result:= 0;
    if (Dat.dwLeftLen <= 0) then begin
      pcb := 0;
      Exit;
    end;

    //MinS := Math.Min(DWord(cb),Dat.dwLeftLen);
    //D6パーソナルにはMathユニットがないための処置。
    MinS := DWord(cb);
    if (MinS > Dat.dwLeftLen) then
    begin
      MinS := Dat.dwLeftLen;
    end;

    copymemory(pbBuff, CTextStreamBuffer(PTextStreamBuffer(dwCookie)^).lpszpos, MinS);
    if Dat.dwLeftLen < MinS then
    begin
      PTextStreamBuffer(dwCookie)^.dwLeftLen := 0
    end else
    begin
      PTextStreamBuffer(dwCookie)^.dwLeftLen := dat.dwLeftLen - MinS;
    end

    Dat.lpszPos := Pointer(Integer(Dat.lpszPos) + MinS);
    pcb := MinS;
  except
    Result:= 1;
  end;
end;

あとは上記サイトのソースコードのコピーでいけます。

ちなみにD6のVCLのRichEditは

などからバージョンは1であるようです。


2012-02-09:
 プログラム掲示板の投稿からVCLのTRichEditでも上記のようなクリップボードを経由することなくUnicodeな文字列の取得と設定が可能なことを追記。
2010-10-12: