C++Builder XEでRTTIユニットを使う

DelphiのRTTI(実行時型情報)を復習する。その2(RTTIユニット)の記事をC++Builderで書いてみました。

■サンプルプログラム:TButtonの全プロパティを表示する

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  //RTTI.TRttiContextを作成
  TRttiContext context;
  //TButtonの型情報を取得
  TRttiType* rttiType = context.GetType(__classid(TButton));
  //TButtonの全プロパティの配列を取得
  DynamicArray<TRttiProperty*> properties  = rttiType->GetProperties();
  //プロパティをListBox1に登録
  for (int i = 0; i < properties.Length; ++i)
  {
    ListBox1->Items->Add(properties[i]->ToString());
  }
}

全プロパティが表示された。

■サンプルプログラム:TButtonの全メソッドを表示する

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  //RTTI.TRttiContextを作成
  TRttiContext context;
  //TButtonの型情報を取得
  TRttiType* rttiType = context.GetType(__classid(TButton));
  //TButtonの全メソッドの配列を取得
  DynamicArray<TRttiMethod*>  method  = rttiType->GetMethods();
  //メソッドをListBox1に登録
  for (int i = 0; i < method.Length; ++i)
  {
    ListBox1->Items->Add(method[i]->ToString());
  }
}

全メソッドが表示された。

■サンプルプログラム:Button1のCaptionの値を取得する

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  //RTTI.TRttiContextを作成
  TRttiContext context;
  //TButtonの型情報を取得
  TRttiType* rttiType = context.GetType(__classid(TButton));
  //TButtonのCaptionプロパティを取得
  TRttiProperty* property = rttiType->GetProperty("Caption");
  //Button1のCaptionプロパティの値を取得
  UnicodeString caption = property->GetValue(Button1).AsString();
  //ListBox1に登録
  ListBox1->Items->Add(caption);
}

Button1のCaptionプロパティの値がListBoxに表示された。

■サンプルプログラム:Button1のCaptionの値を設定する

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  //RTTI.TRttiContextを作成
  TRttiContext context;
  //TButtonの型情報を取得
  TRttiType* rttiType = context.GetType(__classid(TButton));
  //TButtonのCaptionプロパティを取得
  TRttiProperty* property = rttiType->GetProperty("Caption");
  //Button1のCaptionプロパティの値を設定
  property->SetValue(Button1, TValue::FromVariant("ぼたん"));
}

ボタンのキャプションが変わった。

■サンプルプログラム:メソッドを実行する

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  //RTTI.TRttiContextを作成
  TRttiContext context;
  //TStringsの型情報を取得
  TRttiType* rttiType = context.GetType(__classid(TStrings));
  //TStringsのAddメソッドを取得
  TRttiMethod* method = rttiType->GetMethod("Add");
  //TStrings.Addを実行
  for (int i = 1; i < 4; ++i)
  {
    TValue value[1] = { TValue::FromVariant("テスト" + IntToStr(i)) };
    method->Invoke(ListBox1->Items, value, 0);
  }
}

ListBoxに追加された。

TCustomForm.UpdateActionsを試してみる

「DEKOのざつだん。」の11/10/10の記事TCustomForm.UpdateActionsの紹介がありました。
おもしろそうだったので、試してみました。

フォームにメニュー、ツールバー、チェックボックスを2つ配置します。
メニュー、ツールバーのボタン、チェックボックスの1つにActionプロパティにAction1を指定します。

UpdateActionsメソッドをオーバーライトします。

下側のチェックボックス(CheckBox1)のチェックの状態によって、Action1のEnabledプロパティを変更するようにします。

Unit1.h

class TForm1 : public TForm
{
...
protected:
  virtual void __fastcall UpdateActions(void);
};

Unit1.cpp

void __fastcall TForm1::UpdateActions(void)
{
  Action1->Enabled = CheckBox1->Checked;
}

CheckBox1をチェックすると、Action1にひも付いているメニュー、ツールバーのボタン、チェックボックスの状態が更新されます。

もちろんCheckBox1のOnClickイベントに記述しても同じことができます。
ですが、UpdateActionsメソッドにアクションの更新処理をまとめて記述することで、ソースコードの見通しが良くなりそうだと思いました。

チェックボックスをもう一つ追加したところ。
アクションの状態はUpdateActionsメソッドの中で一括管理。

void __fastcall TForm1::UpdateActions(void)
{
  Action1->Checked = CheckBox1->Checked;
  Action2->Checked = CheckBox4->Checked;
}

DelphiでCSVファイルを作成するクラス

ビフォアーアフター(CSV出力機能編) – TIM Labs」を読んで、CSVファイルの作成処理を見直すことにしました。

問題

該当する出力機能のソースコードを発見したあなたは何とも言えない気持ちになりました。
具体的には以下のようなソースコードになっていたからです (※掲載にあたり比較的読み易くなるよう整理してあります):

ビフォアーアフター(CSV出力機能編) – TIM Labs」より

ここで紹介されているようなソースコードが、私のプログラムにもあります。

CSVファイルの作成処理を見直すことにしました。

■願望

以下のような形で記述できるようにします。

var
  FieldList: TList<TPair<String, String>>; //列名とプロパティ名のペア
  Staffs: TObjectList<TStaff>; //保存する社員データ
begin
  //社員一覧
  Staffs := GatStaffs();

  //CSVファイルに出力する列名とプロパティ名
  FieldList := TList<TPair<String, String>>.Create;
  FieldList.Add(TPair<String, String>.Create('社員番号', 'Id'));
  FieldList.Add(TPair<String, String>.Create('名前', 'Name'));
  FieldList.Add(TPair<String, String>.Create('メールアドレス', 'Email'));

  //CSVファイルに保存する
  TCsv<TStaff>.ExportFile('C:\\Staffs.csv', FieldList, Staffs);

