Unicode対応のTListBox
TListBoxをUnicode対応に。
TMyWStringsというUnicodeを保持できるリストを使います。
このリストにUnicodeを持たせ、あとはDrawItemを書き換えてUnicodeを表示させます。
TMyCustomListBox
通常のリストボックスと仮想リストボックスを利用したものを作るのでまずはそれらの土台となるTMyCustomListBoxを作ります。
type
{TMyCustomListBox}
TLBGetWideDataEvent = procedure(Control: TWinControl; Index: Integer; var Data: WideString) of object;
TMyCustomListBox = class(TCustomListBox)
private
F_slList: TMyWStrings;
FOnData: TLBGetWideDataEvent;
protected
function DoGetData(const Index: Integer): WideString;
function GetStyle: TListBoxStyle;
property Items: TMyWStrings read F_slList;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Clear; override;
procedure DrawItem(Index: Integer; Rect: TRect; State: TOwnerDrawState); override;
published
property AutoComplete;
property Align;
property Anchors;
property BevelEdges;
property BevelInner;
property BevelKind default bkNone;
property BevelOuter;
property BiDiMode;
property BorderStyle;
property Color;
property Columns;
property Constraints;
property Ctl3D;
property DragCursor;
property DragKind;
property DragMode;
property Enabled;
property ExtendedSelect;
property Font;
property ImeMode;
property ImeName;
property IntegralHeight;
property ItemHeight;
property MultiSelect;
property ParentBiDiMode;
property ParentColor;
property ParentCtl3D;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ScrollWidth;
property ShowHint;
property Sorted;
property TabOrder;
property TabStop;
property TabWidth;
property Visible;
property OnClick;
property OnContextPopup;
property OnDataFind;
property OnDataObject;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnDrawItem;
property OnEndDock;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMeasureItem;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnStartDock;
property OnStartDrag;
property OnData: TLBGetWideDataEvent read FOnData write FOnData;
end;
TCustomListBoxを継承します。
DoGetDataとDrawItemを実装してOnDataプロパティを書き換えてUnicodeの表示に対応します。
GetStyleはこのクラスを継承する後のクラスで必要になります。
constructor TMyCustomListBox.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
F_slList :=
TMyWStrings.Create(
inherited Items);
end;
destructor TMyCustomListBox.Destroy;
begin
F_slList.Free;
inherited Destroy;
end;
procedure TMyCustomListBox.Clear;
begin
F_slList.Clear;
end;
function TMyCustomListBox.DoGetData(
const Index: Integer):
WideString;
begin
if (Assigned(OnData))
then begin
OnData(Self, Index, Result);
end else begin
Result := Items[Index];
end;
end;
//DrawItem
procedure TMyCustomListBox.DrawItem(Index: Integer; Rect: TRect; State: TOwnerDrawState);
const
lci_MARGIN = 2;
var
lrc_Rect: TRect;
ls_WData:
WideString;
begin
if (Style
in [lbOwnerDrawFixed, lbOwnerDrawVariable, lbVirtualOwnerDraw])
and (Assigned(OnDrawItem))
then begin
OnDrawItem(Self, Index, Rect, State);
end else begin
with Canvas
do begin
FillRect(Rect);
lrc_Rect := Rect;
if (UseRightToLeftAlignment)
then begin
Dec(lrc_Rect.Right, lci_MARGIN);
end else begin
Inc(lrc_Rect.Left, lci_MARGIN);
end;
if (Style
in [lbVirtual, lbVirtualOwnerDraw])
then begin
if (Assigned(FOnData))
then begin
ls_WData := DoGetData(Index);
end else if (
Index >= 0)
and (Index < F_slList.Count)
then begin
ls_WData := Items[Index];
end else begin
ls_WData := '';
end;
end else begin
ls_WData := Items[Index];
end;
DrawTextW(Handle, PWideChar(ls_WData), -1, lrc_Rect, DT_VCENTER
or DT_NOPREFIX
or DT_SINGLELINE);
end;
end;
end;
function TMyCustomListBox.GetStyle: TListBoxStyle;
begin
Result :=
inherited Style;
end;
F_slListをTCustomListBoxのItemsを引数にしてCreateしているのでリストの保存先はTCustomListBox(に実装されているリスト)になります。実際に保存されるのはF_slListによってWideStringからStringに変換されたデータです。そのStringのデータをF_slListを通して取り出すことでWideStringに戻すわけです。
そしてそのWideStringのデータをDrawItemメソッドをオーバーライドすることでDrawTextW
APIを使ってUnicodeに対応した描画を行います。
TMyListBox
通常のリストボックス。
メソッドを一つ加えてプロパティ二つをpublishedにするだけです。
{TMyListBox}
TMyListBox = class(TMyCustomListBox)
protected
procedure SetStyle(lbStyle: TListBoxStyle);
public
constructor Create(AOwner: TComponent); override;
published
property Style read GetStyle write SetStyle default lbOwnerDrawFixed;
property Items;
end;
constructor TMyListBox.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Style := lbOwnerDrawFixed;
end;
procedure TMyListBox.SetStyle(lbStyle: TListBoxStyle);
begin
if (lbStyle = lbStandard) then begin
lbStyle := lbOwnerDrawFixed;
end;
inherited Style := lbStyle;
end;
StyleプロパティをセットするためのSetStyleメソッドを加えます。
StyleがlbStandardの場合DrawItemが呼ばれないのでlbStandardを指定した場合はlbOwnerDrawFixedに変換してセットします。
TMyListBoxEx
次に仮想リストボックスを利用したものを作ります。
通常のリストボックスだと数千行以上のデータを読み込もうとするとえらく時間がかかるようになります。
そういう場合は仮想リストボックスにしてデータはTStringListなどに読み込むようにすると劇的に速くなります。
ただ仮想リストボックスはリストに追加したり削除したりするたびにリストボックスのCountプロパティを変更しないといけない煩わしさがあります。
それを専用のリストクラスを作って解決しようというものです。
type
{ TMyListBoxEx }
TMyListBoxEx = class(TMyCustomListBox)
private
FWStringList: TMyWStrings;
protected
procedure DeleteString(Index: Integer); override;
procedure SetStyle(lbStyle: TListBoxStyle);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure AddItem(Item: WideString; AObject: TObject); reintroduce;
procedure Clear; override;
procedure DeleteSelected; override;
procedure DrawItem(Index: Integer; Rect: TRect; State: TOwnerDrawState); override;
procedure SelectAll; override;
property Items: TMyWStrings read FWStringList;
published
property Style read GetStyle write SetStyle; // default lbVirtualOwnerDraw; 設計時最初にlbVirtualOwnerDrawにならないのでコメントアウト
end;
TMyListBoxExStrings
仮想リストボックス用の文字列リストのクラスです。
リストのCountプロパティが変動する時に仮想リストボックスのCountプロパティをセットします。
{TMyListBoxExStrings}
constructor TMyListBoxExStrings.Create(AListBox: TCustomListBox);
begin
inherited Create;
FListBox := AListBox;
FiTopIndex := 0;
FbCountThrough := False;
end;
procedure TMyListBoxExStrings.FSetCount;
begin
FListBox.Count := AnsiStrings.Count;
FListBox.TopIndex := FiTopIndex;
end;
function TMyListBoxExStrings.Add(sStr: WideString): Integer;
begin
FiTopIndex := FListBox.TopIndex;
Result := inherited Add(sStr);
FSetCount;
end;
procedure TMyListBoxExStrings.Insert(iIndex: Integer; sStr: WideString);
begin
FiTopIndex := FListBox.TopIndex;
inherited Insert(iIndex, sStr);
FSetCount;
end;
procedure TMyListBoxExStrings.Assign(slList: TStrings);
begin
Clear;
inherited Assign(slList);
FSetCount;
end;
procedure TMyListBoxExStrings.Assign(slList: TMyWStrings);
begin
Clear;
inherited Assign(slList);
FSetCount;
end;
procedure TMyListBoxExStrings.Attach(slList: TStrings);
begin
inherited;
FSetCount;
end;
procedure TMyListBoxExStrings.AddStrings(slList: TStrings);
begin
FiTopIndex := FListBox.TopIndex;
inherited AddStrings(slList);
FSetCount;
end;
procedure TMyListBoxExStrings.AddStrings(slList: TMyWStrings);
begin
FiTopIndex := FListBox.TopIndex;
inherited AddStrings(slList);
FSetCount;
end;
procedure TMyListBoxExStrings.InsertStrings(iIndex: Integer; slList: TStrings);
begin
FiTopIndex := FListBox.TopIndex;
inherited InsertStrings(iIndex, slList);
FSetCount;
end;
procedure TMyListBoxExStrings.InsertStrings(iIndex: Integer; slList: TMyWStrings);
begin
FiTopIndex := FListBox.TopIndex;
inherited InsertStrings(iIndex, slList);
FSetCount;
end;
procedure TMyListBoxExStrings.Delete(iIndex: Integer);
begin
FiTopIndex := FListBox.TopIndex;
inherited Delete(iIndex);
TMyListBoxEx(FListBox).DeleteString(iIndex);
end;
procedure TMyListBoxExStrings.Exchange(iIndex1, iIndex2: Integer);
begin
FiTopIndex := FListBox.TopIndex; //EndUpdate対策
inherited Exchange(iIndex1, iIndex2);
end;
procedure TMyListBoxExStrings.LoadFromFile(sFile: WideString);
begin
inherited LoadFromFile(sFile);
FSetCount;
end;
procedure TMyListBoxExStrings.BeginUpdate;
begin
FbCountThrough := True;
end;
procedure TMyListBoxExStrings.EndUpdate;
var
li_ItemIndex : Integer;
begin
if (FbCountThrough) then
begin
li_ItemIndex := FListBox.ItemIndex;
FbCountThrough := False;
FSetCount;
FListBox.ItemIndex := li_ItemIndex;
end;
end;
Unicodeを直接リストに持たせるTMyWStringsから派生させます。
TMyWStringsと同じようにAssignやAddStringsなどのリストを引数に取るものはTStrings版とTMyWStrings版の二つ作成する必要があります。
本体
次に仮想リストボックス本体です。
通常のリストボックスより実装しないといけないメソッドが多くなります。
{TMyListBoxEx}
constructor TMyListBoxEx.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FWStringList := TMyListBoxExStrings.Create(Self);
if (AOwner is TWinControl) then
begin
Parent := TWinControl(AOwner);
Style := lbVirtualOwnerDraw;
end;
end;
destructor TMyListBoxEx.Destroy;
begin
FWStringList.Free;
inherited Destroy;
end;
procedure TMyListBoxEx.SetStyle(lbStyle: TListBoxStyle);
begin
inherited Style := lbVirtualOwnerDraw;
end;
procedure TMyListBoxEx.Clear;
begin
FWStringList.Clear;
Count := 0;
end;
procedure TMyListBoxEx.AddItem(Item: WideString; AObject: TObject);
begin
FWStringList.AddObject(Item, AObject);
Count := FWStringList.Count;
end;
procedure TMyListBoxEx.SelectAll;
begin
if (MultiSelect) then
begin
SendMessage(Handle, LB_SETSEL, 1, -1);
end else
begin
SendMessage(Handle, LB_SETCURSEL, -1, 0);
end;
end;
procedure TMyListBoxEx.DeleteString(Index: Integer);
begin
inherited;
end;
procedure TMyListBoxEx.DeleteSelected;
var
i, li_Count: Integer;
begin
FWStringList.BeginUpdate;
try
if (MultiSelect) then
begin
li_Count := 0;
for i := FWStringList.Count -1 downto 0 do
begin
if (Selected[i]) then
begin
FWStringList.Delete(i);
Inc(li_Count);
if (li_Count = SelCount) then
begin
Break;
end;
end;
end;
end else
begin
if (ItemIndex > -1) then
begin
FWStringList.Delete(ItemIndex);
end;
end;
finally
FWStringList.EndUpdate;
end;
end;
procedure TMyListBoxEx.DrawItem(Index: Integer; Rect: TRect; State: TOwnerDrawState);
const
lci_MARGIN = 2;
var
lrc_Rect: TRect;
ls_WData: WideString;
begin
if (TMyListBoxExStrings(FWStringList).FbCountThrough) then
begin
Exit;
end;
if (Style = lbVirtualOwnerDraw)
and (Assigned(OnDrawItem))
then
begin
OnDrawItem(Self, Index, Rect, State);
end else
begin
with Canvas do
begin
FillRect(Rect);
lrc_Rect := Rect;
if (UseRightToLeftAlignment) then
begin
Dec(lrc_Rect.Right, lci_MARGIN);
end else
begin
Inc(lrc_Rect.Left, lci_MARGIN);
end;
if (Assigned(OnData)) then
begin
ls_WData := DoGetData(Index);
end else
if (0 <= Index)
and (Index < FWStringList.Count)
then
begin
//リストの範囲内である
ls_WData := Items[Index];
end else
begin
ls_WData := '';
end;
DrawTextW(Handle, PWideChar(ls_WData), -1, lrc_Rect, DT_VCENTER or DT_NOPREFIX or DT_SINGLELINE);
end;
end;
end;
※注意点
通常のリストボックスと違いCreateでStyleのデフォルト値をセットをしません。
本当ならlbVirtualOwnerDrawをセットしたいのですが、それをするとコンポーネントとしてインストールした場合にフォームにドロップするとエラーになってしまいます。
lbVirtualにしてもだめでした。
ということでTMyListBoxExをコンポーネントとしてインストールした場合、設計時フォームにドロップした時のStyleはlbStandardのままです。
そのままでコンパイルしてしまうとリストを読み込んだときにエラーになってしまうので最初にStyleをlbVirtualOwnerDrawに変更する必要があります。
myListBox.pas
2008-07-21: