sci

最果て風呂

C# によるはじめての GUI アプリケーション

まえがき

旧字旧仮名と新字新仮名を相互に変換するスクリプトEmacs で作成してから 1 年が経過しようとしている。これまでの間に辞書を補強しつつ、Ruby 等のスクリプトも作成して Shell 上での変換が可能になった。また、Vim editor でも同じことが出来るようになった。

EmacsVim 上では選択範囲のみの変換が出来たり変換後の単語が色付けされるので、気分的には GUI っぽいプログラムだと思っている。しかし、コマンドを入力しなければならない点を考えるとやっぱり CUI と呼ばざるを得ないのかな。

RubySinatra による Web アプリケーションとして実装することができた。テキストエディタからウェブブラウザに変わっただけだけど、ボタンを押すと何らかのレスポンスが返ってくる。これは立派な GUI アプリと言えるのではないだろうか?

この Web アプリケーションの作成過程において、はじめてオブジェクト指向的な雰囲気を味わったような気がする。概念を理解できていないので、あくまでもイメージでしかないのだけどね。

「メッセージを受けて返答する」ってやつだけど、マクロ的に見て form を使って submit する HTML と構造が似ていると思った。プログラムすべきは「その情報を受けてどのようなデータを返すのか」の部分なので、HTML で送信ボタンの設置を書くだけではなくそれを処理する CGI(エンジン) を作成した経験があれば理解し易いのかもしれない。

事はじめ

Windows 上で手軽に GUI アプリケーションを作成することができるらしいので、Visual Studio 2008 Express をインストールして C# を勉強することにした。IDE の解説を読めば「あいさつダイアログ」のようなものはすぐに作れるのだけど、肝心の処理をする部分は自分で実装しなければならないので、C# 自体を勉強しなければならない。手軽に GUI アプリケーションは作れるのだけど、自分の欲しいものはそう簡単に出来るわけではないのですね。

まずは CUI

ということで、Gushwell さんの「C#プログラミングレッスン 書庫」を Zaurus に入れて風呂に入りながら読むことにした。変数の宣言に型を指定しなければならない等、面倒な手続はあるものの、スクリプト言語に近い感覚で扱えそうな感じだった。

黙読だけではなく音読ということで、まとまった時間に実際に例題を動かしてみた。その時に感じた(不正確)な印象を日記から引用しよう。やりたいこと(例えば外部ファイルの読み込みや配列作成など)は決まっていたので、それに関する記述が多い。

foreach の中で使われる変数(ひとつずつ取り出した要素を代入するための変数ね)にも型を指定しないとダメなんだって。これは面倒。
配列を初期化する際にカンマで要素を区切って指定するけれども、最後に残っても良いんだって。

int [] hoge = {1, 2, 3};
int [] hoge = {1, 2, 3,};
                      ^これ

下の場合は 3 の次に nil か何かが存在して、要素数が 4 の配列になると思っちゃうよね。実際はそうじゃないんだけど。
ファイルを一行ずつ読み込むことはできるようになった。utf-8 のファイルを読み込んでも、ターミナルへの出力は自動で CP932 になるみたい(だからターミナルでは文字化けしない)。つぎは正規表現を使って整形したいのだけどよくわからんなぁ。Regex もオブジェクトにしないとダメとか何なの?
変数への代入は値渡し。配列の場合も値渡し。だけど各要素へは結果的に参照渡し。辞書の作成までは出来た。
文字列の繰り返し置換(破壊的)のやり方がわからん。あ、普通に

line = line.Replace(key[0], key[1]);

のように同じにしておけば良かったのか。これで辞書および入力のファイル名は決め打ちだけど、文字列を単純に置換するプログラムが出来た。スクリプト型ではないバイナリ型というものを作成したのは初めてなんじゃなかろうか?
型をしっかりと指定しなくてはいけないのでかなり苦労した。試行錯誤でたまたま上手くいったようなものなので、スコープ等の内容は理解していない。
前回の実験時よりも語彙数や変換するテキストの文章が多くなっているけれども、処理時間を測ってみると、

real   0m21.579s
user   0m0.047s
sys    0m0.031s