CSVファイルに出力する列名とプロパティ名のペアはもっと簡単にかけそうな気がしますが、方法がわかりませんでした。

■実装

まずは社員クラス。

ID,Name,Emailのプロパティを持ちます。

TStaff = class
private
  FId: String;
  FName: String;
  FEmail: String;
public
  constructor Create(Id: String; Name: String; Email: String);
  property Id:String read FId;
  property Name:String read FName;
  property Email:String read FEmail;
end;

次に社員のリストを返す関数。

function GatStaffs(): TObjectList<TStaff>;
begin
  Result := TObjectList<TStaff>.Create();
  Result.Add(TStaff.Create('1', '鈴木', 'suzuki@example.com'));
  Result.Add(TStaff.Create('2', '田中', 'tanaka@example.com'));
  Result.Add(TStaff.Create('3', '木村', 'kimura@example.com'));
end;

さて、本題のCSVファイルに保存する処理です。

総称型を使う関数は、グローバル関数では作成できません。
TCsvクラスのクラス関数として定義しました。

TCsv<T: class> = class
private
  //オブジェクトのプロパティの値を取得する
  class function GetFieldValue(Obj: T; PropName: String): String;
public
  //CSVファイルに出力する
  class procedure ExportFile(
    FileName: String;  //出力するファイルのファイル名
    FieldList: TList<TPair<String, String>>; //出力する列名と値
    List: TList<T>); //出力データのリスト
end;

プロパティ名からオブジェクトのプロパティの値を取得する関数です。

総称型Tからプロパティの値を取得できなかったため、一度TObject型に変換しています。

// @param Obj オブジェクト
// @param PropName プロパティ名
// @return オブジェクトのプロパティの値
class function TCsv<T>.GetFieldValue(Obj: T;
  PropName: String): String;
var
  Context: TRttiContext;
  RttiType: TRttiType;
  RttiProperty: TRttiProperty;
  C: TObject;
begin
  if not (Obj is TObject) then Exit;

  C := Obj;
  Context := TRttiContext.Create;
  RttiType := Context.GetType(C.ClassType);
  RttiProperty := RttiType.GetProperty(PropName);
  Result := RttiProperty.GetValue(C).AsString;
end;

CSVファイルに出力する関数です。

ファイルの保存にはTStringList.SaveToFileを使っています。
CSVファイルのサイズが大きい時は、もう一工夫が必要でしょう。

class procedure TCsv<T>.ExportFile(FileName: String;
  FieldList: TList<TPair<String, String>>; List: TList<T>);
var
  Csv: TStringList;
  Row: TStringList;
  Field: TPair<String, String>;
  Obj: T;
begin
  Csv := TStringList.Create;
  Row := TStringList.Create;

  //見出し
  for Field in FieldList do
  begin
    Row.Add(Field.Key);
  end;
  Csv.Add(Row.CommaText);

  //値
  for Obj in List do
  begin
    Row.Clear;
    for Field in FieldList do
    begin
      Row.Add(GetFieldValue(Obj, Field.Value));
    end;
    Csv.Add(Row.CommaText);
  end;
  Csv.SaveToFile(FileName);
  Row.Destroy;
  Csv.Destroy;
end;

オブジェクトからプロパティの値を取得するのに、今回はRTTIを使用しました。
他に、無名メソッドを使う方法も考えられます。
無名メソッドの方が柔軟性が高いかもしれません。

PHPの配列から要素を削除する方法

PHPの配列から要素を削除する方法

■要素を1つだけ削除する

要素を1つだけ削除するには、unsetを使う方法が簡単で効率的です。

#配列から要素を削除する
unset(配列から削除する要素)

$myarray = array(
  'C++' => 'ビャーネ・ストロヴストルップ',
  'Perl' => 'ラリー・ウォール',
  'Python' => 'グイド・ヴァンロッサム',
  'Ruby' => 'まつもとゆきひろ');

#キーがRubyの要素を削除する
unset($myarray['Ruby']);

実行結果

array(3) {
  ["C++"]=>
  string(42) "ビャーネ・ストロヴストルップ"
  ["Perl"]=>
  string(24) "ラリー・ウォール"
  ["Python"]=>
  string(33) "グイド・ヴァンロッサム"
}

キーが’Ruby’の要素が削除されています。

■要素を複数削除する

複数の要素を削除する時は、array_spliceを使用します。

#インデックスの位置以降の要素をすべて削除する
array_splice(配列, インデックスの位置)

$myarray = array(
  'C++' => 'ビャーネ・ストロヴストルップ',
  'Perl' => 'ラリー・ウォール',
  'Python' => 'グイド・ヴァンロッサム',
  'Ruby' => 'まつもとゆきひろ');

#2番目以降の要素をすべて削除する
array_splice($myarray, 2);

実行結果

array(2) {
  ["C++"]=>
  string(42) "ビャーネ・ストロヴストルップ"
  ["Perl"]=>
  string(24) "ラリー・ウォール"
}

第3引数を指定すると、削除する要素の個数を指定できます。

#インデックスの位置以降の要素を指定個数削除する
array_splice(配列, インデックスの位置, 削除する要素の数)

$myarray = array(
  'C++' => 'ビャーネ・ストロヴストルップ',
  'Perl' => 'ラリー・ウォール',
  'Python' => 'グイド・ヴァンロッサム',
  'Ruby' => 'まつもとゆきひろ');

#1番目以降の要素を2個削除する
array_splice($myarray, 1, 2);

実行結果

array(2) {
  ["C++"]=>
  string(42) "ビャーネ・ストロヴストルップ"
  ["Ruby"]=>
  string(24) "まつもとゆきひろ"
}