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で直接メッセージを送るという方法が必要になります。
試してみる
- フォームにTRichEditを貼り付けて実行。
- FF と入力。
- FF を選択状態にする。
- Alt+Xを押す。
これで選択状態だった FF が ÿ に変わります。
FF と入力
FF を選択状態に。
Alt+Xキーを押す。
選択状態だった FF が ÿ に変わります。
もし変わらなければその環境でのTRichEditはUnicode対応ではないのでこのページの技は使えません。
これはリッチエディットコントロールの持つ変換機能を利用してUnicodeな文字を入力するやり方です。
この他にもIMEパッドの文字一覧などから入力することもできます。
つまりVCLのTRichEditはそのままでUnicodeな文字の入力に対応しているということです。
- 変換された ÿ を選択状態にします。
- Ctrl+C を押してクリップボードへコピーします。
- メモ帳を開きます。
- 「編集」→「貼り付け」を行います。
メモ帳に ÿ が貼り付けられたと思います。
Unicodeな文字をクリップボードへ送ることも可能です。
- Unicodeな文字を残して適当に編集します。
- メモ帳の「ファイル」→「名前を付けて保存」で文字コードを「UTF-8」にして保存します。
- 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は
- 改行コードがCR+LFであること
- 「元に戻す」のレベルが一回だけであること
などからバージョンは1であるようです。
2012-02-09:
プログラム掲示板の投稿からVCLのTRichEditでも上記のようなクリップボードを経由することなくUnicodeな文字列の取得と設定が可能なことを追記。
2010-10-12: