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

TMyWStrings

ウムラウトのようなUnicode文字列をTStringsのようなリストで管理しようというクラス。

目次

ソースコード

今までUnicodeとAnsiStringの可逆変換関数を使ってUnicode文字列をリスト管理してきました。
TStringsのリストに入れるときにはgfnsWideToUtf7を使い、取り出すときにはgfnsUtf7ToWideを使います。
ちょっと煩わしいですし、どっちの関数を使うべきなのかなどという気も使わなければなりませんでした。
ということでいっそクラスにしてしまおうじゃないかと。

目標はTStringListのように単独で使えるだけでなく、TListBoxやTComboBoxなどにも使えるようにすることです。

まずはちょっと実験。

type
  TMyStringList = class(TStringList)
  public
    function Add(const S: WideString): Integer;
  end;

本当は
    function Add(const S: WideString): Integer; override;
というようにAddメソッドをoverrideしたいのですが、「 'Add' の宣言がすでに定義されているものと異なります」というエラーメッセージが出てしまいコンパイルできません。
ということでoverride指令なしでやると警告は出ますがコンパイルはできます。

type
  TMyStringList = class(TStringList)
  public
    function Add(const S: WideString): Integer;
  end;

function TMyStringList.Add(const S: WideString): Integer;
begin
  Result := TStringList(Self).Add(gfnsWideToUtf7(S));
end;

WideStringをUTF-7に変換してリストに追加するようにします。
これがTMyStringListのAddメソッドの内容です。

procedure TForm1.Button1Click(Sender: TObject);
var
  List: TStringList; //-------------------- (1)
begin
  List := TMyStringList.Create; //--------- (2)
  try
    List.Add('テスト'); //----------------- (3)
    TMyStringList(List).Add('テスト'); //-- (4)

    myDebug.gpcDebugAdd(List[0]);
    myDebug.gpcDebugAdd(List[1]);
  finally
    List.Free;
  end;
end;

Listは(1)でTStringListと宣言されています。
そして(2)でTMyStringListでCreateしています。
なので(3)のList.AddはTMyStringListのAddが呼ばれるのかと思うとそうではありません。TStringListのAddが呼ばれてしまいます。
リストにはUTF-7に変換された文字列ではなく、'テスト'という文字列がそのまま追加されます。
UTF-7に変換された文字列を追加するためにTMyStringListのAddメソッドを呼び出したければ(4)のようにわざわざTMyStringListでキャストしなければなりません。
TMyStringListのAddメソッドがoverrideしてあればどちらの場合でもTMyStringListのAddメソッドが呼ばれるのですが、残念ながら引数の型が違うのでoverrideできません。

結果

1行目はList.Add('テスト')としたもの。
2行目はTMyStringList(List).Add('テスト')としたもの。
この場合AddをWideString版にしただけなので取り出した文字列はUTF-7に変換されたままの文字列であって欲しいわけです。
ところが1行目はそうなっていません。
結局この場合List.AddとしたのではTMyStringListのAddメソッドは呼ばれずTStringListのAddメソッドが直接呼ばれてしまっています。


Addメソッド等をoverrideできないのであればAddWなどというような名前でWideString版のメソッドを実装してしまうという手もあります。InsertWとかIndexOfWなどなど。
けれども、それは実際使うとき(ほんのちょっとだけれども)ややこしいしできればAddやInsertやIndexOfのままで使いたいものだと。
そういうことで、じゃぁもうTStringsを継承するのはやめてしまおうと。同じ名前で(できるだけ)同じ内容のメソッドを実装してしまえばよいではないかという方向で作ったのがmyWStrings.pasです。

  TMyWStrings = class(TObject)
  private

      ...

  protected
    function  WideToAnsi(sStr: WideString): AnsiString; virtual;
    function  AnsiToWide(sStr: AnsiString): WideString; virtual;

  public
    constructor Create; overload;
    constructor Create(slList: TStrings); overload;

    function Add(const sWStr: WideString): Integer; virtual;
    procedure Insert(iIndex: Integer; sStr: WideString); virtual;

    procedure Assign(slList: TStrings); overload; virtual;
    procedure Assign(slList: TMyWStrings); overload; virtual;

    property Strings[Index: Integer]: WideString read Get write Put; default;
    property AnsiStrings: TStrings read F_slList;  //生のStrings
  end;

全部書き出すと長いのでかいつまんで。

AddやInsertやStringsなどはWideStringを直接受け渡しできます。

AssignはTStringsとTMyWStringsの二つを実装する必要があります。
TMyWStringsはTStringsから派生させていないのでこうなります。
TStrings版のAssignはTStringsから派生したリストをコピーする場合に必要になります。
TMyStrings版のAssignは、TMyWStringListやTMyUStringListなどとの間でリストをコピーするときに変換方式の違いに影響されずにコピーできます。
Assignの他にも一括してリストに追加するAddStrings等も同様にTStrings版とTMyWStrings版の二つを実装します。

