PDCA支援ツールを作ろう その５ ダイアログからデータを受けとる/XML形式でデータを保存する

いやぁ、Oculus Go は強敵でしたね･･･（時間泥棒的な意味で）

どうも、白鳥です。
御題を決めて短期間で開発しよう！というコンセプトではじめた"ワンドロ"開発の一発目から未完成で終わるのは流石にやばいと思って
ガーっと作業してました。
着手がそもそも遅かったのはナイショだよっ！

で、いろいろ足りないものを追加したりして、ひとまず完成です。
やった作業はこんな感じ

* 新しいPDCAケースの作成時、プロジェクト/ケース名を入力するダイアログを表示し、入力データを取得する処理の追加
* 上記入力ダイアログの追加
* 入力内容をXML形式で出力する機能の追加
* 出力したデータを読み込んでフォームに反映する処理の追加
* 未実装のイベントを一通り実装
* ステータス遷移時、ラジオボタンとリッチテキストが適切な状態で表示されるように調整

で、全部事細かに書くのもアレなので、タイトルにある２つの内容をピックアップ。

# ダイアログを表示してデータを取得する

さて、ボタンを押したらポンとダイアログが出てデータ入力を求められる、というのは結構見ることがあると思います。
これが正解！ってのは無いと思いますが、今回はこんな手順で実装しました。

1. ダイアログとして表示するフォームを作成する。
1. ダイアログとやりとりしたい情報をプロパティとして実装する。
1. ダイアログとしての挙動を設定する。

これだけ。シンプル！

さて、順を追って説明しましょ。

## ダイアログとして表示するフォームを作成する。

~~あれ？これ説明要る？~~

とりあえずフォームを新規作成して手ごろな大きさ（今回は400x200）に調整。
ボタンやらラベルやらを乗せていって、見た目をこんな感じに。