という結果になった。以前の記録を見ると、Python で 70 秒、Lua で 150 秒、Vim で 180 秒掛っているので、かなり速いということになる。が、Emacs の 10 秒には及ばなかった。Emacs についてはあらかじめ起動してあって、バッファに全文を読み込んでいる状態からの時間なので速いのは当たり前なんだけど。
入力するファイルは utf-8 専用なのだが、何故か出力が sjis になってしまう。明示的に変換させないとダメなんだろうか?あとで調べてみよう。
辞書はバイナリと同じ場所に置く仕様にすれば、あとは入力ファイルの指定と変換オプションを付ければ形になる。でもせっかく VSC# を使っているのだから、GUI を付けてみないなぁ、なんて。
自身へのパスを知るためには、参照設定に「using System.Windows.Forms」を追加すること。そこからバイナリ自身の名前を取得するにはどうすれば良いの?Split して Length 取って得るようにした。これで Usage: の表示はオーケー。コマンドライン引数の処理は面倒臭いなぁ。
とりあえずコマンドラインで使えるようになったけど、自分で使う訳じゃない(というか誰も使わない)のでモチベーションが続かない。

ということで、Python 等のスクリプト言語で作成したものとほぼ同等のものを作ることが出来た。引用文の中でも触れているけれど、バイナリ形式のプログラムを作成したのはたぶん初めてのことになる。実際には中間言語というものらしいけどね。

いよいよ GUI

しばらくは C# でもプログラムを作ることができて満足していたのだけど、やっぱり GUI アプリケーションにしてみたくなった。「わんくま系」のブログを読み漁って、最終的には MSDN のサイトを中心に試行錯誤していた。

Vim script の時と同じように試行錯誤中の記録は付けていなかったので、自分がどのように作業していたのか思い出せない。高校の時は実験ノートを書かされたのにね。ほんと悔やまれる。これからは疑問点だけでなく、成功・失敗のソースも記録しておくようにしよう。

ということでまたしても日記の引用。

GUI の作り方がわからない。ボタンを押して「おっぱい」と表示させることができるようになった。ただそれだけ。
msdn のサンプルを適当に見て、個人のウェブサイトを適当に見てたら適当に出来た。挙動がもの凄くおかしいのだけど、出来た。ソースを Form.cs に書けばいいのか、Program.cs に書けばいいのか、全くわからない。From.cs の方に書いておいた。
感覚としては Sinatra でウェブアプリケーションを作っているような感じだった。部品を作ってその挙動を記述するというのは、HTML フォームのボタンからのパラメータを受け取って処理をするのと全く同じだ。
いろいろと不具合はあるけれど、GUI アプリを作ることが出来たので満足。

Windows 版でクリップボードを介するデータのやりとりを出来るようにした。フォームを追加して一行書くだけで実現できてしまうので、本当に簡単だ。こうなってくるとファイルを読み込んで変換とか、結果をファイルに保存とかしたくなるけれど、長文のものは CGI 版で変換した方がいいと思う。誤変換もあるので、できれば VimEmacs で目視確認をしながら部分変換させる方が現実的。

Photo

32KB のサイズまでだけど、ファイルを読み込んで変換が出来るようにした。最初はカレントディレクトリが移動してしまうので辞書を見付けられない旨のエラーが出ていたけれど、

instance.RestoreDirectory = ture

