Trac 投稿専用の Windows アプリケーションを作る

前回、Trac に XML-RPC 経由でチケットを投稿する | Moonmile Solutions Blog で、コマンドライン版ができたので、これを windows アプリケーションから作ります。

通常、Unix/Linux の場合には、コマンド呼出をアプリケーションの内部に持って、パイプでつなげるという手法を取ります。windows アプリの場合でも、コマンドラインツールに GUI をくっ付ける場合は、そうします。
また、ライブラリとして切り出すことも可能なのですが…実は、.NET の場合には、直接コマンドラインツールを参照設定することで、ツール内にあるクラス(今回は、TracTools クラス)を使えます。クラスライブラリは DLL に限らず、普通の exe ファイルでも使えるのです…という例ですね。

今までは、COM で作ったり、DLL で関数を共通化したりという小細工が必要(設計段階で考えなくちゃいけなかったという意味で)になるのですが、.NET の場合は、exe を直接クラスライブラリとして扱えるので、適当なクラス分けさえしてあれば、windows アプリケーションとして動作させるのも簡単なのです。

# まぁ、コマンドラインツールのほうで、適度にクラス化が必要なわけですが。そのあたりが、UIDD(User Interface Driven Development)に繋がります。

windows アプリのコードは以下のようにさっくりと、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
namespace TracWin
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            _trac.Setting.Url = "http://localhost:8000/trac/gokui-ios5/login/rpc";
            _trac.Setting.UserName = "masuda";
            _trac.Setting.Password = "masuda";
 
        }
 
        private string _Repoter = "masuda";
        private TracTools _trac = new TracTools();
        private Ticket _ti = null;
 
        /// <summary>
        /// 新規作成
        /// テキストボックス等をクリアする
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonNew_Click(object sender, EventArgs e)
        {
            textBoxID.Text = "";
            textBoxID.Enabled = true;
            textBoxSummary.Text = "";
            textBoxRepoter.Text = _Repoter;
            textBoxOwner.Text = "someone";
            textBoxDescription.Text = "";
            textBoxAction.Text = "";
            textBoxActionValue.Text = "";
            _ti = null;
        }
 
        /// <summary>
        /// 指定IDのチケットを読み込む
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonGet_Click(object sender, EventArgs e)
        {
            if (textBoxID.Text == "")
                return;
            int id = int.Parse(textBoxID.Text);
            Ticket ti = _trac.TicketGet(id);
 
            textBoxID.Enabled = false;
            textBoxSummary.Text = ti.Summary;
            textBoxRepoter.Text = ti.Reporter;
            textBoxOwner.Text = ti.Owner;
            textBoxDescription.Text = ti.Description;
            _ti = ti;
        }
        /// <summary>
        /// チケットを書き込む
        /// IDが空白の場合は、新規登録。
        /// IDが空白でない場合は、更新登録。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonPost_Click(object sender, EventArgs e)
        {
            if (textBoxID.Text == "")
            {
                // 新規登録
                Ticket ti = new Ticket();
                ti.Summary = textBoxSummary.Text;
                ti.Description = textBoxDescription.Text;
                ti.Reporter = textBoxRepoter.Text;
                ti.Owner = textBoxOwner.Text;
                int id = _trac.TicketCreate(ti);
                textBoxID.Text = id.ToString();
                _ti = ti;
                textBoxID.Enabled = false;
            }
            else
            {
                // 更新登録
                int id = int.Parse(textBoxID.Text);
 
                Ticket ti = _ti;
                ti.Description = textBoxDescription.Text;
                ti.Owner = textBoxOwner.Text;
                _ti = _trac.TicketUpdate(ti);
                textBoxID.Enabled = false;
            }
        }
        /// <summary>
        /// 指定IDのチケットを削除
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonDelete_Click(object sender, EventArgs e)
        {
            if (_ti == null)
                return;
            _trac.TicketDelete(_ti.ID);
            textBoxID.Text = "";
            textBoxSummary.Text = "";
            textBoxRepoter.Text = _Repoter;
            textBoxOwner.Text = "someone";
            textBoxDescription.Text = "";
            textBoxAction.Text = "";
            textBoxActionValue.Text = "";
            textBoxID.Enabled = true;
            _ti = null;
        }
 
        /// <summary>
        /// アクションを更新
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonAction_Click(object sender, EventArgs e)
        {
            if (_ti == null)
                return;
            string act = textBoxAction.Text;
            _ti = _trac.TicketUpdate(_ti, act);
        }
    }
}

画面もチープな感じでよいので、こんな感じ。

こんな風にプロトタイプを作る場合には、

  1. コマンドラインツールで動作確認
  2. 適度にクラス分けをしておく(特に呼び出し形式に注意)
  3. チープな画面で windows アプリとして作る

という流れで作ります。NUnit が絡むところは、1 と 2 の間ぐらいなのですが、1 で動作確認をしていると NUnit を使わなくてもそこそこ動作ができます。いわゆるコードの品質が上がります。
windows アプリを作る 3 の段階では、既に内部の動作確認済みなので、GUI との結合だけを確認します。
そして、その後、画面をリッチにしたり、ボタンの制御(不要なときは押せないとか)の処理を入れていきます。

こういう風に組み立てていくと、不思議な動作による手戻り…というかバグ解析が減るので、結構効率があがりますよ、という話です。あと、今回の trac を xml-rpc でアクセスするような内部動作がいまいち不明な場合、try and error が必要な場合は、こんな風に手順よくやっていったほうが結果的に早く終わります。

カテゴリー: 開発, C#, Trac パーマリンク