AnsiStringsプロパティは変換された文字列(AnsiString型)にアクセスできます。
StringsはWideString型の文字列リストで、AnsiStringsは変換関数を使って変換したAnsiString型の文字列リストになります。

あとは必要なメソッドやプロパティを次々と実装していけばできあがります。

protectedにあるWideToAnsiとAnsiToWideがWideStringとAnsiStringの変換関数になります。

//------------------------------------------------------------------------------
{TMyWStringList}
//WideStringをUTF-7に変換してリストに持つ
type
  TMyWStringList = class(TMyWStrings)
  protected
    function WideToAnsi(sStr: WideString): AnsiString; override;
    function AnsiToWide(sStr: AnsiString): WideString; override;
  end;

//------------------------------------------------------------------------------
{TMyUStringList}
//WideStringを直接リストに持つ
type
  TMyUStringList = class(TMyWStrings)
  protected
    function WideToAnsi(sWStr: WideString): AnsiString; override;
    function AnsiToWide(sStr: AnsiString): WideString; override;
  end;

//------------------------------------------------------------------------------
{TMyWideStringList}
//WideStringをオリジナル変換関数で変換してリストに持つ
type
  TMyWideStringList = class(TMyWStrings)
  protected
    function WideToAnsi(sStr: WideString): AnsiString; override;
    function AnsiToWide(sStr: AnsiString): WideString; override;
  end;
//------------------------------------------------------------------------------
{TMyWStringList}
function TMyWStringList.WideToAnsi(sStr: WideString): AnsiString;
begin
  Result := gfnsWideToUtf7(sStr);
end;

function TMyWStringList.AnsiToWide(const sStr: AnsiString): WideString;
begin
  Result := gfnsUtf7ToWide(sStr);
end;


//------------------------------------------------------------------------------
{TMyUStringList}
function TMyUStringList.WideToAnsi(sStr: WideString): AnsiString;
var
  li_Len: Integer;
begin
  li_Len := Length(sStr) + 1;
  SetLength(Result, li_Len * 2);
  lstrcpynw(PWideChar(PAnsiChar(Result)), PWideChar(sStr), li_Len);
end;

function TMyUStringList.AnsiToWide(sStr: AnsiString): WideString;
var
  lp_Src: PAnsiChar;
  lp_Wide: PWideChar absolute lp_Src;
begin
  lp_Src := PAnsiChar(sStr);
  Result := WideString(lp_Wide);
end;


//------------------------------------------------------------------------------
{TMyWideStringList}
function TMyWideStringList.WideToAnsi(sStr: WideString): AnsiString;
begin
  Result := gfnsWideToAnsiEx(sWStr);
end;

function TMyWideStringList.AnsiToWide(sStr: AnsiString): WideString;
begin
  Result := gfnsAnsiToWideEx(sStr);
end;

WideToAnsiやAnsiToWideはこのように対になる関数を指定します。

Createに二種類あるのはTLisbBoxやTComboBoxのItemなどを間接的にTMyWStringsで管理するためです。

constructor TMyWStrings.Create;
begin
  Create(nil);
 //↓のCreateを呼んでいる
end;

constructor TMyWStrings.Create(slList: TStrings);
begin
  if (slList = nil) then begin
    F_slList := TStringList.Create;
  end else begin
    F_slList := slList;
  end;

使い方をみてみます。

var
  List1, List2: TMyWStrings;
begin
  List1 := TMyWStringList.Create;
  List2 := TMyWStringList.Create(ListBox1.Items);

List1はそれ自身でリストを保持します。TMyWStringsのF_slListをTStringListでCreateしてリストを保持しています。

List2はListBox1のItemsにリストを保持します。List2を通してListBox1のItemsを操作する感じです。
List2のようにすればTListBoxやTComboBoxのItemsをTMyWStringsを通してWideStringのやり取りが直接できるので、わざわざ変換関数を使う手間が省けます。
この場合リストへの追加や削除はListBox1.Items.Add(sStr);などとするのではなくList2.Add(sStr);とします。List2を通して間接的にListBox1のItemsを操作するわけです。
ただ、使う前にTMyWStringsで変数を宣言してTMyWStringListでCreateして使い終わったらFreeしないといけないのでその分の手間は増えます。

プロパティ

基本的にTStringsやTStringListのプロパティやメソッドに準拠するように実装しています。
一部省略しているものや逆につけ足しているものもあります。
後ろに「※」印のついてるものがつけ足しであったり少し機能が違うものです。

メソッド


2009-02-01:
  SaveToFileの文字コード指定にcdAuto追加。
  InStringOf, InHeadStringOfメソッド追加。
2008-07-16:
  プロパティ、メソッドの一覧を追記。
2008-06-13: