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

Unicode対応のEditとMemo

Delphi 6でウムラウトのようなUnicode文字を入力するためにはUnicode入力対応のエディットコントロールを作る必要があります。


参考サイト


始めに

単にUnicode文字の入力ができればいいのであればCreativeWindowW APIを使ってエディットボックスを作るだけOKです。
ただしウムラウトのようなUnicode文字の入力はクリップボードを介したコピーアンドペーストでしかできないようです。

作成するエディットコントロールしか置かない入力専用のフォームであれば(複数行入力のMemoの場合はPanelを親にした方が良いのですが)それだけで十分いけます。
ところが他のTButtonなどのVCLコンポーネントと併用しようとすると、方向キーが利かなくなったりTabキーでの移動ができなかったりとちょっと使い勝手が悪くなってしまいます。
そういう問題をあれこれ試行錯誤して解決した結果がこのページなわけです。

ソースコード

まずはソースコードを。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Forms, Classes, ExtCtrls, Controls, StdCtrls,
  ComCtrls, Grids;

type
  TForm1 = class(TForm)
    DrawGrid1: TDrawGrid;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure DrawGrid1Enter(Sender: TObject);
  private
    { Private 宣言 }
    F_hEditHandle: HWND;
    procedure WMMouseActivate(var Msg: TWMMOUSEACTIVATE); message WM_MOUSEACTIVATE;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


procedure TForm1.FormCreate(Sender: TObject);
begin
  DrawGrid1.Align := alClient;

  //エディットコントロールの作成(複数行入力のMemoコントロール)
  F_hEditHandle := CreateWindowW(
      'EDIT',
      nil,
      WS_CHILD
      or ES_MULTILINE     //これをコメントアウトで一行入力のEditコントロール
      or ES_AUTOVSCROLL   //一行入力のときは
      or WS_VSCROLL       //これと
      or WS_HSCROLL       //これもコメントアウトした方が良い
      or ES_AUTOHSCROLL
      or ES_NOHIDESEL
      or WS_VISIBLE,
      0,
      0,
      DrawGrid1.ClientWidth,
      DrawGrid1.ClientHeight,
      DrawGrid1.Handle,
      0,
      0,
      nil
  );

  //フォントのセット
  if (F_hEditHandle <> 0) then begin
    //TFontのHandleを指定する
    SendMessageW(F_hEditHandle, WM_SETFONT, WPARAM(DrawGrid1.Font.Handle), 0);
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DestroyWindow(F_hEditHandle);
  F_hEditHandle := 0;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  //DrawGrid1のサイズに合わせる
  SetWindowPos(F_hEditHandle, HWND_TOP, 0, 0, DrawGrid1.ClientWidth, DrawGrid1.ClientHeight, SWP_NOACTIVATE or SWP_NOZORDER);
end;

procedure TForm1.DrawGrid1Enter(Sender: TObject);
//Tabで他のVCLコンポーネント間との移動を行えるようにTDrawGridを利用する
begin
  //DrawGrid1がアクティブになったら作成したエディットコントロールにフォーカスを移す
  Windows.SetFocus(F_hEditHandle);
end;

procedure TForm1.WMMouseActivate(var Msg: TWMMOUSEACTIVATE);
//マウスでエディットコントロールをクリックしてもVCLのTabStopのインデックスは変わらないことへの対処
var
  lpt_Pos: TPoint;
begin
  lpt_Pos := Point(0, 0);
  GetCursorPos(lpt_Pos);
  if (WindowFromPoint(lpt_Pos) = F_hEditHandle) then begin
    //クリックしたのが作成したエディットコントロールであれば
    //まずDrawGridに(VCLの)SetFocusを行ってTabStopのインデックスを整える

    //その後で作成したエディットコントロールにフォーカスを移す
    DrawGrid1.SetFocus;
    DrawGrid1Enter(nil);
  end;
end;

end.

テキストのセット

procedure TextSet(const hHandle: HWND; const sWText: WideString);
  SetWindowTextW(hHandle, PWideChar(sWText));
end;

テキストの取得

function TextGet(const hHandle: HWND): WideString;
var
  li_Len  : Integer;
  lp_Buff : PWideChar;
begin
  Result := '';
  li_Len := GetWindowTextLengthW(hHandle) +1;
  if (li_Len > 0) then
  begin
    lp_Buff := AllocMem(li_Len * 2);
    try
      GetWindowTextW(hHandle, lp_Buff, li_Len);
      Result := WideString(lp_Buff);
    finally
      FreeMem(lp_Buff);
    end;
  end;
end;

テキストのセットと取得のサンプル。

  //テキストのセット
  TextSet(F_hEditHandle, 'Unicode対応');

  //テストの取得
  ls_Text := TextGet(F_hEditHandle);

Editコントロール作成

エディットコントロールを作成するのはそれほど面倒ではありません。
指定できるオプションが色々あるので一見面倒そうですが必要なオプションは限られます。

  F_hEditHandle := CreateWindowW(
      'EDIT',
      nil,
      WS_CHILD
      or ES_AUTOHSCROLL
      or ES_NOHIDESEL
      or WS_VISIBLE,
      0,                      //Left
      0,                      //Top
      DrawGrid1.ClientWidth,  //幅
      DrawGrid1.ClientHeight, //高さ
      DrawGrid1.Handle,       //親ウィンドウのハンドル
      0,
      0,
      nil
  );

