ホーム >プログラム >Delphi 6 ローテクTips >TListBoxにUnicodeを表示

ウムラウトのようなUnicode文字をTListBoxに表示させましょうというページ。


TListBoxにはOnDrawItemイベントがあります。
この中でTextOutWやDrawTextWなどのUnicode版APIを使えばUnicode文字を表示することができます。
とはいえなんとなく難しそう…とか面倒くさそう…などと思ってしまいます。
私は思っていました。
それでもここは一念発起して頑張ってみるか!と重い腰をあげてやってみたら意外と簡単にできてしまいました。


TListBoxでUnicodeを表示するにはOnDrawItemイベントが起きるようにしなければなりません。
そのためにTListBoxのStyleプロパティをlbOwnerDrawFixedにします。

lbOwnerDrawVariableやlbVirtualOwnerDrawなどでもOKです。
目的に合わせて選択してください。

リストに文字列を追加するときはWideStringをUTF-7に変換して追加します。
そしてOnDrawItemイベントの中でUTF-7に変換したものをWideStringに戻してからTextOutWやDrawTextWへ渡します。
こうすることでUnicode文字がちゃんと表示されるようになります。


function gfnsWideToUtf7(sSrc: WideString): AnsiString;
//WideStringをUTF-7にエンコードして返す
var
  li_Len:  Integer;
  lp_Buff: PAnsiChar;
begin
  li_Len  := WideCharToMultiByte(CP_UTF7, 0, PWideChar(sSrc), -1, nil, 0, nil, nil);
  lp_Buff := AllocMem(li_Len + 1);
  try
    WideCharToMultiByte(CP_UTF7, 0, PWideChar(sSrc), -1, lp_Buff, li_Len, nil, nil);
    Result := AnsiString(lp_Buff);
  finally
    FreeMem(lp_Buff);
  end;
end;

function gfnsUtf7ToWide(sSrc: AnsiString): WideString;
//UTF-7でエンコードされている文字列をWideStringにして返す
var
  li_Len:  Integer;
  lp_Buff: PWideChar;
begin
  li_Len  := MultiByteToWideChar(CP_UTF7, 0, PAnsiChar(sSrc), -1, nil, 0);
  lp_Buff := AllocMem((li_Len + 1) * 2);
  try
    MultiByteToWideChar(CP_UTF7, 0, PAnsiChar(sSrc), -1, lp_Buff, li_Len);
    Result := WideString(lp_Buff);
  finally
    FreeMem(lp_Buff);
  end;
end;


procedure TForm1.Button1Click(Sender: TObject);
var
  ls_Wide: WideString;
begin
  ls_Wide := 'Queensr' + WideString(WideChar($00FF)) + 'che'; //Queensrÿche
  ListBox1.Items.Clear;
  //WideStringをUTF-7に変換してAnsiStringでしか持てないリストに追加
  ListBox1.Items.Add(gfnsWideToUtf7(ls_Wide));
end;

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
begin
  //塗りつぶして
  ListBox1.Canvas.FillRect(Rect);

  //文字列出力
  //UTF-7に変換されているリストの文字列をWideStringに戻してUnicode版APIで出力
  DrawTextW(ListBox1.Canvas.Handle, PWideChar(gfnsUtf7ToWide(ListBox1.Items[Index])), -1, Rect, DT_NOPREFIX or DT_SINGLELINE or DT_VCENTER);
end;

ウムラウトのようなUnicode文字「ÿ」がちゃんと表示されています。

必要なRectやリストのインデックスの類がちゃんとOnDrawItemイベントの引数で与えられるので手間がかかりません。
やらなければいけないことは塗りつぶした後に文字列を出力するだけです。
塗りつぶすのは要するに前の表示を消すためです。
背景色や文字色をわざわざ指定する必要はありません。
選択されている行とそうでない行とでちゃんと色を分けてくれています。
こちらで指定する必要があるとしたらそれは特別他の色の背景色にしたいなどという場合だけです。
その場合もBrushやFontの設定を元に戻す必要はないようです。


上記の例だと文字が左端ぴったりから出力されます。
ちょっと余裕を持たせたいという場合はRectの値を狭めます。
上下はDrawTextWのオプションで縦位置の真ん中にくるようにDT_VCENTERを指定しているのでそのままで大丈夫なので左右を狭めるだけでいけます。

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
const
  lci_MARGIN = 2;
var
  lrc_Rect: TRect;
begin
  //塗りつぶして
  ListBox1.Canvas.FillRect(Rect);

  //文字出力
  lrc_Rect := Rect;
  Inc(lrc_Rect.Left,  lci_MARGIN);  //左マージン
  Dec(lrc_Rect.Right, lci_MARGIN);  //右マージン
  DrawTextW(ListBox1.Canvas.Handle, PWideChar(gfnsAnsiToWideEx(ListBox1.Items[Index])), -1, lrc_Rect, DT_NOPREFIX or DT_SINGLELINE or DT_VCENTER);
end;

ちなみにTextOutWではなくややこしそうなDrawTextWを使っているのはその方が楽だからです。
DrawTextWはとっつきにくい感じはありますが慣れてしまえば第4引数のRectの範囲でクリッピングしてくれたり第5引数のオプションで位置ぞろえを指定できたりするので表示位置の計算をしなくて済む等ちょっと楽できるのです。


リストの文字列をそのまま表示するだけでなく加工して表示することもできます。
ファイルを管理している場合にパス名とファイル名を分割してファイル名を先に表示させてパスをファイル名の後になどというのも可能ですしヘッダーコントロールを併用して簡易ストリンググリッドみたいなのもできます。
というかCanvasを使っているのでビットマップを表示することだってできてしまいます。
がんばれば無敵になれるかもしれません。

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
const
  lci_MARGIN = 2;
var
  lrc_Rect: TRect;
begin
  //塗りつぶして
  ListBox1.Canvas.FillRect(Rect);

  //文字出力
  lrc_Rect := Rect;
  Inc(lrc_Rect.Left,  lci_MARGIN);  //左マージン
  Dec(lrc_Rect.Right, lci_MARGIN);  //右マージン
  DrawTextW(ListBox1.Canvas.Handle, PWideChar(gfnsUtf7ToWide(ListBox1.Items[Index])), -1, lrc_Rect, DT_NOPREFIX or DT_SINGLELINE or DT_VCENTER);

  //下線を引く
  ListBox1.Canvas.Pen.Color := clBtnShadow;
  ListBox1.Canvas.MoveTo(Rect.Left,  Rect.Bottom - 1);
  ListBox1.Canvas.LineTo(Rect.Right, Rect.Bottom - 1);
end;

左端ぴったりではなく少し隙間が空き、アンダーラインが引かれています。