2007年11月30日金曜日

[.NET]ComboBoxのDataSource

今日、お隣さんが独自クラスの配列をコンボボックスに表示しようとしていました。で「ComboBoxのDataSourceに適当なクラスの配列突っ込めないかなぁ?」といっていました。で私が「いやーダメでしょう」と言っている横で、DataSourceにその配列を突っ込み、DisplayMemberとValueMemberにその独自クラスのプロパティ名をセットしてデバッグを開始していました。
まぁ当然Bindするときにエラーになるだろうと踏んでいたんですが、なんと正しく動作しているではないですか。
2人して「うそだろー」とハモってしまいました。

で、気になってしまいMSDNに書いてあるComboBoxの説明を見ると以下のように記載されていました。
-----
■DataSource
IList インターフェイスを実装する、DataSet または Array などのオブジェクト。既定値は null 参照 (Visual Basic では Nothing) です。

■DisplayMember
DataSource プロパティで指定されたコレクションに格納されているオブジェクト プロパティの名前を指定する String。既定値は空の文字列 ("") です。

■ValueMember
DataSource プロパティで指定されたコレクションに格納されているオブジェクト プロパティの名前を表す String。既定値は空の文字列 ("") です。
-----

・・・いやーどうなってるんだろう。今日の発見にまったくつながらない。。。

しかしこの手の部類(ListBoxやDataGridVIew)も同じ方式で実はいけてしまう?様な気がします。機会があれば調べてみよう。と思いました。

2007年11月29日木曜日

[.NET]WindowsFormでのRadioButton

WebFormでもWindowsFormでもRadioButtonはグループ化し、そのグループの中で1つを選択できる仕様です。
WebFormの場合、RadioButtonListがありその中で選択しているのはどれか?というのが簡単にわかります。RadioButtonListを使わなくても、HTML的に選択されている項目が単一でわかるようになっています。

じゃあWindowsFormは?というと、一般的にWindowsFormの場合は、GroupBoxを利用(多分)して単一選択を可能にします。でもGroupBoxからは、どの項目が選択されているかはわからないようです。

単純にやるのであれば、チェックのイベントを拾って処理するってことになります。でもOK押下でDBに登録などといった動作は通常の使用としてありうると考えられます。
はて、WebFormのように簡単にやる方法って無いだろうか。。。今度調べてみよう。

2007年11月27日火曜日

[.NET]カレントディレクトリがかわってしまう?

.NETのアプリケーションは、起動したディレクトリをカレントディレクトリとします。
たとえば、アプリケーションが起動したディレクトリにあるhoge.xmlを読み込む場合、以下のようなコードを実装します。

byte[] buf = null;
using(FileStream fs = new FileStream(@".\test.txt"FileMode.OpenFileAccess.Read)){
    buf = new byte[fs.Length];
    fs.Read(buf0buf.Length);
    fs.Close();
}


このように「.\」を指定または何も指定しなければカレントとなります。
しかし、SaveFileDialogにてファイルを保存すると、勝手にカレントが変わってしまうのです。たとえば何かしらのファイル保存処理でデスクトップにファイルを保存すると、カレントがデスクトップになります。よって「.\」もデスクトップになってしまいます。これは、OpenFileDialogを利用しても同様の現象が起こります。
コレを回避するためには、FileDialog.RestoreDirectoryをtrueにすることで回避でます。

・・・って何でデフォルトでfalseなの?

インタフェースを使う側からすると、普通に考えてカレントが変わらない仕様を想定してしまいます。
この仕様にたどり着くまでに半日費やしました。とほほ。

2007年11月20日火曜日

[.NET]カスタムコントロールに新しいイベントを

仕事中、こんな相談を受けました。要約すると以下のとおりです。

  1. TextBoxの拡張版のコントロールを作成
  2. リターンキーを独自で処理する
  3. リターンキー押下時に連携するコントロールの入力をチェックする
  4. エラーがあったらErrorProvider風のエラー処理をする
