戻る ホーム 上へ 進む

集合と文字列の相互変換

Tip & 解説

集合型を文字列に変換したり、逆に文字列を集合に変換したいときがあるでしょう。例えば、文字のスタイル(Canvas.Font.Style)を文字列として設定ファイル(*.ini)にセーブしたりロードしたりするときです。

Font.Style のような、オブジェクトの published な集合型のプロパティの場合は TypInfo ユニット中にある SetToString, StringToSet というルーチンで変換できます。しかし、集合型の変数や値を相手にするルーチンは用意されていません。

例えば TTreeView のGetHitTestInfoAtなど、結果を集合で返すメソッドはいろいろありますが、返された集合値を文字列として表示、セーブしようと思っても、簡単にはできません。

しかし、集合型の型情報(TTypeInfo)を利用することで、文字列と集合を相互に変換するルーチンを容易に書くことができます。

注意: 下記ルーチンは要素数32個までの集合に使えます。
    また、数値のみが要素の集合は、実行時型情報を持たないので
    使えません。

unit SetUtils;

interface

uses TypInfo;

// 集合→文字列変換
function SetToStr(SetType: PTypeInfo; const SetValue): string;
// 文字列→集合変換
procedure StrToSet(SetType: PTypeInfo; str: string; var SetVar);


implementation

uses Windows, SysUtils;

function SetToStr(SetType: PTypeInfo; const SetValue): string;
var
  pd: PTypeData;
  CompType: PTypeInfo;
  i: Integer;
  pi: PInteger;
begin
  pd := GetTypeData(SetType);
  Assert(pd <> Nil);
  CompType := pd.CompType^;
  Assert(CompType <> Nil);
  pd := GetTypeData(CompType);
  Assert(pd <> Nil);

  pi := @SetValue;

  Result := '';

  for i := pd.MinValue to pd.MaxValue do
    if (pi^ and (1 shl i)) <> 0 then
      if Result = '' then Result := '[' + GetEnumName(CompType, i)
                     else Result := Result + ', ' +
                                    GetEnumName(CompType, i);
  Result := Result + ']';
end;

procedure StrToSet(SetType: PTypeInfo; str: string; var SetVar);
var
  pd: PTypeData;
  CompType: PTypeInfo;
  i: Integer;
  pdw: PDWORD;
  pw: PWORD;
  pb: PByte;
  p: PChar;
  EnumName: string;
  ENumValue: Integer;
begin
  pd := GetTypeData(SetType);
  Assert(pd <> Nil);
  CompType := pd.CompType^;
  Assert(CompType <> Nil);
  pd := GetTypeData(CompType);
  Assert(pd <> Nil);

  pdw := @SetVar;
  pw  := @SetVar;
  pb  := @SetVar;

  case pd.OrdType of
  otSByte, otUByte: pb^ := 0;
  otSWord, otUWord: pw^ := 0;
  otSLong, otULong: pdw^ := 0;
  end;


  p := PChar(Str);

  // '[' と ' ' をスキップ
  while p^ in ['[',' '] do Inc(p);

  // ','  ' ' #0 ']' までを要素名として取り出す
  i := 0;
  while not (p[i] in [',', ' ', #0,']']) do Inc(i);
  SetString(EnumName, p, i);
  // 次の語の先頭までポインタを進める
  while p[i] in [',', ' ',']'] do Inc(i);
  Inc(p, i);


  while EnumName <> '' do
  begin
    EnumValue := GetEnumValue(CompType, EnumName);
    if EnumValue < 0 then
      raise Exception.Create('集合→文字列 変換エラー');
    case pd.OrdType of
    otSByte, otUByte: pb^ := pb^ or (1 shl EnumValue);
    otSWord, otUWord: pw^ := pw^ or (1 shl EnumValue);
    otSLong, otULong: pdw^ := pdw^ or (1 shl EnumValue);
    end;
    // ','  ' ' #0 ']' までを要素名として取り出す
    i := 0;
    while not (p[i] in [',', ' ', #0,']']) do Inc(i);
    SetString(EnumName, p, i);
    // 次の語の先頭までポインタを進める
    while p[i] in [',', ' ',']'] do Inc(i);
    Inc(p, i);
  end;
end;

end.

使用例: TreeView の GetHitTestInfoAt の戻り値を文字列で表示します。

procedure TForm1.Timer1Timer(Sender: TObject);
var
  pt: TPoint;
  t: THittests;
  str: string;
begin
  pt := Mouse.CursorPos;
  pt := TreeView1.ScreenToCLient(pt);
  t := TreeView1.GetHitTestInfoAt(pt.x, pt.y);
  Memo1.Lines.Add(SetToStr(TypeInfo(THitTests), t));
end;

戻る ホーム 上へ 進む

inserted by FC2 system