![新規作成ダイアログ](https://qiita-image-store.s3.amazonaws.com/0/259679/eb617f8f-dbb8-1384-af02-781807f1d287.jpeg)

## ダイアログとやりとりしたい情報をプロパティとして実装する。

見た目かモロバレですが、Project名とCase名の情報をやりとりしたいので、





ダイアログとして表示してるので、Enter/Escキー押下時に反応するように、
FormのAcceptButton/CancelButtonプロパティにそれぞれのボタンを設定。
該当するプロパティをこんな感じに設定

```CaseSelectDialog.cs
        [Browsable(false)]
        internal string caseName { get; set;}
        [Browsable(false)]
        internal string projName { get; set; }
```

この_[Browsable(false)]_ってのは、VisualStudioのプロパティウィンドウに表示しないでね！って命令を出してます。
属性ってやつです。Attributeですね。ここはあんま関係ないので割愛。

"internal"はアクセス修飾子です。Cのときはなかったと思うんだよなー。
同じプロジェクト(アセンブリ)の中からのみアクセス可能。
詳細は[こちら(MSDNの該当ページへ)](https://msdn.microsoft.com/ja-jp/library/ba0a1yw2(v=vs.120).aspx)

で、このプロパティをOKボタン押下時にここのテキストボックスから引っ張ってやることにします。
![このテキストボックス.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/79ab6358-4dc0-25e0-ba91-96c789cb2146.jpeg)


ちなみにコントロールはデザイナで設定した場合、以下のようにprivateで宣言されてるのでほかのクラスからは触れません。

```CaseSelectDialog.Designer.cs

        private System.Windows.Forms.TableLayoutPanel tblWhole;
        private System.Windows.Forms.TableLayoutPanel tblPCNames;
        private System.Windows.Forms.Panel pnlCase;
        private System.Windows.Forms.TextBox txtCase;
        private System.Windows.Forms.Label lblCase;
        private System.Windows.Forms.Panel pnlProject;
        private System.Windows.Forms.TextBox txtProject;
        private System.Windows.Forms.Label lblProject;
        private System.Windows.Forms.Label lblDialogDetails;
        private System.Windows.Forms.TableLayoutPanel tlpButton;
        private System.Windows.Forms.Button btnOK;
        private System.Windows.Forms.Button btnCancel;

```

アクセス修飾子書き換えて触るって出来るのかなー？
あんまよろしくない実装な気がする。独立性の観点から。

## ダイアログとしての挙動を設定する。

上記のフォームはダイアログとして出してるので、Enterキー押下時とEscキー押下時の挙動を設定したい。

と、思ったらフォームにちょうどいいプロパティが用意されてたので使わせてもらいました。

それが以下のIButtonControl型のプロパティ。
System.Windows.Forms.Form.AcceptButtonとSystem.Windows.Forms.Form.CancelButton。
それぞれ、Form上でEnterKeyを押した場合とEscKeyを押した場合に各々のButton.Clickイベントを呼び出してくれます。

しかもそれぞれ呼び出し元にDialogResult.OK/DialogResult.Cancelを返却してくれる細やかさ。

後はテキストに入れたデータをいただくだけ･･･なんですが、これ。
実はちょっと詰まりました。

最初はこんな風に記述して動作を見てみたんです。

```Main.cs
        private void createCaseDialog(string pStr = "") {

            CaseSelectDialog csd = new CaseSelectDialog();

            if (csd.ShowDialog() == DialogResult.OK) {

                lblProjectName.Text = csd.projName;
                lblCaseName.Text = csd.caseName;

            }...

```

_これがね、取れないの。_

いろいろ調べてみたところ、
"自前でForm.DialogResultプロパティの設定を変えない場合、自動的にDispose()されてデータが破棄される"
ということが判明。

急遽CaseSelectDialog.csのbtnOK.Clickイベントに紐付けたメソッドにDialogResultに関する処理を追記。
（オマケに呼び出し元でダイアログをDispose()する処理を追記）

```CaseSelectDialog.cs
        private void btnOK_Click(object sender, EventArgs e) {

            caseName = txtCase.Text;
            projName = txtProject.Text;

            DialogResult = DialogResult.OK;
            return;
        }
```

```Main.cs
        private void createCaseDialog(string pStr = "") {

            CaseSelectDialog csd = new CaseSelectDialog();

            ...(中略)...

            csd.Dispose();

            currentStatus = STATUS_PROGRESS_PLAN;

            return;
        }

```

すると今度はしっかりデータを取得できるようになりました。
これでダイアログはOKです。


# XML形式でデータを保存する

これはDobon.net様の記事が大変参考になりました。

オブジェクトの内容をXMLファイルに保存 - DOBON.NET
https://dobon.net/vb/dotnet/file/xmlserializer.html

上の記事読んでいただければ詳細なことはそこに書いてあるんですが
せっかくなので初学者向けにザックリ。

XMLでデータを入出力するには次の３ステップが必要です。

1. XMLに吐き出したいものを構造体として定義する。
1. 必要なモノをusingディレクティブ(ソースの一番上にあるusing～って書いてあるとこ)に追加する。
1. データの入出力に必要なクラスと、XML形式に調整するためのクラスのインスタンスをNew!して作ってデータをやりとりする。

これだけ。
各々のやり方はこんな感じ。

## XMLに吐き出したいものを構造体として定義する。

```Main.cs
        public struct pdcaData {
            public int status;
            public string strPlan;
            public string strDo;
            public string strCheck;
            public string strAct;
        }
```


## 必要なモノをusingディレクティブ(ソースの一番上にあるusing～って書いてあるとこ)に追加する。

今回必要なのは次の２つ。

| name space | 用途 |
|:--|:-:|
| System.IO | データを書き込んだり読み込んだりするために必要なやつ。度々お世話になる。  |
| System.Xml.Serialization  | データをXML形式にまとめてくれる子。XMLからデータを引っ張りだすときも活躍してくれる。  |

で、これをこんな風にusingディレクティブに追加。

```Main.cs
using System.IO;
using System.Xml.Serialization;
```

## データの入出力に必要なクラスと、XML形式に調整するためのクラスのインスタンスをNew!して作る。

ここで必要になるインスタンスは全部で３つ。
とはいえ、入出力に分けるとそれぞれで２つずつね。１つは共通。

出力したい場合

| クラス | 用途 |
|:-:|:-:|
| StreamWriter  |  データを書き込むためのストリームを開いてくれる子。基本ファイルに吐き出すときはお世話になる。 |
| XmlSerializer  |  データをオブジェクトに変えてくれる子。逆も出来る。 |

入力したい場合

| クラス | 用途 |
|:-:|:-:|
| StreamReader  |  データを読み込むためのストリームを開いてくれる子。基本ファイルを読み込むときはお世話になる。 |
| XmlSerializer  |  オブジェクトをデータに変えてくれる子。逆も出来る。 |

これをこんな感じでインスタンス化します。
引数の指定があるから気をつけて。

出力の場合

```Main.cs
            pdcaData pdcaData = new pdcaData();
            StreamWriter streamWriter = null;
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(pdcaData));

            try {

                streamWriter = new StreamWriter(  出力先のパス  , false, new UTF8Encoding(false));
                xmlSerializer.Serialize(streamWriter, pdcaData);

```

入力の場合

```Main.cs
            pdcaData ret = new pdcaData();

            ...(中略)...

            StreamReader streamReader = null;
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(pdcaData));

            try {

                streamReader = new StreamReader( 入力元のパス , new UTF8Encoding(false));
                ret = (pdcaData)xmlSerializer.Deserialize(streamReader);

```

StreamWriterはインスタンス作るときに"出力先のパス"、"ファイルの後ろに継ぎ足すか、丸々上書きするか"、"文字エンコードはどれを使うか"の情報が必要で、

StreamReaderはインスタンス作るときに"入力元のパス"、"文字エンコードはどれを使ってるのか"の情報が必要。

XmkSerializerはインスタンス作るときに"XMLを作る/XMLを読み解くもととなる構造体の型情報"が必要。

ってポイントに注意する必要があります。
それから、読み取ったデータはobject型なので、必ず型変換（キャスト）が必要ってとこも忘れずに。

ここらへんのポイントを抑えればXMLは結構素直に言うこと聞いてくれます。
後々テキストで見れるのも魅力的だったりします。
これでデータの書き出し/読み込みはOK！

---

ってところで今回の御題についてはおしまい。
作ったソース設計書もあわせてGitにあげてあります。
https://github.com/Shiratori1218/pdcaAssistApp

実はこまいTODOが残ってるんだけど（データ保存時とか工程進める際に確認ダイアログ出すとか）
概ね求めてた機能は実装できたので問題なしです。
次機種検討次機種検討。

作業時間は05:13:21也
今回のツール作成にかかった時間は全部で16:37:63と

あれ、これ真面目にやってれば２日くらいで終わったのか･･･

うーん、いろいろ反省点は残るなぁ。
とりあえずマイルストン設定した方がいいかも？