というものでした。しかし「4」の工程でエラーの表現を独自のもの(表現)にしたい場合もある。という話があったので、「あぁ、そのコントロールに新しいイベント実装したら?」とアドバイスしました。
イベントが設定されていたらソイツを呼び出し、そうじゃなかったらコントロール内で処理すりゃいいってことになります。

・・・とは言ったものの、はて?どうやって実装すればいいんだろう?説明した相手も、「じゃあどうやって?」っていうような顔をしていました。でその人は「ちょっと調べてみます」といって去っていきました。

私も気になって調べてみました。すると意外と簡単に実装できそうなことがわかりました。
以下、参考になりそうなサイトです。

でもこういう実装をした場合、VS.NET上どのようになるんだろう?プロパティみたいに連携しそうな予感が。。。ちょっと見てみたい。もうWPFの時代が目の前ですが、そのうちサンプルを作成してみようと思います。

2007年11月19日月曜日

[.NET]シリアライズ

C#でxml及びバイナリ形式のシリアライズについてこのblogに記載しました。
.NET Framworkでは以下のクラスを利用すれば簡単にシリアライズすることが出来ます。

  • System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
  • System.Xml.Serialization.System.Xml.Serialization

最近VC++の改修案件があり、ソースをなめていると、MFCのCArchiveに関するコードが出てきました。
いやー実に懐かしい感じがしました。10年ほど前、こんなコードを見た記憶がよみかえりました。
当時、意味がわからずじまいで現在に至りましたが、今はコード見た瞬間にどういう意味なのかが飲み込めました。
言語は移り変わりますが、こういった考え方というのは変わらないものなんですね。

シリアライズを簡単に行う仕組みって、もしかしてMFCのアレが原点なんだろうか?

2007年11月15日木曜日

[.NET]log4netのFileAppender

log4netのFileAppenderはログファイルを排他モードでオープンします。
今回同一のファイルに対して複数のプロセスから同時に同じログファイルを出力する必要があるので、仕方なくファイルオープン周りの処理をカスタマイズするために調査してみました。
いざ実装しようとすると、それらしいモードが選択出来そうなコードが。。。でマニュアルを調べてみると設定ファイルに記載すれば済みそうなことがわかりました。
log4net(1.2.x)では定義ファイルに以下の一行を入れれば共有モードになります。

<lockingmodel type="log4net.Appender.FileAppender+MinimalLock"/>


「FileAppender+MinimalLock」で「FileAppenderの内部クラスMinimalLock」って表現するっぽいです。ちょっと微妙。
今回はコードからマニュアルを見る方向に行きました。しかし英語のマニュアルなので、読むのがつらい。と思いがちですがほとんど実例が載っています。先に読むクセを付けないと。。。
しかしこのMinimalLock多数のプロセスからの出力は考慮されていない感じな実装に見えました。今はごまかせてるっぽいのでいいですが、折を見てプロセス間共有可能な実装にカスタマイズする予定です。System.Threading.Mutexあたりを利用すれば実現できそうな予感が。。。

2007年11月13日火曜日

[.NET]virtualと非virtual

最近の作業で、log4netのRollingFileAppenderの拡張をしました。
で、改修ポイントを調査し、該当のメソッドをoverwriteし実装を完了しました。
その後、テストすると思うように動作しない。そこでブレイクポイントで待っているとなぜかブレイクしない。調べてみると、RollingFileAppender(継承元クラス)の該当メソッドが呼び出されていることがわかりました。

該当のメソッドは非virtualメソッドであり、拡張したクラスから呼び出された場合は拡張したクラスの該当メソッドが呼び出されますが、RollingFileAppenderから該当メソッドを呼んだ場合はRollingFileAppenderの該当メソッドが呼ばれることがわかりました。
そうなんです。C#ではデフォルトでメソッド呼び出しは非virtualなのです。

もともと私はJavaからC#にはいった経緯があり、Javaと同じ(デフォルトvirtual)つもりで実装してしまっていました。
ここは難易度の高いところだったのでヒヤヒヤでしたが、他の方法で機能は実現できたのでよしとしておこう。

2007年11月9日金曜日

[.NET]ADO.NET(ODP.NET)

ADO.NETでは、今までバラバラだったDBに関係するクラスが共通のインタフェースを実装したことと、FactoryMethodによるインスタンス生成のラップにより、どんなデータベースにつないでいるかを意識する必要が無くなりました。実装はこんなかんじ。



DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
using (DbConnection con = factory.CreateConnection())
using (DbCommand cmd = factory.CreateCommand())
{
    con.ConnectionString = "User Id=scott; Password=tiger; Data Source=ora10g";
    con.Open();
    cmd.Connection = con;
    DbDataReader reader = cmd.ExecuteReader("SELECT * FROM TEST_TABLE");
        :
        :
        :
    reader.Dispose();
}


ところが、この機構を使った場合、ODP.NETの拡張機能が使えない(使いづらい)ことになってしまいます。まぁ共通I/Fなので当たり前といえば当たり前なんですが。。。で、何が痛いかというと、

  • OracleCommand.BindByNameプロパティが使えない(キャストすれば。。。)
  • OracleCommand.OracleDbTypeプロパティが使えない(キャストすれば。。。)

です。で、何が不便かというと、

  • BindByNameの影響で、bind変数「:USER_ID」としてもOleDB同様「?」相当
  • OracleDbTypeの影響で、CHAR型のbind変数は、同じサイズにPadLeftしないとヒットしない

という問題が発生します。
だからといってキャストするのはイヤだしOracleしか使わないと決まっているシステムであったため、今回は直接ODP.NETのクラスを生成することにしました。とはいえDispose漏れの考慮からラップクラスは用意しています。

しかし、OLE DBやSQLServerの場合はDbProviderFactoryでいけるはず。Oracle以外にあたったらこの方式が最適なんでしょう。SQLServerは何もしなくても「@USER_ID」でbind出来ちゃうんだろうか。出来るのであればなぜODP.NETはそうじゃないんだろう?


あぁODP.NETでもこの問題が解決できればなぁ。。。

2007年11月7日水曜日

[.NET]System.Diagnostics.StackTrace(結果まとめ)

いろいろ悩んだSystem.Diagnostics.StackTraceにの利用についてですが、元々は以下のようなものを作ろうとしていました。
  • ネームスペースの一部分をファイル名にしたXMLを読み込む
  • 指定キーよりXMLの要素を取り出す
  • ただし、呼び出し元から指定するのはキーのみ

最終的にリスクが大きいと考え採用を見送ったことは記載しましたが、仕様を再確認したところ「指定キー」は重複しないことがわかりました。結果、以下のような仕様になりました。
  • クラス初期化時に、xxx.exe.configに記載された特定のキーからXML一覧を作成
  • XMLの一覧を順次読み込む
  • 読み込んだデータを1件ずつDictionaryに格納する
  • 指定キーからアクセスされた情報をそのまま返却(エラーチェックもしない)

キーが重複しないので結果フラットな構成でよく、後は分けられたファイルをマージするという構成となりました。えぇまぁ非常にシンプルな仕様です。

仕様を純粋に実装しようとして遠回りしましたが、非常にスッキリしたものが出来上がったような気がします。いやー仕様をいろいろな人と会話してみものですね。最近1人だと行き詰る傾向が。。。

2007年11月6日火曜日

[他]作業の進め方について(いまさら)

ソフト開発では、作ったプログラムが思うように動作せず、何をすればよいのかわからなくなってしまったり、ある修正が別の問題を引き起こしてしまったり。。。単純ではありません。
昨日私もそのような目にあい、一日中イタチごっこのような修正を繰り返し、結局解決せず、帰宅しました。その日、入浴中に「なにが問題なんだろう」と物思いにふけっていました。明日は問題整理としてから作業に取り掛かろう。そう思いました。

で、今朝、30分ほど仕様検討&問題を整理実施し、1つづつ順を追って作業してみると2時間で問題が解決しました。

問題が発生したときというのは、なぜか力ずくで何とかしたくなります。それは時として遠回りとなり、いつまでたっても問題が解決しないという悪循環な状態に陥ります。そこには必ずといってよいくらいに問題を整理し仮説を立てる作業を実施していない自分があります。