とすることでカレントディレクトリの移動をしなくなった。
老眼のため小さいフォントだと旧字体を判別することが出来ないので、フォントのサイズを変更できるようにした。メニューバーが寂しいのでいろいろと追加して、メニューバーからも変換コマンドを発行できるようにした。
この GUI アプリは少量をちょろっと変換して楽しむものなのだが、ファイルの入出力を付けてみたくて、変換結果を別名で保存できるようにした。
Form のレイアウトが思い通りにならなくて、ウインドウサイズは固定にしていたのだけど、TableLayoutPanel を使うことによってテキストボックスも拡大縮小が出来るようになった。
なんだかショボい見た目になってしまった。GUI の設計って難しい。辞書を指定するダイアログを作成しようとしたけどできない。初期設定って難しい。
辞書設定ができるようになった。mto_win_gui.exe の他に同じディレクトリ内に mto_win_gui.exe.config も必要になる。アプリケーションを起動した時に、初期設定で指定されている辞書ファイル(../dict/xxx-jisyo)が見付からない場合は、設定ウインドウが開くようになっている。
テキストボックスには現在設定されている辞書ファイルへのパスを表示したかったのだけど上手くいかず、毎回デフォルトの辞書を表示するようにした。ファイルを選択して設定ボタンを押せばそれを使うようになるので問題はないけど、アプリケーションを再起動してやらなければならない。この辺どうやって処理すれば良いのだろうか?
メニューの配置は MacOSX 上のアプリケーションと同じようになっているので、Windows 使いの人には違和感ありありだと思う。まぁコマンドが少ないので問題ないといえばないのだが。
これでコマンドプロンプトの苦手なコンピュータ初心者でも手軽に仮名使いの変換ができるようになったと思う。辞書は自分で編集が出来るので自分だけの辞書を作ることができる。この編集過程において仮名使いの勉強も出来るというウマウマ状態。
何度も書いているけれど変換することが第一の目的ではなく、この「辞書の編集過程における勉強」こそが真の目的なのですぞ。自分の場合はこれに加えて各種スクリプト言語の練習という目的も加わっているけれど。
辞書の形式はカンマやタブ区切りではなく、空白 + / という変則的なものになっている。; 以降はコメントになっているので、疑問点などをメモしておける。最初に実装したプラットフォームが Emacs だったのでこんなフォーマットになった。
辞書を変更すれば旧仮名使いに限定されることなく近畿弁や津軽弁への変換も可能。これは数多くある既存の辞書もフォーマットを変更すれば使えるということ。正規表現を変更すればそのまま使えるようになるけど、ビルドし直すのは面倒なので辞書側を検索・置換で変更した方が簡単だろう。

Photo
フォントの大きさを変えることができる(けれど記憶はされません)

Photo
辞書を指定することができる(けどアプリケーションを再起動するまで反映されません)

初期設定ファイルは mto_win_gui.exe.config に保存されるのではなく、

C:\Documents and Settings\username\Local Settings\Application Data\mto_win_gui\

以下に保存されるんだね。
辞書を指定してから一旦アプリケーションを再起動しないと反映されなかったのだが、その場ですぐに反映できるようになった(と思う)。実は Properties.Settings.Default.KanaDict 等のパラメータはアプリケーションの起動時に読み込まれるだけなので、その後にこの値を変更しても再度アプリケーションを起動するまで変更されることはないのであった。
それなら辞書設定ダイアログ(Form2)から直接代入しちゃえば?と思ったら、C# ではグローバル変数が存在しないんだってね。スコープとか良くわからないので、public を使ってみた。

Form1
public static string KANA  = Properties.Settings.Default.KanaDict;
public static string KANJI = Properties.Settings.Default.KanjiDict;

Form2
Form1.KANA  = textBox1.Text;
Form1.KANJI = textBox2.Text;

のようにすると、その場で変更することができるようになった。class Form1 にある KANA という変数に代入したいよ、ってことなんだね。で、public にしておかないと外部のクラスからアクセスできないと。こんな理解でいいのか?
残りの作業としてはアイコンやドキュメントの作成があるけど、面倒なので放置決定。

GUI アプリケーションを作成したのは今回がはじめての事だったのだが、Web アプリケーションを作成した経験が役に立ったと思う。感覚的には「CUI で作ってあるやつの入出力部分がターミナルからになっているものを、それぞれ Form やボタンに置き換えたもの」という感じ。

ソース的には入力の args[]; が button_Click(オプションに相当) や inputText.Text(入力ファイルに相当) に、出力の Console.WriteLine が outputText.Paste に変わっただけというもの。

まとめ

これまでの記述を振り返ってみると、CGI を利用した HTML を書いた事のある人は、意外と簡単に GUI アプリケーションを作成することが出来るのではないかと思う。まぁやる気があればの話だけど。