Revit APIでダイアログの値を次開く時まで保持する方法

# はじめに
Revitでアドイン開発をする際、ダイアログに入力した値を保持しておきたいがやり方が分からない、というときの手順です。
調べるとProperties.Settings.Defaultなど色々出てきたのですが、これは「[拡張ストレージ](https://www.revitapidocs.com/2015/79486a74-376c-9555-c873-45d5a750f051.htm)」というやつにXML形式で読み書きするのが便利だそうです。
「拡張ストレージ」については調べても上級者向けの解説しかなかったため、次に使うときのために使い方のメモを残します。
使い方のメモなので、詳しい定義や意味までは追いません。
問題がある場合はコメントください。
# 環境
OS: Windows8.1
Revit: 2019.2
# 実装
## ダイアログの中身をXML形式で保存
ここはおのおの楽な方法でデータを作成します。
XML形式でといっても保存時には文字列型に`ToString()`するため、XML形式には限りません。
XMLが楽だと思われますが、他の形式の方が使いやすいという方は別の方法で文字列作成していいと思います。

## 拡張ストレージへの保存
### トランザクションの開始と終了
拡張ストレージに保存するにはTransactionを開始する必要があります。（読み込み時には不要です）
TransactionはDocumentから取得することができ、DocumentはApplicationから取得することができます。→[参考](https://kizarukun.com/revitapi-element-properties)
以下参考サイトのDocument取得部分のコピペ。

```C#
UIApplication uiapp = this.ActiveUIDocument.Application;
UIDocument uidoc = uiapp.ActiveUIDocument;
Autodesk.Revit.ApplicationServices.Application app = uiapp.Application as Autodesk.Revit.ApplicationServices.Application;
Autodesk.Revit.DB.Document doc = uidoc.Document;
```

拡張ストレージへ保存する際のトランザクションの開始と終了の流れは以下の形になります。
` /* ここで書き込み実行 */`の部分で書き込み処理を行います（後述）。見辛いので書き込みは関数にした方が良いと思います。
エラー表示の部分は必要に応じてエラーを表示したりログに出力したりしてください。

```C#
// 拡張ストレージに保存
using (Transaction transaction = new Transaction(doc)) {
  if (transaction.Start("ExtensibleStorage") == TransactionStatus.Started) {
    try {
      /* ここで書き込み実行 */

      if (transaction.Commit() != TransactionStatus.Committed) {
        // コミットに失敗した時のエラー（Rollbackが必要）
        transaction.RollBack();
      }
    }
    catch (Exception ex) {
     // 途中で失敗した際のエラー（Rollbackが必要）
     transaction.RollBack();
    }
  }
  else {
    // トランザクションの開始に失敗した時のエラー
  }
}
```

### 拡張ストレージの読み書き
読み書きにあたっての公式のDocは[ここ](https://www.revitapidocs.com/2015/e74f9357-cc3c-558e-73b8-38ce6d247869.htm)。
参考URLは[ここ](https://spiderinnet.typepad.com/blog/2012/02/revit-api-2012-extensible-storage-revit-element-data-extension.html)等。

#### 書き込み
書込みにあたっての大まかな流れは以下です。
1. SchemaBuilder を宣言する。（ある場合4へ）
2. SchemaBuilder に権限とスキーマ名を設定する。
3. SchemaBuilder にフィールド名を追加する。
4. Schema を取得。
4. docの持つProjectInformationに対して、このSchemaでEntityを取得する（無い場合作成）
5. Entityに登録したいXMLを文字列型としてセットする。
6. ProjectInformationに5のEntityをセットする。

```C#
// まず、スキーマがあるかどうかを判定
Schema sch = Schema.Lookup(new Guid("適当なGUID"));
  if(sch == null){
  SchemaBuilder sb = new SchemaBuilder(new Guid("適当なGUID"));
  // 権限等
  sb.SetReadAccessLevel(AccessLevel.Public); // Public/Vendor/Applicationの3種類あります。とりあえずPublicで。
  sb.SetWriteAccessLevel(AccessLevel.Public);
  // スキーマ名
  sb.SetSchemaName("適当なスキーマ名");
  // フィールド名設定
  sb.AddSimpleField("適当なフィールド名", typeof(string));
  // スキーマを取得
  sch = sb.Finish();
}
// Entityを取得
Entity ent = doc.ProjectInformation.GetEntity(sch);
// XMLを設定
ent.Set<FieldType>("適当なフィールド名", xmldata.ToString());
// Docに設定
doc.ProjectInformation.SetEntity(ent);
```

これで、ダイアログのOKボタンを押した時にこれが実行されるようにすればOKです。

#### 読み込み
読み込みの方はいたってシンプルで、
1. Schema を取得
2. あればProjectInformationに対してSchemaの中のフィールド名からEntityをゲット
です。

ここではジェネリッククラスTを使います。
引数の`Element `は`doc.ProjectInformation`、`strGUID`は書き込み時の「適当なGUI」、`strFieldName`は「適当なフィールド名」にそれぞれ読み替えてください。

```C#
public static T GetData<T>(this Element elm, string strGUID, string strFieldName) {
  Schema sch = Schema.Lookup(new Guid(strGUID));
  // そもそも無い時
  if (sch == null) {
    return default(T);
  }
  // 有効な値が無い時
  if (!elem.GetEntity(sch).IsValid()) {
    return default(T);
  }
  T entity = elem.GetEntity(sch).Get<T>(strFieldName);
  return entity;
}
```
処理としてはこれだけです。
stringで保存したので、呼び出すときは以下のように`<string>`とします。

```C#
string strXMLdata = GetData<string>(...);
```
これで、ダイアログを開くときに読み込んだstrXMLdataを反映させていけばOKです。


# 感想
Revit関係はググっても英語のページばかりだなあと思いました。