・・・もうおっさんなので、そのあたりをわきまえて作業しないと。と強く感じました。

2007年11月5日月曜日

[.NET]System.Diagnostics.StackTrace(その2)

System.Diagnostics.StackTraceによるコールスタックをたどる方法ですが、パフォーマンスがひどく悪くなることがわかりました。
履歴をたどれば1つのクラスに出来るんですが、何かあった場合のリスクを考慮して、クラスを分けることにしました。今回のケースは急がばまわれの典型と思い込むことにしました。

ただ、コピペによるコーディングミスが多発しそうな予感がするのでデバッグモードのみ整合性をチェックするロジックを入れることにしました。

2007年11月3日土曜日

[.NET]System.Diagnostics.StackTrace

今、以下のようなクラスを作っています。
  • ネームスペースの一部分をファイル名にしたXMLを読み込む
  • 指定キーよりXMLの要素を取り出す
  • ただし、呼び出し元から指定するのはキーのみ
そこで、メソッドの呼び出し履歴をさかのぼるために、System.Diagnostics.StackTraceを使ってみました。
とりあえずデバックモードでそれらしく動作したので、一安心していたのですが、ヘルプを見るとリリースモードでは呼び出しが最適化され、一部のコールスタックが最適化されることがある旨記載があっため念のためリリースモードで試してみると案の定コールスタックの出方が異なることがわかりました。

そこで、#ifで、デバッグモードとの切り替えを実施してみましたが、本来通過しているメソッドの履歴がなくなっており(この辺が最適化だと思います)ちょっと困っています。
いくつかのアセンブリに分かれるのとファイルの単位=アセンブリの単位であるのと、このクラスはこのクラスのみのアセンブリになるため、
  • コールスタックをさかのぼる方式は同じ
  • このクラスがあるアセンブリ以外のアセンブリまでさかのぼる
  • そのネームスペースのファイルを参照する
  • 指定キーよりXMLの要素を取り出す
  • 値を返却する
という方式をとってみようと思います。が、うまくいくだろうか。。。
最悪全部のアセンブリ用にサブクラスを作り、初期化ルーチンのみ実装する方針になりそうなんですが、何かいい方法は無いだろうか。。。

2007年11月1日木曜日

[.NET]アプリケーションセッティングとユーザセッティングの保存

WindowsFormアプリケーションでは、設定ファイルとして「アプリ名.config」というファイルを使用することが出来ます。
この設定はアプリ固有のものとユーザ固有のものと分けることが出来ます。

今回あるちょっとしたツールでiniファイルやレジストリ的にデータの保存がしたくてこの機能を利用してみました。このツールでは「アプリ名.config」に保存したいなぁと思い、以下のような実装をしました。
  • 該当のプロパティはアプリケーションスコープ
  • あるボタンを押下するとxxx.Properties.Settings.Default["YYY"]に値を設定
  • xxx.Properties.Settings.Default.Save()にて保存
しかし、いくらSave()を実行しても設定は保存できませんでした。調べてみるとアプリケーションスコープでは情報を保存できないとのことで、とりあえずユーザスコープに変更し、保存してみるとなんとなく保存できました。しかし、どこに保存されているかがわからず、またアプリケーションスコープに戻してみました。

ここでおかしな現象が発生しました。なぜか実行時にこのプロパティにアクセスすると例外が発生するようになりました。原因がさっぱりわからない。その後3時間くらい右往左往していて、たまたまExceptionのInnerExceptionを見ると何かおかしなパス(C:\Documents and Settings\matsuo\Local Settings\Application Data\インストール時に設定した会社名?)の書いてある例外が。。。そのパスを見るとそこにはユーザ設定らしいファイルが存在しました。

そのファイルを消すと元通り正しく動作するようにまりました。よく見ると過去に作ったアプリのフォルダも存在していました。
その後、プロジェクトのプロパティ「設定」画面の「同期」ボタンを押下することによりクリアできことがわかりました。

・・・挙動不信な例外が発生したら、まずマニュアルを。次に「InnerException」を。次にGoogleで。教訓になりました。