一行入力のEditコントロールならこれでOKです。
複数入力のMemoコントロールなら下のようにすればOKです。

  F_hEditHandle := CreateWindowW(
      'EDIT',
      nil,
      WS_CHILD
      or ES_MULTILINE
      or ES_AUTOVSCROLL
      or ES_AUTOHSCROLL
      or WS_VSCROLL
      or WS_HSCROLL
      or ES_NOHIDESEL
      or WS_VISIBLE,
      0,                       //Left
      0,                       //Top
      DrawGrid1.ClientWidth,   //幅
      DrawGrid1.ClientHeight,  //高さ
      DrawGrid1.Handle,        //親ウィンドウのウィンドウハンドル
      0,
      0,
      nil
  );

ES_NOHIDESELはフォーカスを失った時に選択範囲を非表示にしたければコメントアウト。
Memoコントロールのとき横スクロールさせたくなければES_AUTOHSCROLLとWS_HSCROLLをコメントアウト。
親ウィンドウをTDrawGridにしている理由は後述。

Tabキーによる移動

ただ作成しただけではTabキーによる他のVCLコンポーネント(TButton など)間の移動ができません。
オプションにWS_TABSTOPをつけたりCreateWindowExWに変えて第一引数にWS_EX_CONTROLPARENTを指定したりしてもだめでした。
というかTButtonコントロールなどがあると方向キーでの範囲選択や移動などができなくなります。
どうもVCLコンポーネントにキー入力を横取りされてしまっているような感じです。

この問題はTDrawGridを親ウィンドウに指定してTDrawGridのOnEnterイベントに作成したエディットコントロールへフォーカスを移すコードを書くことで回避できました。

procedure TForm1.DrawGrid1Enter(Sender: TObject);
//Tabで他のコンポーネント間との移動を行えるようにTDrawGridを利用する
begin
  //DrawGrid1がアクティブになったらエディットコントロールにフォーカスを移す
  Windows.SetFocus(F_hEditHandle);
end;

あと作成したエディットコントロールをマウスでクリックした場合はVCLのTabStopのインデックスは変化しないので、本来なら作成したエディットコントロールの(ベースとなるDrawGridの)次のコントロールに移動してほしいところがそうならないことがあります。
この問題はマウスクリックが起きたというメッセージをフォームで受け取り、クリックした位置を取得してその下にあるウィンドウを調べ、それが作成したエディットコントロールであればベースとなっているDrawGridにSetFocusしてTabStopを整えてから作成したエディットコントロールにフォーカスを戻すことで回避しました。

  private
    { Private 宣言 }
    procedure WMMouseActivate(var Msg: TWMMOUSEACTIVATE); message WM_MOUSEACTIVATE;
procedure TForm1.WMMouseActivate(var Msg: TWMMOUSEACTIVATE);
//マウスでエディットコントロールをクリックしてもVCLのTabStopのインデックスは変わらないことへ対処
var
  lpt_Pos: TPoint;
begin
  GetCursorPos(lpt_Pos);
  if (WindowFromPoint(lpt_Pos) = F_hEditHandle) then begin
    //クリックしたのが作成したエディットコントロールであれば
    //まずDrawGridに(VCLの)SetFocusを行ってTabStopのインデックスを整える

    //その後で作成したエディットコントロールにフォーカスを移す
    DrawGrid1.SetFocus;
    DrawGrid1Enter(nil);
  end;
end;

TDrawGridを親ウィンドウにする理由

作成するエディットボックスの親ウィンドウをSelfやTEditあるいはTMemoやTRichEditではなくなぜTDrawGridにするのかというと、色々やってみた結果一番具合が良かったからです。
TEditやTMemoだとReadOnlyにしていても、作成したエディットコントロールに最初にフォーカスをセットしたときにTEditやTMemoのキャレットが余計に表示されてしまうのです。
一度フォーカスを他のButtonなどに移してしまえば消えるのですが、HideCaret APIを使ってみたりプロパティを色々変えてみたりしてもだめでした。
それではというのでTListBoxを使ってみたところ余計なキャレットは表示されないのですが選択領域を示す破線の枠が描画されてしまいます。
これはこれで良いかなとも思ったのですが、試しにTStringGridをDefaultDrawing、Optionを全部Falseに、ScrollBarsをssNoneにしてやってみたら余計なものが表示されなくていい感じになりました。
で、継承を見てみるとTStringGridはTDrawGridから派生しているので、Tabでの移動のためのベースにしか使わないのだからシンプルな方を選んだというわけです。

制限

ウムラウトのようなUnicode文字の入力はクリップボードを介したコピーアンドペーストでしかできません。
IMEパッドからの入力や単語登録したウムラウトなどのUnicode文字は一度AnsiStringに変換されてから入力されるような感じで、代替文字に変わるかデフォルト文字の「?」(半角クエスチョンマーク)に変わってしまいます。

Unicode対応のRichEditにはそのような制限はないので、ちょっと面倒な点はありますがそちらの方が良いかもしれません。


2008-12-14:RichEditはIMEパッドからのウムラウトのようなUnicode文字の入力を受け付ける件を追記。
2008-05-30:ウムラウトのようなUnicode文字はコピペでしか対応できない件を追記。
2008-04-18: