2007年12月31日月曜日

年越し

こっちも久々に更新。

気がつくと2007年も終わり。今年は.Netに始まって.Netに終わる一年だったなぁ。おかげで当初に比べると色々なことがわかってきて、色々なことができるようになってきたね。

DataGridViewとはまだまだ付き合い長いだろうけどねぇ。未だにわからないところが多々あるし・・・。

ただ、実際にはもうWPFとかに移行していかないとマズイんだろうな。

ま、今年も色々ご迷惑をおかけしましたが、来年もまた変わらずよろしくお願いします。

2007年12月28日金曜日

Officeファイルのフルテキスト検索

Download details: Microsoft Filter Pack

SqlServer2005用に提供されているOffice2007ファイルに対してのフルテキスト用IFilterパック。実は元々Office2003までのファイルについてはbinary項目としてテーブルに保存して、そこに対してのフルテキスト検索は行えていたんだけど、今回提供されたこれでようやく2007までのほぼ全てのOfficeファイルに対して直接検索が行えるようになったんだよなぁ。
んで、当然ExpressEditionでも利用可能、とうたっているんだよね・・・。

こう考えると無償でできる範囲ってのは、一昔前のウン千万クラスな案件だと、かなり対応できてしまうくらい広くなっているんだよねぇ。
こうなってくると「支払ってもらうだけの価値」を認めてくれるくらいのモノを作れるようにならないとね・・・。

2007年12月27日木曜日

APの配布を考慮した場合

ClickOnceとかWindowsInstallerとかで配布することを考えると、どうしても今の案件のようなファイル構成というのはあまり相性がよくない。良くないというか発想が間違っている(w

今みたいに画面単位=Exeファイルという構成がどう考えてもマズイ。
そうなると基本的にシステムとしてはExeファイルは一つで、画面単位はDllファイル一つ(もしくは複数を合併)させた形でないと、Vista以降の環境においてはどうやっても問題が出てくるなぁ。

「インストール時に制限すればいいんじゃね?」

という考えもあることはあるけど、それはシステムを提供する側としては問題アリだと考えるんだよなぁ。完全にこっちの都合だし。
アプリケーション(またはソリューション)を「どこにインストールするか」は、そのクライアントPCの利用者が決めることであって、開発者が決めることじゃないのがあるべき形だと思う。

というか、自宅PCとかで考えてもらえば、普通のソフトはそうなっているじゃないの?なんでそれを業務用だからといってルールを変えようとするんだろうね。どっちもWindows上で動くソフトなんだけどなぁ。

2007年12月21日金曜日

後方一致なLike検索のレスポンスをあげる

対象となるフィールドにインデックスをはっていても、後方一致(’%ああ’とか。ワイルドカードが頭につくタイプ)を行った場合は、無条件でインデックスは使用してくれないんですなぁ。それでそういった場合の対応策というのが調べ物していて見つかったのでメモ。

元ネタ:後方一致を計算列とインデックスでチューニング

リンク先に書いてあるように、要は後方一致時に利用できるようなインデックスを用意する、というのがポイントになるとのこと。ただし、後方一致の際は検索条件を逆転させて(’%いあ’→’あい%’)検索を行うことになる。そしてその対象となるような項目を用意しておけばいいんだけど、面白いポイントはここ。

後方一致用の対象は普通の項目の反転した結果となる

ので、そこはデータベースに計算させてしまおう、というところ。計算列というんだけど、自分はまだちゃんと使ったことはなかったんだよね。
こういう合わせ技で使うと色々できそうな予感・・・。

2007年12月20日木曜日

SqlServer2005以降でOUTPUT句なんてものが

見落としていたー!!
これをうまいこと使うと、INSERTやUPDATEなどの実行結果をそのまま取得できるんだなぁ。

今回の案件だと、INSERTした後に必ずSELECTして実際のUIDを取得しているんだけど、この処理自体をすっぱりと削除できてしまうワケだ。
DBにものすごく依存している話なので、ライブラリのどこに実装するかは難しいけど、コレが用意されていると、結構楽になる事があるなぁ。

・・・試しに組み込んでみるかな?

2007年12月18日火曜日

フルテキスト検索時の書き方

忘れないためのメモとして。
大まかに言えば二通りに分かれるみたい。
一つ目はこれ。

SELECT *
FROM
  TEST_FULLTEXT TFT
WHERE
  CONTAINS(TESTSTRING,'"10*"')

二つ目がこれ。

SELECT *
FROM
  TEST_FULLTEXT TFT
    INNER JOIN CONTAINSTABLE(TEST_FULLTEXT, TESTSTRING,'"10*"') KFT
      ON (TFT.TESTUID = KFT.[KEY])
ORDER BY
  KFT.RANKDESC;

二つの違いは「検索結果のランクを利用するかしないか」。上が利用しない純粋な検索で、下がランクを利用した、どちらかというとGoogle先生っぽいやりかた。
恐らく今回は二つ目のやりかたになるだろうなぁ。
しかし試しに100万件のテストデータつくって検索してみたけど、LIKE検索よりも10数%は高速っぽい。

VS2008出ちゃったねぇ

ついにというかVisualStudio2008がリリースされて、FrameWorkも3.5に突入。

そんな中でもこのBlogはDataGridViewだけに拘り続けていこうと思ってますw
いいじゃん、こういう役に立たないサイトが一つくらいあっても。

WPF?SilverLight?知っていますが扱いません。扱えませんよ!w

2007年12月10日月曜日

DataGridViewのセル間DragDrop

DragDropの時にはTypeを指定して実際の値を拾う必要があるんだけど、DataGridViewの場合それはDoDragDropイベント発生時の値は利用できないってトコなんだよね。そして標準で用意されているCell関係は全て値のTypeが定義されていない(=Object)なので、はてさてどう書いてあげればいいものか、というとこで悩み中。

DragDrop発生時はこんなコードで、セルの値をDragDropするぞ、とやる。

_dragDrop.DragEffectType = Me.DoDragDrop(Me.CurrentCell.Value, DragDropEffects.Copy)


そして値を受け取る際はこんなコードなんだけど・・・。




Dim targetCell As DataGridViewCell  = Me.CurrentCell


Dim cellValueType As System.Type = targetCell.ValueType


Dim copyValue As Object = drgevent.Data.GetData(cellValueType)


targetCell.Value = copyValue 



問題となるのは3行目。ここでTypeが必要なんだけど、CellクラスのValueTypeは見事にObjectでやんの。自前で拡張した部分だと、ここはちゃんとTypeを返却してくれるんだけど・・・。



利用できるTypeを取得するGetFormatsメソッドもあることはあるんだけど、それを利用した処理にしてしまうと問題があるような気がしてならないなぁ。

2007年12月6日木曜日

ReportViewer

使い始めた当初は「だいぶマニアックで使っている人すくないだろうなぁ」と思っていたんだよね。
まぁ現時点でもそうなんだけどw

でも費用をかけないで~、という何か間違った(w)開発をする時には、コイツ抜きでは難しいんだよね。

機能的には世間一般の帳票ソフトといい勝負ができるんだけど、いかんせんちょっとクセがあるんだよなぁ。
まぁ色々やってみた内容はここで公開できればしていこうかと。

2007年12月4日火曜日

DataGridViewの仕様?

色々とカスタマイズしていてぶつかっているのがイベント周りなんだけど、
DataGridView_CellLeaveイベントとCellクラスのLeaveイベントで、DataGridView_CellLeaveイベントしか発生しないという現象が・・・。

これは自分がカスタマイズしている部分だけだからなのか、元々そういう仕様なのかが全然わからない。

時間がないのもあるんだけど、どのあたりから手を入れてみればいいかもさっぱりなんだよなぁ。

2007年11月29日木曜日

VistaでのVB6ランタイム

どうやらちゃんとWindowsInstallerを利用してインストールしても、レジストリに登録失敗するケースがあるみたい。

今回のケースではTabCtl32.ocxとMSFlxGrd.ocxがおかしくなっていた。再度Regsvr32.exeで登録しなおすことで問題は解決できたんだけど・・・。ネット上を見ていると、このほかにもWinSock周りで同様の現象がでるみたい。

UACをOFFにした状態でインストール、その後UACをONにしたりしても同じような状況になるって話もでてるね。
なんか特定のコンポーネントに由来した問題のような気もするな・・・。

2007年11月16日金曜日

DataGridViewとContextMenuStrip

ContextMenuStripを利用してちょっとしたメニューを出したりすることはよくあるんだけど、その発生元がDataGridViewになった場合はちょっと手間が必要になる。

というのも、DataGridView.ContextMenuStripプロパティで設定したものは、DataGridView全体に対して有効になるので、例えば列ヘッダ(ColumnHeader)や行ヘッダ(RowHeader)でContextMenuStripを切り替えたいなんて時には、ちょっとだけ細工が必要になるんだよね。

  • 列ヘッダ

列ヘッダで独自にContextMenuStripを使う場合は、DataGridViewColumn.HeaderCell.ContextMenuStrip、と下っていって設定する。それで今のところ最もベターなロジック記述場所はというと、DataGridView.ColumnAddedイベントの中。
Columnが追加された際に発生するイベントごとにContextMenuStripを指定してあげればいいかな、と思われ。
単発プログラムでなく汎用的に使う場合とかなら、DataGridView.OnColumnAddedメソッドをオーバーライドして対応するのがベターかな。

  • 行ヘッダ

行ヘッダの場合も似たような事情で、DataGridViewRow.HeaderCell.ContextMenuStrip、となる。
列ヘッダの場合と異なるのは、「今存在する全ての行に対して設定する必要がある」というところ。なのでベターな記述場所はDataGridView.RowsAddedイベントまたはDataGridView.OnRowsAddedメソッドとなると思う。

2007年11月13日火曜日

何かのコントロールを継承したDataGridViewEditingControlで

コンストラクタとかでTabStopにTrueを設定しておかないと、Tabキーを押した際にフォーカス遷移がただしく行われなくなるんだねぇ・・・。

考えてみれば当たり前なんだけど、思いっきり失念していたのでメモ。

2007年11月8日木曜日

RealVNCだめぽ

どうやらFree版ではVista対応を行わない様子。
久々にサイトをのぞいてみると、レイアウトが思い切り変更されて、Rev4.3 Release!みたいな宣伝が。

んで、そっち系統はちゃんとVista対応しているんだよねぇ・・・。
FreeEditionはRev4.1で取り残されているし。切捨て時だな、これは。

2007年11月7日水曜日

SearchServer?

最近β2がリリースされた、検索ソリューション用のサーバアプリ・・・つーか、扱いはSharePointServiceみたいなもんだな、これ。
どうやらEnterpriseSearch的なことができるようになる、ということらしいんだけど、英語サイトしかないのでまだ詳しく見てないw

一時期NamazuとKakasi使って社内情報に対する検索って(隠れて)用意したことはあったけど、おそらく到着地点は同じだろうね。あとはどれくらい使いやすいか、とどこまで検索できるか、ってところかな?

JITデバッガの無効化

Tracが落ちる、というかApacheが例外吐いてなおかつJITデバッガを起動しようとしてくれやがる件で、ためしにJITデバッガ自体を無効にしてみた。忘れないために手を入れたところをメモ。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\Debugger
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\DbgManagedDebugger

この2つのレジストリキーを削除。
VisualStudioのインストールによってJITデバッガが有効になってしまい、今までキャッチしていなかった例外をキャッチして、デバッグダイアログを表示してしまう、ってのが事の本質だなぁ。

インストールした、というけどサーバー上に入れていたVisualStudioって、SqlServerのReportingService用のヤツでVBもC#もインストールしていないから実質デバッグできないヤツなんだよね。そういう判断をしてJITデバッガのスイッチが設定されていればいいんだけど・・・問答無用でOnにしてくれているので、Apacheが例外はいたときにダイアログが出てしまい、OK押下まで再起動しない → 落ちている、という状況になっているワケだ。

これで大丈夫になることを切に願うw

2007年11月6日火曜日

VB6のコレクションと.Netのコレクション

久々にVB6の修正をやっていて気づいた。
VB6のコレクションにはClearメソッドがない・・・。

うわー、単純だけど地味に効いてくるなぁ・・・。よくCollectionの拡張しないでやっていたね、自分。

2007年10月30日火曜日

ラムダ式?

C#3.0の話題を出したときに、少しだけでていたラムダ式。
CodeZineでサンプルがでていたのでちょっと張っておく。

Common Lispでのラムダ式の一例

(setf f #'(lambda (x) (expt 2 x))) (funcall f 10)

おびただしい量の括弧です。しかし、C# 3.0のラムダ式も負けてはいません。次のようになります。

カッコいいラムダ式の一例

Func<int, int> f = (((x)=>((((x)>10))?(((x)+x)):((x*(x))))));
f( 9 );

これ、なんていう正規表現?可読性の「か」の字もねぇぞ!

ADOでセッションプーリング

[HOWTO] ADO プログラムからのセッション プーリングの実装
なんでいまさら・・・と思ってみてみたら、Ado.Netじゃなくて純粋にADOだった。つまるところ、既存のVB6資産でも有効にできるってとこだねぇ。といってもウチの社内では「セッションプーリング」を判っている人はいないだろうから、多分今まで通り「AP起動時に接続、終了時に解放」という流れはそのままなんだろうなぁ。
まぁ自分もプーリング知るまでは、それしかない、と思っていたからあまり強くはいえないw

しかし接続文字列で指定できたとはねぇ・・・意外とまだ知られていない(公表されていない)設定があるんじゃないのかな?

2007年10月29日月曜日

DataGridViewでのEnterキー制御

業務アプリ作っていると必ずといっていいほど、話題になりやすいのがEnterキー制御。

昔からの習慣で、Enter押下で次の項目へカーソルを移動、というのが多いんだよね。それ自体は特に否定も肯定もしないけど。

ただ、その機能をDataGridViewに追加しようとする前に、ちょっとだけ考えてみて欲しい。
Enter押下→次のセル、だけでいいのかどうか?

画面によってはEnterで次コントロールというのがあるだろうし、下のセルへ移動させるのもあると思う。そのあたりを踏まえてどう拡張するかってのを考えて欲しい。そのあたりを踏まえていれば、あとあと「えー」なんて文句をいいたくなる事も減ると思うw

実際の対処なんて、大したことはないんだよね。
ProcessDialogKeyメソッド、ProcessDataGridViewKeyメソッド、ProcessRightKeyメソッドあたりを調べてもらえばわかると思うけど、このあたりで「Enterの際にはセル(またはコントロール)を移動する」というロジックにしてあげればOK。

ひっかかるとしたら、編集状態がからむとき。
EndEditメソッドを呼び出して、その結果OKなら移動してあげる、とかそういう制御は必要だろうね。

2007年10月24日水曜日

DataGridViewでセルの検索

DataGridViewを利用しているとやっぱりExcelみたいな使い方をしたくなると思われ。

前回(アンバウンドなDataGridViewでフィルター機能)書いたのもその一つ。なので今回はセルの検索をやってみようと。

セルを検索するときも、バインディングしているかどうかでロジックは変わる。
バインディングしている際は、現在のDataSetからDataViewを作り出してしまってしまえば、どの行が対象なのかがわかるので、後はどのセルかを走査してあげればOK。

アンバインドの場合は、元となるDataSetがないのでDataViewを使えない。なので力技の出番となるんだよね。
共有行を一気に非共有行へとしてしまうように(w)、全ての行とセルを一斉に走査するしかないんじゃないかなぁ。

あ、もちろんDataGridView自体をカスタマイズして、仮想モードでちゃんと動くようにしてある場合は別ね。

VistaのMDAC

元ネタはニュースグループに投稿されていた、「WindowsVista上でのVB6sp5のインストールにて」という件について。

Vistaでは今までのMDACと互換性を保持しているWindowsDACというものがインストールされている。そしてこれはMDAC2.8として扱われるのでそれ以前のバージョンはインストールすることはできない。

今回の件では、VB6Sp6であればインストール可能という事なのでどうにかなるだろうけど・・・。つか、とっくにサポートされていないSP4やSP5を使おうと考えるのが、もうダメってことなんだろうね。

2007年10月22日月曜日

機種依存な丸付き数字

自分としてはふるくさーい人間なんで、丸付き文字は「機種依存だコノヤロウ」と、問答無用で拒否する傾向にあったり。
で、いろんな話を聞いているとそのあたりの情勢も結構変わってきたようで。

旧来のShiftJis(JISX0208)では、1~20までしかなかった。なのでそれをベースにしているeuc-jpやISO-2022では表示できない。

ところが現在はJISX0213という規格もできて、そこでは1~50までがサポートされているんだよねぇ。ついでにいくとUnicode3.2規格でもサポートされるので、今後は普通に使っていい、とする必要はあるかも。

売上~請求~入金と、発注~支払~出金

今現在請求周りの再設計・・・。
そこで今まで考えてはいたけれど、ということを改めて見直してみた。それは「請求の裏返しが支払」という事。まぁ販売管理やっている人だと、結構こういう事言うのはあるね。

でも、それを実際にやってみたことはないんだよな。
絶対にデータのストレージ先は分けて設計しているんだよな。

ということで、頭の回路をフルフルに動かして考えてみた。結果、多分いけそうだね、という形までは思いつけたのよ。大元の考え方は次のような感じで。

ある科目なり項目に対して、金額(売掛または買掛)が発生するその金額に対して回収または支払の予定をたてる予定に対して金種(現金や振込み)の移動が発生する

こういう観点から見れば、売上~入金も発注~出金も同一のフレームワークで定義できるんだよね。
ついでなんでよく言われそうな点についても考えてみた。

(1)売掛と買掛を同一テーブルにすると見づらいし、メンテのときに面倒
ビューを利用すれ。本来はテーブルを直接触るほうが変。つか「見づらい」という観点でDB設計するな。

(2)売掛と買掛は意味合いが違うんだから、別テーブルじゃないとおかしいのでは?
情報を定義する際の視点の違い。どちらでも意味がちゃんと通るので、「こうじゃないとおかしい」のは成り立たない。どちらでもいい、が正解。

・・・まぁこんなところかと。ああ、なんかグチっぽいなぁw

2007年10月16日火曜日

自動採番

昨日、自動採番周りを再設計していた際に思い出した。
別の案件で同じように自動採番の仕組みを構築していたんだけど、排他制御まわりで上手くいかなかったんだよね。
で、そこで単純なことを忘れていたのに気づいたわけで。

大きなトランザクション中だから、自動採番近辺でトランザクション終了してもロックが外れるワケがない

・・・あたりまえだわなw
ということで今回はくだらないミスをしないためにも、自動採番周りは別セッション開いてやることにしようかと。ついでに、コネクションブールを考慮して、トランザクション終了時に自動で接続切るようにしてしまおうかな。

昔と違ってコネクションの接続解放のコストって大分なくなってるからねぇ。AP単位でコネクション維持しておく必要がなくなってるもんな。

アンバウンドなDataGridViewでフィルター機能

データバインディングした状態で、フィルタ機能をDataGridViewに実装するのはサンプルがMSDNにあるんだよね。DataGridView 列ヘッダー セルのドロップダウン フィルタ一覧を作成する
ただしアンバウンドな状態でのサンプルってのはないんだよな。

というのもアンバウンドな時にフィルタリングする、というのは処理的に大した話じゃないんで。

バインディングのときはDataViewを利用して簡単にフィルタを行えるけど、アンバウンドのときはそうはいかないのよ。同じ手法でやるとすると、「今現在保持している値をDataSetに収納する」ということをやってからじゃないと、DataViewでフィルタはかけることができないね。地味にこの部分がコスト高い。

なのでそこはすっぱりと諦めて(w)バインディングしているかどうかでフィルタ処理を行うクラスを切り替えてしまう、というのが一番いいんじゃないんだろうか。

ちなみにアンバウンドな時のフィルタというのは、「DataGridViewRow.Visible = False」にするだけ。
これだけな話なのでサンプルがないんだよね(w

2007年10月15日月曜日

いやはや

開発も佳境へ・・・だとどんなによかったか(w

まだ設計終わってないってどういう事よw

まぁね、今回はいい経験になるのは間違いないから、それを信じて頑張っているけどね。自分は周りをサポートするけど、俺をサポートする人材がいねぇ。なんとかならんか、これ。

2007年10月13日土曜日

SqlServerのチューニング

ハード的にSqlServerの動きを良くするには色々と方法があるそうで。

  • RAID-1(ミラー)構成にする
  • 物理的にディスクを増やす
  • CPUを増やす

「ミラーにする」というのはSqlServerに限らない話。Writeの速度は違うけど、Readの速度が最も早いのはミラー構成だからね。「物理的にディスクを増やす」のと「CPUを増やす」というのは若干SqlServerに限定した部分。SqlServerの同時実行命令数というのがこれらに依存するためなんだよね。DIskI/Oは物理ディスク数分しか同時実行できないので、極端に言うと物理ディスクを複数ぶら下げて、そのあたりにデータファイルを分散させるのがベスト。普通は自動拡張なんてさせないで、最初の時点で想定容量を求めた上で固定でアロケーションするんだけどな・・・。自動拡張でしかやったことない人だとわからないかも。
(それでも6.5までのSqlServerだと固定アロケーションが普通だったんだけどねぇ)

そしてCPUについては、まあ最近のご時勢でわかると思うけど・・・。簡単に言えば使えるスレッドの数はCPUの物理数とコア数に比例するから。
なので既存システムでスループットをあげたいという場合、最も行いやすい方法はこのあたりだろうね。ただ個人的には「設計の問題」が大きいから、サーバーごと新世代のHWにごっそり入れ替えるとかじゃなければ、それほどの機能向上は望めない気がしているけど。

2007年10月11日木曜日

Windowsの再アクティベーションの条件

サポートニュースグループでこんなのが流れてきた。

どうやらハード交換の際に再アクチが必要になるかどうか、ということだそうで。

  • ディスプレイアダプタ
  • SCSIアダプタ
  • IDEアダプタ
  • ネットワークアダプタのメディアアクセス コントロール (MAC) アドレス
  • RAM の容量の範囲 (0 ~ 64 MB、64 ~ 128 MB など)
  • プロセッサの種類
  • プロセッサのシリアル番号
  • ハード ディスク デバイス
  • ハード ディスクのボリュームシリアル番号
  • CD-ROM/CD-RW/DVD-ROM ドライブ

提示されたのはこれ。ディスプレイアダプタが条件に入っているのは正直「どうよ?」って感じかな。
メモリ容量もねぇ・・・今のご時勢なら除外すべきでしょ。
まぁ、これって2003Serverに対しての質問だったから、XPやVistaの場合はまた違うのかもね。

2007年10月10日水曜日

列挙型のTooltipによるガイドが・・・

Friendで定義してある列挙型なのに、マウスカーソルあてて出てくるToolTipを見るとPublicとして表示されているなぁ・・・。
Friend定義してあるクラスのファイルでやってみても同じように表示されてくるから、VisualStudioのバグだな、これ。
別にいたくもかゆくもないバグだから、きっと次のサービスパックでも直らない気がするw

MSセミナー行ってきた

昨日はSilverlightとASP.NET+AJAXなセミナーに行ってきた。
今のところ自分が作成するシステムでは使う予定はないけれど、使うようになる可能性は結構高いかなぁ、なんて思っているね。
多分1案件でもブラウザベースなものが受注できれば、確実に使うと思う・・・。

Silverlightはブラウザのプラグイン、AJAXはJavaScriptとどちらもセキュリティポリシー次第では使えなくなる場面はあるけれど、多分そんなにポリシーが厳しいお客さんの案件はウチにはこないだろ。
どちらにも共通しているのは「次のユーザー体験」というところ。
デザインセンスが切実に欲しい・・・。

しかしセミナーというところでは隠れた問題が・・・。

11/26:Ado EntityFramework
11/30:.Net3.5 TechnicalBriefing

ちくしょー。どっちも確実に出ておきたいんだけど、時期が問題だ!

2007年10月2日火曜日

プロパティでSystem.Typeを指定したい?(6)

前回までで、プロパティダイアログにて利用するTypeConverterが用意できたことになるので、残っているのはColumnクラス側で利用するプロパティに属性をつけてあげること。

その際の記述はこんな感じ。

System.ComponentModel.TypeConverter(GetType(コンバータクラス))

プロパティの属性としてそのまんまなTypeConverter属性を記述してあげると、デザイン時に意図した動きになってくれる。例えばこんなので。

<System.ComponentModel.Browsable(True), _
System.ComponentModel.Description("ここで指定したType情報を元に、セルの編集の際にサブエディタとしてインスタンス化します"), _
System.ComponentModel.TypeConverter(GetType(MultiLayoutTypeConverter))> _
Public Property EditorInstanceType() As System.Type
    Get
        Return DirectCast(MyBase.CellTemplate, MultiLayoutCell).EditorInstanceType
    End Get
    Set(ByVal value As System.Type)
        DirectCast(MyBase.CellTemplate, MultiLayoutCell).EditorInstanceType = value
    End Set
End Property

こんな感じで指定しておくとバッチリかと。

SmartUIアンチパターン

[ 技術講座 ] Domain-Driven Designのエッセンス 第2回から。

Smart UI(利口なUI)アンチパターン

層状アーキテクチャの対極をなすアンチパターン。ビジネスロジックやデータアクセスのコードが、UIのコードと一緒になってしまっている、いわばスパゲッティな状態。利口なUIと呼ぶのは、ビジネスロジックを含むすべての処理がUIの中で行なわれるから。最もやっつけで手軽なやり方がこれなので、設計を何も考ないとこの状態に陥ってしまう。開発チームのスキルが低くて、かついわゆる第4世代(4GL)と呼ばれるようなグラフィカルな開発ツールを最大限に利用する場合は、このパターンの採用にも一理ある。しかし、利口なUIを採用してしまうとMDDが一切機能しなくなるので、DDDは諦めるしかない。

いやはや全く。あまり強くは言えないけれどなー。
目標とするのはこういうところじゃないと、どこかでせっぱつまるっす。というか、つまったw

2007年9月27日木曜日

プロパティでSystem.Typeを指定したい?(5)

今回はConvertTo関係。これでTypeConverterな話は一段落。前々の記事に書いたように「TypeConverterで扱う型・クラスをある型・クラスに変換」な処理を行うのがこのメソッド。

使われる場面ってのはプロパティダイアログ・・・ではなく実行時だと思っていていいのかな?いきなりだけど実際のロジック。

''' <summary>コンバータが、指定したコンテキストを使用して、指定した型にオブジェクトを変換できるかどうかを示す値を返します</summary>
Public Overrides Function CanConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal destinationType As System.Type) As Boolean
    If destinationType Is GetType(System.Type) Then Return True
    Return MyBase.CanConvertTo(context, destinationType)
End Function

''' <summary>指定したコンテキストとカルチャ情報を使用して、指定した値オブジェクトを、指定した型に変換します</summary>
Public Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
    If TypeOf value Is System.Type Then
        For Each child As System.Type In Me.GetClassList(context)
            Dim targetType As System.Type = TryCast(value, System.Type)
            If (targetType IsNot Nothing) AndAlso (child.Name = targetType.Name) Then
                Dim result As String = String.Empty
                result = child.Name.Replace(child.Namespace, "")
                Return result
            End If
        Next
        Return String.Empty
    End If
    Return MyBase.ConvertTo(context, culture, value, destinationType)
End Function

プロパティに設定されるのはStringによるクラス名なので、それを実際のインスタンス(System.Type)から変換するのが主なお仕事になるね。その際に注意するのが、クラス一覧は名前空間を除去した形で利用したほうがいいということ。そりゃー、ダイアログのあの小さい領域にフルフル名称で表示されていたら見づらいのでね。

とりあえずここまで準備できればあとは実際のプロパティ側で属性を指定すればOK。
そこは次回で。

VB6サポート

昨日Windows2008Serverのセミナーに参加してきたんだけど、どうやらまだVB6ランタイムはサポートされる様子・・・。

技術者としては「とっととサポートうち切って.Netに完全移行」させてしまっていいと思うんだよねぇ。どうせVB6がサポートされないと困る!なんて言っている人たちって、サポートうち切られたからって他OSに移る可能性は低いと思うんだよなぁ。

WindowsでVBしか使えない人だから文句を言っているような気がしてならないのよ。

2007年9月26日水曜日

ServerCoreの判断

今日のセミナーネタ。

Dim bServerCore
Const strExplorer = "\explorer.exe"
strRoot = WshEnv("SYSTEMROOT")
strExpPath = strRoot + strExplorer

Set objFso = CreateObject("Scripting.FileSystemObject")

If objFso.FileExists(strExpPath) Then
    WScript.Quit(0)
    bServerCore = False
Else
    bServerCore = True
End If

マイクロソフトのセミナーでこんな力技の紹介されるとは思わなかったw

2007年9月21日金曜日

PowerShellスゲー

PowerShellだと、データベースに接続してどうのこうのするスクリプトも.Netな感覚そのままに記述ができるそうで。元ネタから引用。

$connection = New-Object System.Data.SqlClient.SqlConnection("Data Source=.; Integrated Security=True");
$command = New-Object System.Data.SqlClient.SqlCommand("select getdate() as [date]", $connection);
$connection.Open()
$value = $command.ExecuteScalar();
$connection.Close();

バッチスクリプトだけでとんでもなくいろんなことができてしまうんだねぇ。月次更新とか一括更新系はもうAPにする必要すらないのかも・・・。

2007年9月20日木曜日

現在ログインしているユーザーの判断

あぁ、頭が固くなってきているようで・・・。
どのAPIとかクラス使ってできるのかなぁ・・・なんて考えていたけど、一番簡単な手段があった。

コマンドプロンプトでSETを実行 USERNAME変数を確認

なんでもかんでも自前でやろうとしてしまうのは、よくないクセだなぁ。

2007年9月19日水曜日

プロパティでSystem.Typeを指定したい?(4)

今回はConvertFrom関係。前の記事で書いたように「ある型・クラスから、このTypeConverterで扱う型・クラスに変換」にちなんだ処理を行うのがこのメソッド。

プロパティダイアログで利用する場合は「String ←→ System.Type」と変換できればよいので、CanConvertFromメソッドでは、ある型=StringであればTrueを返却するようにコーディングすればOK。

''' <summary>指定したコンテキストを使用して、コンバータが特定の型のオブジェクトをコンバータの型に変換できるかどうかを示す値を返します</summary>
Public Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal sourceType As System.Type) As Boolean
    If sourceType Is GetType(String) Then Return True
    Return MyBase.CanConvertFrom(context, sourceType)
End Function

こんな感じで大丈夫。そしてConvertFromメソッドでは実際の変換処理を記述してあげればOK。内部でプロパティでSystem.Typeを指定したい?(3)の時に書いたロジックを使うのでそこも参照してください。

''' <summary>指定したコンテキストとカルチャ情報を使用して、指定したオブジェクトをコンバータの型に変換します</summary>
Public Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
    If TypeOf value Is String Then
        For Each child As System.Type In Me.GetClassList(context)
            If child.Name.Replace(child.Namespace, "") = value.ToString Then
                Dim result As Object = Nothing
                result = System.Activator.CreateInstance(child)
                Return result.GetType
            End If
        Next
        Return Nothing
    End If
    Return MyBase.ConvertFrom(context, culture, value)
End Function

こんな具合かな?利用できるクラスの一覧を取得して、一覧に存在するクラスが指定された際にはそのTypeを返却する。一覧に存在しない際はNullを返却する。返却する際には一覧で保持しているクラス名から、実際のインスタンスを生成(System.Activator.CreateInstance)して、そのTypeを取得しています。

とまあ、こんな感じでFrom関係のメソッドも大したことがないのよね。To関係のメソッドもそれほど違いはないので、次回の記事と見比べてもらうといいかも。

外人さんのやる気

日本人と外国人との差に、「他の国の言葉でも躊躇無く突撃してくる」というのがある気がする。

MSDNフォーラムでそれっぽいスレッドがあったんだよねぇ。
恐らくは機械翻訳かけて投稿したんだろうけど・・・。
自分はそこまでできないなぁ・・・。やるなら自力で文章起こさないと気がすまない。

重みのない文字

SqlServerのMVPな人の記事から。

今まで全く知らなかった、こういった「重みの無い文字」。U+32C0とかその類の文字なんて、この記事みるまで存在すら知らなかったw

んで、記事ではPowerShellでのソート結果となっているんだけど、なんか雰囲気は.Net全般の様な気がするんだよねぇ・・・。SqlServerだとソートの照合順序指定に関わるからまだ判るけど、シンプルに「文字のソート」全体の話題だとしたら結構面白い問題かも。

まぁよほどアレな人じゃないとこんな文字打たないだろうけど。

2007年9月15日土曜日

ヘッダの拡張・・・どうするよ

DataGridView本体は色々拡張しているんだけど、未だに拡張を行っていない部分があるんだよねぇ。ヘッダなところが。

どうデザインさせるか」とかを考えると、エディタ作れるようにならなければ厳しい部分があるんだよねぇ。自分が使うだけならどうとでもやるんだけど、他の人が使うのが大前提だからなぁ。難しいわぁ。

2007年9月13日木曜日

プロパティでSystem.Typeを指定したい?(3)

前回書いた6つのメソッドの中で、まずはGetStandardValuesメソッドとGetStandardValuesSupportedメソッドから。

「標準値リスト」を提供する部分に関わるメソッドで、IDE側ではGetStandardValuesSupportedメソッドがTrueを返した際に、GetStandardValuesメソッドでドロップダウンリストに表示する一覧を取得しにくる、という流れになっているのよ。

んじゃ今回提供したい標準値リストって何が入っていればいいか?と言われると。

「自分のプロジェクトと参照設定されているアセンブリなどに存在するクラスの一覧」

が目標になるわけだ。同じようなものとしては、DataGridViewのカラムを設定するあたりだね。あれと同じ動き。とりあえず説明はあとで大体のロジックを。

''' <summary>設定対象となるクラスの一覧を取得します</summary>
Protected Function GetClassList(ByVal context As System.ComponentModel.ITypeDescriptorContext) As ArrayList
    Dim service As ComponentModel.Design.IDesignerHost _
        = DirectCast(context.GetService(GetType(ComponentModel.Design.IDesignerHost)), ComponentModel.Design.IDesignerHost)
    Dim discoveryService As ComponentModel.Design.ITypeDiscoveryService _
        = DirectCast(service.GetService(GetType(ComponentModel.Design.ITypeDiscoveryService)), ComponentModel.Design.ITypeDiscoveryService)

    Dim result As New ArrayList
    For Each childtype As System.Type In Me.FilterGenericTypes(discoveryService.GetTypes(Me._targetType, False))
        If ((childtype Is Me._targetType) OrElse childtype.IsAbstract) OrElse _
            (Not childtype.IsPublic AndAlso Not childtype.IsNestedPublic) Then Continue For

        Dim attribute As DataGridViewColumnDesignTimeVisibleAttribute _
            = TryCast(ComponentModel.TypeDescriptor.GetAttributes(childtype).Item(GetType(DataGridViewColumnDesignTimeVisibleAttribute)), DataGridViewColumnDesignTimeVisibleAttribute)

        If (attribute Is Nothing) OrElse attribute.Visible Then result.Add(childtype)
    Next
    Return result
End Function

''' <summary>インスタンス化できないクラスを一覧から除去します</summary>
Private Function FilterGenericTypes(ByVal childTypes As ICollection) As ICollection
    If (childTypes Is Nothing) OrElse _
       (childTypes.Count = 0) Then Return childTypes

    Dim childArray As New ArrayList(childTypes.Count)
    For Each type As System.Type In childTypes
        If Not type.ContainsGenericParameters Then childArray.Add(type)
    Next
    Return childArray
End Function

中々お目にかからないクラスが多いと思う。IDesignerHostとかITypeDiscoveryServiceとか。ざっくりと言ってしまうと、ITypeDiscoveryServiceインターフェースは「プロジェクトと参照設定されているアセンブリ」に対して探し物をしてくれるクラスが継承しているもの。IDesignerHostインターフェースはMSDNあたりで見てくださいw
なのでITypeDiscoveryServiceを利用してクラス一覧を取得、んでその中からインスタンス化できないクラスを除去。もう一つついでにデザイン時に利用していいクラスかどうか(DataGridViewColumnDesignTimeVisibleAttribute)を判断して一覧を整理してあげればOK。ただ、最後の属性を見てってのは特にやらなくても問題ないケースが多いかな。デザイン時には非表示だけど実行時には使いたいようなクラスを自前で用意するような、そんな面白いケースになった時にこの属性を使ってあげていればいいんだけどね。今のところであってません、そんなケースにはw

これで標準値リストについては大体OK。このロジックは、コントロールを拡張しだすと結構使いたい場面が多くなる・・・人もいるかな?

曜日名を全て取得する

昔:Constなどで定義、またはマジックナンバーとして焼付け
今:System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.DayNamesでString配列として取得

色々考えるとSystem.Globalization関係って使っていたほうが楽になりそう。和暦→西暦変換とか西暦→和暦変換もOSの機能で用意されているものを利用できるんだよね、こっちだと。

Cultureの中でCalendarクラスのインスタンスを持っているんだけど、こいつが色々用意されているのよ。和暦用にJapaneseCalendarクラスとかもあるんで。

ここらへんを使うと日付系のロジックは結構シンプルにいけるのかも・・・?

2007年9月12日水曜日

ISO8601

1週間というものを定義しているISOがあるなんて知りませでしたよ、ええ。

こいつに基くと、最初の週は「1 年の最初の週が、週の最初の曜日として指定されている曜日が次に訪れるまでに 4 日以上かかる週になるように指定します。」なんてルールに基くので、1月2日が土曜日なんて場合は、1/1~1/2は「前年度の最終の週」という扱いにするそうだ。

第x週の扱いを調べているとこんなのが出てきて少々驚き。
・・・ま、使わないんだろうなぁw

プロパティでSystem.Typeを指定したい?(2)

プロパティでTypeConverterを利用したデザイン時サポートを行う際には、System.ComponentModel.TypeConverterクラスを継承して実装していくのが楽なんだと思う。まぁ実際には大した作業量の違いはないかも知れないけど。で、このクラスのメソッドで実装する必要になるのは次にあげるものくらい。

  • GetStandardValuesSupportedメソッド
  • GetStandardValuesメソッド
  • CanConvertFromメソッド
  • CanConvertToメソッド
  • ConvertFromメソッド
  • ConvertToメソッド

上にあげた6つのメソッドは、実際のところ2つ*3組という考え方で捉えるといいかも。

まずはGetStandardValuesSupportedメソッドとGetStandardValuesメソッド。これはプロパティダイアログでドロップダウン型の時に関わってくるメソッドで、「標準値リスト」というのをサポートするかどうか、という関連で利用されるね。GetStandardValuesSupportedメソッドでTrueを返却するなら標準値リストをサポートする、Falseならサポートしないという風にIDE側で判断するのに使われます。ドロップダウン型は標準値リストをサポートする時だけ利用できると思っていればいいかな。そしてそのリストに設定される値一覧がGetStandardValuesメソッドで取得できる、と。

そしてCanConvertFromメソッドとConvertFromメソッド。これは「ある型・クラスから、このTypeConverterで扱う型・クラスに変換」という処理に関連している。変換可能な場合はCanConvertFromメソッドでTrueを返却して、実際の変換処理をConvertFromメソッド内部で行うことになるね。今回みたいなケースでは「文字列からSystem.Type」という変換をすればよし。

それと逆の動作をするのがCanConvertToメソッドとConvertToメソッド。こっちは「TypeConverterで扱う型・クラスをある型・クラスに変換」という処理を行うことになる。変換可能であればCanConvertToメソッドでTrueを返却、実際の処理をConvertToメソッドで行えばよし。実際にはConvertFrom程処理は少なくないんだよね、こっち。

細かい中身については次回以降で。

2007年9月8日土曜日

プログラムのセルフアップデート

Vista以降のOSで一番問題になるのがこれかも。

今回のシステムでも(一応)は機能として最新モジュールのコピーやらそういった機能を持たせているんだけど、適切に設定されているセキュリティ、具体的に言うとProgramFilesフォルダ関連にセットアップしだ場合は、セルフアップデート機能は「自分自身」以外は失敗してしまうことになるね。
セキュリティ的な方面から言うと、

ProgramFilesはユーザー領域ではなく、システム領域なので勝手に更新されるのはよくない

という事なんだよな。気持ちはわかる。管理者でも読み取りと実行権限しか持たせないってのはよくわかる。実際に対応するとなるとWindowsInstallerかClickOnceなどの純正テクノロジを利用するのがベスト。VistaやWindows2008Serverなどで用意されている救済策を利用して、セットアップ系プログラムと誤認させる手段もあることはあるけど、かなり後ろ向きというかいきあたりばったり。

そう考えると次の3通りになるのかなぁ?

  1. システムのAP全てに自己更新機能を持たせる(ClickOnce)
  2. 特定のタイミングでシステム全体のAPをインストールするmsiを作成し、ActiveDirectoryなどで配布
  3. ProgramFilesにインストールさせない(w

技術屋としては1か2だけど・・・。多分会社としては3を選択しそうな気がしてならないw

2007年9月7日金曜日

プロパティでSystem.Typeを指定したい?

あるクラスのプロパティでSystem.Typeなものを指定したい場合というのは結構あるだろうし、普通にロジックで

Public Property DummyType As System.Type
End Property

なんて実装してしまえばできるから使っている人は多いと思う。で、今回の伝票レイアウトな話題でも、それを利用しているんだけど単純にロジック書くだけではちょっと問題があるんですな、DataGridView関連では。

それは「InitializeComponentメソッドで処理されないプロパティは、明示的に対応処理を行わないといけない」という問題。DataGridViewで有名どころはデザイン時のRowTemplate関連かな?こいつはデザイン時にプロパティを設定しても、デザイン画面では即座に反映されてくれない。一度表示されている行を消して、その後に戻してあげないといけないんだよね。仕様としてRowTemplate関連は「行を追加する」タイミングでしか適用されないので、仕様通りっちゃーその通りなんですな。
これと似ていて、ある種のプロパティはInitializeComponent内部で操作されていないと、実装者がロジックでゴリゴリ対応してもらわないといけない状態になるんだよね。そして普通にSystem.Typeなプロパティは標準で設定するダイアログといういのがないのでInitializeComponent内部で設定してもらうことができない → 実装者がゴリゴリ、となってしまうわけだ。こりゃあ使いづらい。というかバグ出してくれそうw

ではどうするか、というとSystem.ComponentModel.TypeConverterというクラスを利用することになります。これは「変換」を行うためのクラスで、「文字列→インスタンス」とか「インスタンス→文字列」なんてことをするために利用するね。今回ではプロパティダイアログ上で、「クラスを選択→クラス名を文字列で」と「クラス名→System.Type」という機能が必要なので用意することにします。

詳しい話は次回以降。

これからの文字項目の桁数

今のところデータベースの文字型項目で容量を考える際、「その項目に必要なバイト数」で領域を定義していると思うんだけど、今後を考えるとこのやり方では非常にマズイというか。

CHARやVARCHAR世代は主にバイト数でカウントする世代で文字コードなんてものは全然考えていない場合が多い。文字コードを考え出して、「バイト数で定義なんてできねーよ」となってでてきたNCHARやNVARCHAR世代は、文字数でカウントする世代だね。後者の「文字数で定義」になれている人ならサロゲートペアがこようと基本は問題ない。
(内部的にバイト数を利用していたりしなければ)

そしてもう一つの問題がVistaより可能になった「UniCode文字結合」。サロゲートペアは「補助として次の16bitを利用する」という1文字=32bitな特殊字のことで、文字結合というのは「が」と「か + ゛」は同一に扱うという問題。本当に厄介なのはこっちかも知れない。何しろVistaからなんだから。

対応としては、文字列を扱うときにはカルチャを意識してロジック組まないといけないってことだそうで。文字を結合するのは現状と同じでいいけど、比較するときにカルチャを指定するってこと。これを踏まえると文字列比較用のメソッドを、今のうちに用意しておいたほうがいいってことなんだよねぇ。

ちなみに文字結合はSqlServer2005では対応されている。「が」という文字を検索したとき、「か + ゛」もちゃんと条件にひっかかってくれる。

・・・あれ?てことはNCHARって固定長じゃないの?それとも2文字分使うってことなのか??

2007年9月4日火曜日

シマンテックやるねェ!

サーバープログラムをアンインストールするとプログラムフォルダの内容が削除される

シマンテックが久々に放ったホームランw
いやぁ、こりゃ豪快すぎる。

伝票レイアウトセル(5) セル その3

Paintメソッドでイメージを表示させるにあたってもう一つ考えておく必要がある話題が。

というのも、このPaintメソッド自体はやたらめったらに呼ばれるから。
「マウスを動かす」「キーを押す」などDataGridViewでちょっとでも変更が起きそうな際には必ず呼ばれる。
なのでそのあたりを考慮しておかないと、もともとコストの高いイメージキャプチャ処理なので重いAPとなってしまうわけだ。

んじゃ何をもって判断してあげればよいかというと。

  • DataGridViewElementStates
  • DataGridViewCellStyle

この二つ。DataGridViewElementStatesは引数elementstateに設定されて、DataGridViewCellStyleは引数cellstyleに設定されてくる。これらの中身をセルレベルで保持させておいて、Paintメソッドの都度引数と保持したものとを比較し、異なる際に限ってイメージのキャプチャと描写処理を行ってあげるのがベターだと思う。
・・・多分もう少し適切な方法もあるとは思うんだけど。

ちなみにelementstateはEnumなのでそのまま保持だけど、cellstyleはClassなので、cellstyle.Cloneの結果を保持するようにしておく必要があるね。そうしないと引数と保持しているものが同じものを参照してしまう。

''' <summary>現在の DataGridViewCell を描画します。</summary>
Protected Overrides Sub Paint(ByVal graphics As System.Drawing.Graphics, _
                              ByVal clipBounds As System.Drawing.Rectangle, _
                              ByVal cellBounds As System.Drawing.Rectangle, _
                              ByVal rowIndex As Integer, _
                              ByVal elementState As System.Windows.Forms.DataGridViewElementStates, _
                              ByVal value As Object, _
                              ByVal formattedValue As Object, _
                              ByVal errorText As String, _
                              ByVal cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
                              ByVal advancedBorderStyle As System.Windows.Forms.DataGridViewAdvancedBorderStyle, _
                              ByVal paintParts As System.Windows.Forms.DataGridViewPaintParts)

    MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)

    If Me._editorInstanceType Is Nothing Then Return
    If (_beforeState <> elementState) OrElse _
       (_beforeCellStyle Is Nothing) OrElse _
       (Not _beforeCellStyle.ToString = cellStyle.ToString) Then

        Using subEditor As MultiLayoutControl _
            = TryCast(System.Activator.CreateInstance(Me._editorInstanceType), MultiLayoutControl)

            If subEditor Is Nothing Then Return

            subEditor.Top = subEditor.Height * -1
            subEditor.Left = subEditor.Width * -1
            If (Me.DataGridView IsNot Nothing) AndAlso _
               (Me.DataGridView.TopLevelControl IsNot Nothing) Then
                Me.DataGridView.TopLevelControl.SuspendLayout()
                Me.DataGridView.TopLevelControl.Controls.Add(subEditor)
            End If

            '色とフォントの設定
            If (elementState And DataGridViewElementStates.Selected) = DataGridViewElementStates.Selected Then
                subEditor.BackColor = cellStyle.SelectionBackColor
                subEditor.ForeColor = cellStyle.SelectionForeColor
            Else
                subEditor.BackColor = cellStyle.BackColor
                subEditor.ForeColor = cellStyle.ForeColor
            End If
            subEditor.Font = cellStyle.Font

            subEditor.Values = TryCast(value, SubEditor.SubEditorValues)
            subEditor.OnConvertValuesToInterface()
            'イメージキャプチャのメソッドを呼び出す
            _controlImage = subEditor.CreateControlImage(False)

            If (Me.DataGridView IsNot Nothing) AndAlso _
               (Me.DataGridView.TopLevelControl IsNot Nothing) Then
                Me.DataGridView.TopLevelControl.Controls.Remove(subEditor)
                Me.DataGridView.TopLevelControl.ResumeLayout()
            End If

        End Using
        _beforeState = elementState
        _beforeCellStyle = cellStyle.Clone
    End If

    'イメージの描写
    graphics.DrawImage(_controlImage, cellBounds.X, cellBounds.Y, _controlImage.Width, _controlImage.Height)
    '枠線が必要な際は描写
    If (paintParts And DataGridViewPaintParts.Border) = DataGridViewPaintParts.Border Then
        MyBase.PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle)
    End If
End Sub

ロジックとしてはこんな感じかと。ここまでの記事で用意した拡張メソッドやらなんやらてんこもりなので、昔の記事も色々と見てください。

2007年9月3日月曜日

伝票レイアウトセル(5) セル その2

今回はセルクラスの一番のポイント。Paintメソッド。

このメソッドでやることってのは単純で、「DataGridViewの必要な領域にイメージを描写する」ということ。
テキスト系のセルであれば、設定されている値とかを画面に描写するし、イメージ系であればそのままグラフィックを描写してあげればいいんだよね。

ただこの伝票レイアウトセルの場合、表示したいイメージというのはセル側からではわからないのよ。
(実際には編集コントロールにて生成されるユーザーコントロールのイメージを描写したいから)

そのためDataGridView.Paintメソッドでは「編集コントロールでホストするコントロールのインスタンスを生成、値を展開後イメージをキャプチャする」という処理が必要になるね。どうみても重い処理になるのは間違いないところだw

ユーザーコントロールその2で書いてあるイメージのキャプチャを利用することになりやす。
こいつを「値の展開後」に呼び出してあげてキャプチャ、その次にgraphics.DrawImageメソッドで画面に表示というか描写というか。ユーザーコントロールその1で定義した、「値をコントロールへ」と「コントロールから値へ」というイベントはここで呼び出すことになります。

あー、なのでイベント用メソッドはProtectedで問題なし、というのは間違いですね。Protected Friendじゃないといけないっす。

ちなみにインスタンスを生成した際、親DataGridViewに追加するのではなく、親.TopLevelControl.Controls.Addとしたほうがいいと思われ。そのほうが余分な制御が走らない分いいと思いますわ。もちろん追加するインスタンスの座標は-1でも掛けて目に見えない場所にしておくこと。そうすればOK。

それでPaintメソッドはもう少し話が続くので次回の記事までひっぱります。
毎回こんな処理やられたら大変だー、というところです。

複数バージョンのOfficeを同居すると・・・

複数のバージョンの Office がインストールされている場合の Office オートメーションについてKBで公開された記事なんだけど、これを読むと「なんちゅー作りだ」と思ってしまう・・・。

原則異なるバージョンの同居を許さない、という思想だからなんだろうけどねぇ・・・。インストール時、バージョンによっては起動時にPROGIDの値を更新してるってんなら、そりゃ毎回挙動が代わるわな。

2007年8月31日金曜日

伝票レイアウトセル(5) セル その1

ある程度編集コントロール側ができてきたので、今度はセル側の追加実装をはじめましょう。

セルクラス その2でちょっとだけ扱ったInitializeEditingControlメソッド。
こいつの中で行うことをちょっとだけ(w)説明。

渡されてくる引数についてはMSDNとかを見てほしい。
ここでやるべき重要なことってのはそれほど多くは無いね。

  • 利用される編集コントロールへ必要な値を設定する
  • 編集コントロールからユーザーコントロールへと値を展開するイベントを発生させる

の2点くらい。ロジックとしてはこんな感じかな。

''' <summary>セルの編集に使用されるコントロールを初期化します</summary>
Public Overrides Sub InitializeEditingControl( _
ByVal rowIndex As Integer, _
ByVal initialFormattedValue As Object, _
ByVal dataGridViewCellStyle As System.Windows.Forms.DataGridViewCellStyle)

MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)

Dim columnSubEditorEdit As MultiLayoutColumn _
= TryCast(DataGridView.Columns(ColumnIndex), MultiLayoutColumn)
If columnSubEditorEdit IsNot Nothing Then
_editorInstanceType = columnSubEditorEdit.EditorInstanceType
End If

Dim target As MultiLayoutEditingControl = TryCast(Me.DataGridView.EditingControl, MultiLayoutEditingControl)
If target Is Nothing Then Return
With target
'必要なプロパティの設定
.EditingControlDataGridView = Me.DataGridView
.EditingControlRowIndex = Me.RowIndex
.EditingControlValueChanged = False
'値を設定しコントロールのインスタンスを生成させる
.Values = TryCast(Me.Value, SubEditor.SubEditorValues)
.SetEditorInstance()
If .EditorInstance IsNot Nothing Then
'イベントの発生
.EditorInstance.OnConvertValuesToInterface()
End If
End With
End Sub

今回もまた、実際に利用しているロジックなので説明していない部分はあるけどスルーな方向でw
重要なのは、Dim target As MultiLayoutEditingControl ~の行から下。
セル側から編集コントロールへ、実際の編集に利用するユーザーコントロールの生成をお願いする必要があるわけだ。これをしないと編集状態になっても何も表示されないままなんだよね。IDataGridViewEditingControl側ではいつコントロールのインスタンスを生成すればよいのか、というのはつかめないのでInitializeEditingControlメソッド内部で呼び出してあげないといけないわけ。

次はいよいよ大物。Paintメソッドにとりかかろう~。

2007年8月30日木曜日

伝票レイアウトセル(4) セル・ユーザーコントロール その3

もう少しユーザーコントロール側で必要な機能があるので。
それは「」と「フォント」。
大体は親であるDetaGridViewでのStyleを継承した表示にするんだけど、そのあたりに対応しておくと楽。

一つ一つコントロールに設定・・・はさすがに面倒なので、こんな感じにオーバーライド。

''' <summary>BackColorChangedイベントを発生させます</summary>
Protected Overrides Sub OnBackColorChanged(ByVal e As System.EventArgs)
    MyBase.OnBackColorChanged(e)
    For Each child As System.Windows.Forms.Control In Me.Controls
        child.BackColor = Me.BackColor
    Next
End Sub

''' <summary>ForeColorChangedイベントを発生させます</summary>
Protected Overrides Sub OnForeColorChanged(ByVal e As System.EventArgs)
    MyBase.OnForeColorChanged(e)
    For Each child As System.Windows.Forms.Control In Me.Controls
        child.ForeColor = Me.ForeColor
    Next
End Sub

''' <summary>FontChangedイベントを発生させます</summary>
Protected Overrides Sub OnFontChanged(ByVal e As System.EventArgs)
    MyBase.OnFontChanged(e)
    For Each child As System.Windows.Forms.Control In Me.Controls
        child.Font = Me.Font
    Next
End Sub

こんな感じで、OnBackColorChanged、OnForeColorChanged、OnFontChangedメソッドをそれぞれオーバーライドしておくと、これを継承して実装している限りは特に意識しなくてもユーザーコントロールに設定された色やフォントの情報が一気に適用されるね。それと、こういうときイベントで記述するかOn~メソッドのオーバーライドで記述するかって悩むけど、原則はメソッドのオーバーライドだと思う。結果同じにしても。

ちなみに実際にセルのStyleを適用させようとしてくるのは、DataGridViewCell.InitializeEditingControlメソッドとIDataGridViewEditingControl.ApplyCellStyleToEditingControlメソッドで行う。

2007年8月29日水曜日

伝票レイアウトセル(4) セル・ユーザーコントロール その2

セルに表示したいイメージのキャプチャーについて、というちゃんとやるなら深い話・・・。
なんだけど、そこまでのスキルも持っていないので今回利用した方法をってとこで。

まず目標としては、「ある状態のユーザーコントロールのキャプチャーを行って、それをセルに表示する」というところ。DataGridViewで提供されているセルでは全て自前で描写しているんだけど、今回はその手は使えない。
理由は簡単で、ユーザーコントロール上にどんなものが配置されるかもわからないから

なので単純にキャプチャしてしまいそれを表示しよう、という手段の方が楽だったってことなんだよねw
もちろん本当に単純にキャプチャできるわけはなかったんだけど。

今回使ったのはこんなやり方。
ユーザーコントロールのOnPainjtメソッドを利用してGraphicsクラスのインスタンスを取得する
これであればユーザーコントロール上に何を置かれても、原則はイメージを取得できる。DrawToBitmapメソッドのように特定コントロールで利用できないとかそういう問題はないしね。ただ、Graphicsを取得する際に気をつけないといけないのが一点だけある。

「Visible=TrueでなければGraphicsには描写されない」

という点。ただし、ここは勘違いしやすいポイントでもあるんだけど「目に見えている」必要はなく、ただ「VisibleプロパティがTrue」であるだけでGraphicsに描写はされるんだよね。ここに引っかからなければ解決も同然。イメージをキャプチャする際には、生成したユーザーコントロールを親フォームの可視領域外(マイナス座標とか)に配置してしまえばいいのだ。その時には、ダミーでフォーカスを移せるコントロールを一つ新たに作成し、そこにフォーカスを移した状態でキャプチャするとOK。そうでないとカーソルが残った状態がキャプチャされてしまう。テキストボックスとかでいいんじゃないかな?
それでは実際のロジックを。

Dim beforeControl As System.Windows.Forms.Control = Me.ActiveOwnerControl
'ダミーコントロールの追加
Dim dummyControl As New System.Windows.Forms.TextBox
With dummyControl
    .Name = "dummy"
    .Size = New System.Drawing.Size(0, 0)
End With
Me.Controls.Add(dummyControl)
dummyControl.Focus()
Me.Refresh()

'Graphicsを作成する
Dim targetControl As System.Windows.Forms.Control = Nothing
Dim targetGraphic As System.Drawing.Graphics = Nothing
targetControl = Me
targetGraphic = targetControl.CreateGraphics
Dim paintArgs As New System.Windows.Forms.PaintEventArgs(targetGraphic, targetControl.ClientRectangle)
Me.OnPaint(paintArgs)

'GraphicsオブジェクトよりBitmapオブジェクトのコピー
Using contGrap As System.Drawing.Graphics = targetGraphic
    Dim originalHdc As IntPtr = contGrap.GetHdc
    Using bmpGrap As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(_controlImage)
        Dim targetHdc As IntPtr = bmpGrap.GetHdc
        MultiLayoutControl.BitBlt(targetHdc, 0, 0, targetControl.Width, targetControl.Height, _
                                  originalHdc, targetControl.Left, targetControl.Top, SRCCOPY)
        bmpGrap.ReleaseHdc(targetHdc)
    End Using
    contGrap.ReleaseHdc(originalHdc)
End Using
targetGraphic.Dispose()
targetControl = Nothing

If beforeControl IsNot Nothing Then beforeControl.Focus()
Me.Controls.Remove(dummyControl)

このソースは実際に利用しているものなので、独自に拡張した部分も混じってます。
ActiveOwnerControlは、ControlクラスにもActiveControlを追加したヤツです。実際のロジックはちょっと色々あるんだけど。MultiLayoutControlというのは、このユーザーコントロール自身のクラス名です。そして真ん中あたりのGraphicsからBitMapをコピーするのは、どこかのサイトでサンプルがあったので利用したんだけど・・・どこのサイトかが忘れてしまいました。

とりあえずはこの方法でキャプチャできたので、セル側ではこのイメージを描写してもらう事になりますな。
それはまた後で。

伝票レイアウトセル(4) セル・ユーザーコントロール その1

伝票レイアウトセルで編集時に利用するユーザーコントロールの基本部分について。

このコントロールで必要になる機能というのが色々あるのよね。

  • 編集開始時に値からコントロールへと表示させる
  • 編集終了時にコントロール上の状態を値へと変換する
  • セルに表示するためのイメージキャプチャ

上二つは、簡単に書いてしまっているけど「キー制御をどうするか」という問題と密接にからんでいたりする。というのも、
業務APではよくある「Enterキーで次へ~」といった制御と絡んでくる問題と、もともとDataGridViewでもっている「編集終了=F2」の扱い。
Enterキーの方はここにとどまらない問題なので、それぞれのお仕事で決めて欲しいw

もう一つ、F2キーの対応でDataGridViewの流儀にのっとってF2=編集終了、とする場合はIDataGridViewEditingControl.EditingControlWantsInputKey
メソッドで「F2はDataGridView側で処理する(Return False)」するように書き換えてあげれば大丈夫だと思う。
自分の場合、どんなコントロールがセルの上で利用されるかが判断つかなかった、というのを優先したので
F2で編集終了という機能は外した。なので、違うセルにいかないと編集終了しませんw

それで実際に上二つを対応するとなると、イベントを発生させるやり方がベターかなと思うね。
イベントであればVisualStudioのIDE側で見て判るから。なのでこんな定義で。

''' <summary>インターフェースで設定された値をValuesプロパティに展開する必要がある際に発生します</summary>
''' <remarks>インターフェース上で入力された値を、Valuesプロパティに保持させる処理を行います。</remarks>
Public Event ConvertInterfaceToValues()

''' <summary>Valuesプロパティの値を元にインターフェースに表示をする必要がある際に発生します</summary>
''' <remarks>Valuesプロパティで保持されているセルの値を、サブエディタの画面上に展開する処理を行います。</remarks>
Public Event ConvertValuesToInterface()

あとはコレと対になるイベント用メソッドを用意しておく。んで、ここは.Netの流儀に従っておくといいかな。

''' <summary>Valuesプロパティで保持している値をインターフェース上に展開します</summary>
''' <remarks>ConvertValuesToInterfaceイベントを発生させます</remarks>
Protected Friend Overridable Sub OnConvertValuesToInterface()
    RaiseEvent ConvertValuesToInterface()
End Sub

''' <summary>インターフェース上の値をValuesプロパティに変換します</summary>
''' <remarks>ConvertInterfaceToValuesイベントを発生させます</remarks>
Protected Friend Overridable Sub OnConvertInterfaceToValues()
    RaiseEvent ConvertInterfaceToValues()
End Sub

コメントは下手なのであまり気にせずにw それとアクセス修飾子はProtected Friend としてあるけど、Protectedで本当は
問題なし。

これでイベントまでは下準備できたので、次はイメージのキャプチャな話を。

2007年8月28日火曜日

.Netで開発してきて

やっぱりVB6までのレガシーと比較すると、格段に便利なんだよねぇ。
.NetFrameWork2.0世代で便利といえばここ最近の色々で判明してきた

「デリゲート」

コツを掴むと非常に便利、というか強力すぎ。
今までもコールバック対応ということでAddressOf演算子は利用できたんだけど、デリゲートになってからはその便利さが格段に増えているんだよなぁ。
昔はデザインパターンのDecoratorとかに沿って処理をするクラスを指定する方法(今の開発でいうと、KeyPressやDoubleClickのあたり)を使っていたんだけど、デリゲートを使うとなると、そのロジック自体がどこに書いてもいいことになるんだよね。クラスを用意するまでもないわけだ。

元々イベントの仕組みもデリゲートを利用しているので、今の状態でも意識しないでデリゲートは使っているんだけど、ここを意識して使うようになるだけでも大分変わるな(リフレクタとかで除いてみるとよくわかる)。

オブジェクト指向のキモの一つである多態性を実感するのには一番いい機能なんじゃないかなぁ。

伝票レイアウトセル(3) セルクラス その2

引き続いて今度はメソッド周り。わかってしまえばたいしたことはないんだよね。

InitializeEditingControlメソッド
編集が開始する際の初期処理を行うメソッド。今回で言うと、指定されている編集コントロールのインスタンスを生成させ
必要なプロパティをセルから編集コントロールへと受け渡すのが必要。
セルの値を渡して、その値を編集コントロール上へと展開させるメソッドでも読んであげればいいかね。

ParseFormattedValueメソッド
セルに表示されているものから、値へと変換する必要がある際に呼ばれるメソッド・・・なんだけど、伝票レイアウトセルでは
表示されているもの=編集コントロールのイメージなので、引数formattedvalueをそのままReturnしているだけで
特に問題なし。

GetFormattedValueメソッド
値をセルに表示するモノへと変換する必要がある際に呼ばれるメソッド・・・こいつも今回のカスタマイズでは特に重要
じゃないんだよね。編集コントロールのイメージを表示するのはOnPaintメソッド内部で行う必要がある、というかここで
どうこうしてもイメージ表示はできませぬ。ImageCellでも継承していればできるんだろうけど。そうするとセルの値=イメージ
になってしまって、微妙な使い心地へとw

Cloneメソッド
カスタマイズして増えたプロパティを、新規に生成したCloneインスタンスに追加設定してあげればOK。これは今回に
限ったことじゃないね。セルをカスタマイズする際はどこでも必要だ。

基本はこの3つのメソッド+厄介者何点か。

次はちょっととんで実際に表示するUserControlで必要になる機能の話かな。

2007年8月27日月曜日

伝票レイアウトセル(3) セルクラス その1

伝票レイアウトを実現するには、DataGridViewCellのカスタマイズを結構色々やる必要があるねぇ。
プロパティやメソッドを色々・・・ってのもあるけど、一番のヤマ場は

OnPaintメソッド

業務系のAPを作る人たちの中で、グラフィック描写の知識が多い人ってのはかなりレア度が高いと思う。
関わっている業態にもよるだろうけど、ふつーに販売管理だーなんだー、っていう人だったらなおさら未知の領域だと思われ。
Graphicsクラスとか、業務には全くかかわりの無い領域だからねぇ。こんなの知らなくたって業務システム作れるしw

なのでOnPaintは最後の最後として、まずは必要となるプロパティから。

オーバーライドする必要があるのは少なくて二つばっかり。

EditTypeプロパティ
このセルが利用する編集コントロールのTypeを返却するプロパティ。今回は、前回の記事までで利用している
IDataGridViewEditingControlインターフェースを継承したクラス、のTypeになるね。

ValueTypeプロパティ
このセルで扱う値のTypeを返却するプロパティ。今のところ特にそれ専用のクラスというのは用意してないけど、
実装のしやすさなどの面も考慮して、このあたりで一つ用意しておくといいかも。
あくまでも「そんなものは用意しない!」というならオーバーライドしないでもOK。気合で対応だ!

独自に追加するプロパティは今のところ一つかな?

編集時に「UserControlを継承したコントロール」を利用するっていう仕組みなんだけど、実際に利用するTypeを取得設定できる
ようなプロパティが必要。んで値を保持しておく必要もあるね。自分で作っているときはEditorInstanceTypeプロパティとか
そんな名前で作っているな。
それでこのプロパティについては、後々(この表現が多いけど)手を加えると楽になる事があるんだよね。

それはプロパティのグリッドから設定できるようにすること。

要はデザイン時に設定できるようにすることで、InitializeComoponentメソッド内部で実行してくれるから、DataGridViewが元々
抱えている不具合(RowTemplate.Heightが値を変更してもデザイン時に適用されない、とか)も気にしないですむんだよね。

ただ、これをやるにはTypeConverterという結構厄介なクラスと係わり合いになる必要があるので・・・。
一通りの実装が終わった後で記事にしようと思う。

2007年8月24日金曜日

伝票レイアウトセル(2) 編集コントロール その5

実際に編集コントロールをEditingPanelに追加するってのは、まぁ言葉通りだったりする。

Me._parentDataGridView.EditingPanel.SuspendLayout()
With Me._parentDataGridView.EditingPanel
    With .Controls
        .Clear()
        .Add(subEditorInstance)
    End With
    .Location = Me._parentDataGridView.PointToClient(cellPoint)
End With
Me._parentDataGridView.EditingPanel.ResumeLayout()

書いてしまえばこれだけなんだな、実は。EditingPanel自体はDataGridViewの編集に関わるほぼ全てで利用される
ので一度Clearしてあげてから、実際に表示したいコントロールをAddしてあげる。それでもってちょっと表示位置を
調整してあげたりすればもうOKだね。

ちなみにこれと逆に編集が終了した時の処理ってのは、DataGridViewCell.DetachEditingControlメソッド。
これが編集が終了して、ホストしているコントロールを解放してあげる処理ってわけだ。
後々このメソッドもオーバーライドする必要は出てくるんだけどね。

それはまた後ほど。

伝票レイアウトセル(2) 編集コントロール その4

編集コントロールでもう一つどうしても必要になる処理があるんだよね。それは、

「編集開始時にDataGridViewのEditingPanelに編集コントロールを追加する」

という処理。本来はIDataGridViewEditingControlを継承しているクラスは「単一コントロール」を継承して実装されている
のが殆どなので、DataGridView側で編集開始時にこのクラスのインスタンスをEditingPanelに自動で追加してくれているんだよね。
でも今回やろうとしている伝票レイアウトセルは、「UserControlを継承し、実際に利用するインスタンスはプロパティで設定」という
仕組みにしようとしているので、このあたりにも手を入れる必要があるのよ。

プロパティで定義させようとしているのは、簡単に言ってAP側で明細セルのデザインができる環境にしたかったから。
デザインする必要が無いなら、Dll側で既にデザインされたコントロールとIDataGridViewEditingControlを継承した
クラスを用意してしまえば事足りるのよね。

ちなみにDataGridViewの仕組みとして編集が開始されると、内部で保持しているEditingPanelというPanelコントロールに対して、
実際に編集に用いるコントロールが動的に作成される。そしてその動的に作成されたコントロールがDataGridView.EditingControl
プロパティで参照できるコントロール、という訳で。

ということでこういった処理をするメソッドを、Protected Friend あたりで指定した状態で用意しておく必要がありやす。

処理の中身は次の記事で。

2007年8月23日木曜日

伝票レイアウトセル(2) 編集コントロール その3

次は追加しないといけないプロパティ。

まず絶対必要になるのは実際の値を設定取得するプロパティ。まぁValueとかが妥当じゃないかと。
で、こいつが利用するクラスは前もって決めていた伝票レイアウトセルで利用する値のクラスにする。
実際は定義した値用クラスをそのまま利用するケースが殆ど多いとは思うよ。

「ソートしよう」とか「フィルタしよう」とか考えなければ(w

実際に利用している自分としては、やれソートだ、やれフィルタだという要件があったので、
値用クラスは抽象クラスとしてしまっているけど。

実際に実装を始めると、もう少しプロパティは増えていくことになるんだけどね・・・。
それはまた後でってことで。

キーボードとマウスのフック(3)

解決・・・!

別プロセスを起動する際にProcessクラスを利用して終了時イベントを拾っていたんだけど、これに関連していた。
どうやらSystem.Windows.Forms.Application.RemoveMessageFilterメソッドは自分自身のスレッドではなく、カレントのスレッドに対して行われる操作らしい。別プロセスが完全に終了しきっていない、Process.Exitイベント内部だと呼び出し先のAPのスレッドになってしまうのかな?フィルタが解除されないのはそういった理由だと思われる。完全に終了するまでループさせて、その後にフィルタ解除するようにすると問題なく解除できた。

ちなみにそのおかげである状態までロジックでウエイトをかけるやりかたも決定できたよ。
Do While 条件
    System.Threading.Thread.Sleep(1)
    System.Windows.Forms.Application.DoEvents()
Loop
これでいくと単純にループして待ったときのようにCPU100%にはならずに、ついでに再描写なども起こってくれる待ちロジックになったね。
でもなんかApplicationクラスの動きとしては変な感じがするなぁ、コレ。

キーボードとマウスのフック(2)

前回の方法でフィルタリングだけはできた。
System.Application.AddMessageFilterメソッドで新しいフィルタ(IMassegeFilterを継承した)を追加してあげればあっさりとフィルタできた。

ところが、だ。

フィルタが解除できない(´Д⊂
同じようにSystem.Application.RemoveMessageFilterメソッドでAdd~のときに追加したインスタンスを指定してあげているんだけど、解除できない。

なんで??

伝票レイアウトセル(2) 編集コントロール その2

引き続きメソッド周りを。

ApplyCellStyleToEditingControlメソッド

編集開始の段階で行うスタイル調整。背景色とかフォントとかを編集コントロールに設定するメソッド。
今回は色関連とフォントくらいでいいのかな。

EditingControlWantsInputKeyメソッド

編集コントロールで処理するキーかDataGridViewで処理するキーかを判断するメソッド。
今回みたいに「何でもあり」な編集コントロールは、黙ってTrueを返却しておいて問題なし。

GetEditingControlFormattedValueメソッド

編集された値を取得するメソッド・・・。注意する点があって、「編集された」といわれていてもセルに表示する文字列を
返却していればいいか、と言われると違うんだよねぇ。
原則は引数contextに設定されているDataGridViewDataErrorContextsの値がDataGridViewDataErrorContexts.Formattingを
含んでいる場合は文字列を。それ以外のときは実際の値を返却するのがいい、ってとこだ。含んでいる、というように、
この引数はビット演算してあげる必要があるね。

PrepareEditingControlForEditメソッド

セルの編集準備を行うメソッド。テキストボックス系とかだったらここで全選択の処理を行ったりするんだけど、
伝票レイアウトセルとしてはすることは何もないw

一応基本のメソッドとしてはこんな感じ。GetEditingControlFormattedValueメソッド以外は特に難しいところもないと思う。
GetEditingControlFormattedValueメソッドだけは「セルで使う値」を考えてからの実装するほうがいいかも。

2007年8月22日水曜日

キーボードとマウスのフック

同期処理をちょいちょいといじっているんだけど、相変わらずこれが不調。
なんだサンプル通りやっても入力を握りつぶせなーい!、と悩んでいたんだけど、どうやら色々とマネージな世界から外れないとできない様子で。

スレッドID拾うだけでも「これは旧形式です」なんて怒られる様子からすると、なんか違う方法でいくんだろうなぁ・・・なんて思ってたら。
できそうな記述を発見。Application.AddMessageFilterなんてものが。

今日は時間切れだけど、明日はなんとか形にしないと・・・。

伝票レイアウトセル(2) 編集コントロール その1

伝票レイアウトなセルで利用する編集コントロールは、UserControlとIDataGridViewEditingControlを継承するものとなる。
まずは単純なプロパティ周りから。

EditingControlDataGridViewプロパティ
この編集コントロールの親となるDataGridViewを保持取得するためのプロパティ。なんか変数作ってそれに値を設定取得するだけでOK

EditingControlRowIndexプロパティ
編集対象となる行のインデックス。これも変数作って設定取得できればOK

EditingControlValueChangedプロパティ
編集コントロールで値が変更されたかどうかを設定取得するためのプロパティ。いまのところ変数作って(略

EditingPanelCursorプロパティ
表示されている編集コントロールでのマウスポインタを取得するためのプロパティ。色々作ってみたけど、
Return MyBase,Cursor
しか書いたことがないw

RepositionEditingControlOnValueChangeプロパティ
値が変更される際に編集コントロールの位置を変更するかどうか・・・なんだけどこれも
Return False
しか書いたことがない

そしてもう一つ。EditingControlFormattedValueプロパティ
これは編集コントロールの値を表示用に変換するのと、変換された値から編集コントロールへと設定するという
二つの機能があるのよね。本来ならここもちゃんとする必要があるんだけど、伝票レイアウトを実現しようとした
際に限って「あまり重要じゃない」。セッターでは値を保持するくらいでいいし、ゲッターでは値のToStringなぞ
返却しておけば大丈夫だ。

理由がちゃんとあって、伝票レイアウトの場合セルに表示するのは文字列とかじゃなくグラフィックになるから。

なもんで、セルには値がはいっていればそれで大体OK。基本となるプロパティはこんな感じ。
次はメソッド関係を少しずついこうかな。

マニアな道へ

考えてみるとしばらくDataGridViewな話しか書いてないw

というよりも世の中.Net2.0から3.0へと移ろうとしているからなぁ。WPFでどうこうする方がメジャーになろうとしているのかも。

まぁ、自分はその中でもDataGridViewとしばらくおつきあいしないといけないのでまだまだ色々やっていくつもり。

完全にマニアな世界だw

2007年8月21日火曜日

伝票レイアウトセル(1) セルの値

二つ前の記事(w)のようなことをやるとなると、一つのセルで複数の値を保持する必要があるんだよね。
ということで問答無用で何かしらのコレクション(または派生クラス)を値に使う必要が出てくるわけで。
それでここらへんがデータバインディングを諦める理由のひとつだったり。

で、実を言うとセルの値にコレクションを使うだけならDataGridViewCellクラスでごにょごにょするだけで対応はできる。

DataGridViewTextBoxCellを拡張して値にコレクションを使おうとすると同じことをやる必要があるね。
ParseFormattedValueメソッドとGetFormattedValueメソッド。表示されている内容→値、と、値→表示する内容。
この二つの間のやりとりさえできれば、コレクションを使うのも問題なし。
手っ取り早くやるなら、コレクションのToStringメソッドの戻りをセルに表示する、としてしまえばOKだ。
値を作るときは、その逆で表示されている文字列からコレクションを新しく作ってあげればいいしね。

これで値は決まったので、次はセルクラスか編集コントロールかかな?

同期と非同期

あー、思ったよりも深いかも。

ProcessクラスのWaitForExitメソッドで外部プロセスを実行した際には、呼びもとのAPでは全てのメッセージ処理が行われない、ということがわかったので色々と方法を試しているんだけど。VB6時代に利用していたWaitForSingleObject APIを利用したパターンだとまた微妙な動作をしてくれる。

子プロセスとして起動したAPの背景だけがクリアされる。それ以外の部分は最描写もキチンと行われている・・・。
これがWaitForExitメソッドだと全て再描写されないんだよね。
動き的にはAPI使っているほうがいいんだけど、どっちもダメな事には変わりないねぇ。

後は今思いついている最後の手として、

APを同期で起動した際、呼び出し元のAPではマウスのローカルフックを行う起動されたAPの終了を監視し、終了時にローカルフックを終了させる

という方向でいこうかと。こっちでの問題は「別ユーザーで起動したAP」の終了判断なんだよなぁ。Processクラスで起動できればExitイベントがあるからいいんだけど、別ユーザーの場合API使って起動するもんでねぇ・・・。
さて、どうしようか?

伝票入力のためのDataGridViewセルカスタマイズ

一つ二つとセルを拡張していくと大体見えてくるだろうけど、
DataGridViewでセルを拡張する際には「セル」「カラム」「編集コントロール」の3点セットが基本となるんだよね。
そして編集コントロールというのは、「原則なんらかの通常コントロールを継承」して作られているわけで。

極論で言えば、「セルの値」と「表示方法」さえクリアできればControlから派生したものであるかぎり
何でも編集コントロールとして扱うことが可能なんだよね。

これを踏まえると伝票入力への対応というのもある程度方向性が決まると思う。

UserControlを継承した編集コントロール」であり「動的に利用するUserControlが指定可能」だと楽。

この方式で実装したものが一つ前の記事にあるようなものなわけだ。
あれはカラムのプロパティで利用するUserControlを指定し、編集時にはそいつのインスタンスを編集コントロールとして
扱うようになっている(実際には編集時だけでなく表示時にもインスタンスを作成する必要があるけど)。

ま、これができるとDataGridViewの中にDataGridViewだとかCheckedListBoxセルなんてものも対応できるようになるね。
ただしアンバウンドだけどな(w

データバインディングとUserControlを継承したセルってのは相性が悪いではなく、そもそもの前提が異なるからねぇ。
バインディングの便利さを捨てて、レイアウトの表現力を増やしたと思えばいいんだけどね。

2007年8月20日月曜日

DataGridViewカスタマイズの目標

gridcell

現在の目標はコレ。
DataGridViewでレイアウトを自由にしてしまおう、というところ。
コイツは実際に自分の手元で動作しているサンプル画面なので、
ここまでは辿り着けるってところだ。

まぁ細かい問題は色々と抱えているけど、やればここらへんまではこれますよー、という目標ということで。

さすがに怖い

前日の某お客さんのサーバー設定。

とりあえずリブートしてもらったのでリモート接続も復旧。当初の予定のドライブ容量変更にとりかかろうと、思ったんだけど。

一度解放するドライブで動作中のドライバやサービスがあったりしたら、どうなるんだろう?

今回はDドライブを一度解放して、容量調整するんだけどその時動作中のプログラムやらがDドライブにあったらどうなるんだろう・・・?
かなりやってみたい誘惑に駆られるけど、さすがに怖いw

DataGridViewのセルを拡張する(2) コンボボックスをアンバウンドで使う その2

初期状態のコンボボックスセルでは、普通のコントロールのように適当なクラスをアイテムに利用していると、
フォーカスが遷移するあたりで例外がサックリと発生してしまうと思う。逆に言えば、ここをクリアするとDataGridViewでも
コンボボックスは普段通り利用可能になるというコトで。じゃあ何が問題で例外が発生しているかというと。

  • コンボボックスの編集結果をセルに表示する
  • セルに表示されている値から編集時のコンボボックスを調整する

初期状態で足りないのはこの2点。実際には上の「セルに表示する」ってのは、致命傷というほどじゃなくそのままいこうと思えば
そのまま行ってしまえる程度なんだよね。なので本当に問題なのは下の「セルから編集時のコンボボックスを調整」する方。
ここが初期状態のコンボボックスセル関連で不足している部分。アンバウンドだから発生するんだよね、ここは。

じゃあこれをどこで行えばいいか、と言われると。
「編集の前段階」なのでDataGridViewCellで行う処理だ、ということになるんだよね。実際には

DataGridViewCell.ParseFormattedValue メソッド (System.Windows.Forms)

というメソッドがあるのでここで行うことに。実は違う方法もあるんだけど、それはまた別の機会かな。
TypeConverterを利用する方法は結構難しいところがあるので。

''' <summary>表示用に書式設定された値を実際のセル値に変換します</summary>
Public Overrides Function ParseFormattedValue( _
    ByVal formattedValue As Object, _
    ByVal cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
    ByVal formattedValueTypeConverter As System.ComponentModel.TypeConverter, _
    ByVal valueTypeConverter As System.ComponentModel.TypeConverter) As Object

    Dim columnEdit As ExComboItemColumn _
        = TryCast(Me.DataGridView.Columns(ColumnIndex), ExComboItemColumn)
    If columnEdit Is Nothing Then Return Nothing

    For Each cellItem As Object In columnEdit.Items
        If cellItem.ToString = formattedValue.ToString Then Return cellItem
    Next
    Return Nothing
End Function

大体こんな感じ。ここだけみると「あれ?」という感じになるんだけど、実はカラム側で下準備が一つある。

''' <summary>コンストラクタ</summary>
Public Sub New()
    MyBase.New()
    Me.CellTemplate = New ExComboItemCell
    Me.ValueType = GetType(ComboItem)
End Sub

カラム側のコンストラクタでValueTypeプロパティにComboItemクラスのTypeを設定しているところがある。
こうしておくと、このカラムではComboItemクラスのインスタンスを値として扱うよ、という宣言になるのよ。
では最終的なソースを。

''' <summary>独自クラスを利用できるコンボボックス型DataGridViewCell</summary>
Public Class ExComboItemCell
    Inherits System.Windows.Forms.DataGridViewComboBoxCell

    ''' <summary>
    ''' EditTypeプロパティ
    ''' </summary>
    ''' <value>ExComboItemEditingControlのType</value>
    ''' <remarks>ExComboItemEditingControlを返却する</remarks>
    Public Overrides ReadOnly Property EditType() As System.Type
        Get
            Return GetType(ExComboItemEditingControl)
        End Get
    End Property

    ''' <summary>入力コントロール初期化</summary>
    Public Overrides Sub InitializeEditingControl( _
        ByVal rowIndex As Integer, _
        ByVal initialFormattedValue As Object, _
        ByVal dataGridViewCellStyle As System.Windows.Forms.DataGridViewCellStyle)

        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)
        Dim columnEdit As ExComboItemColumn _
            = DirectCast(Me.DataGridView.Columns(ColumnIndex), ExComboItemColumn)
        If columnEdit IsNot Nothing Then

        End If
    End Sub

    ''' <summary>クローンの生成</summary>
    Public Overrides Function Clone() As Object
        'プロパティのクローン作成
        Dim cloneCell As ExComboItemCell = DirectCast(MyBase.Clone, ExComboItemCell)

        Return cloneCell
    End Function

    ''' <summary>表示用に書式設定された値を実際のセル値に変換します</summary>
    Public Overrides Function ParseFormattedValue( _
        ByVal formattedValue As Object, _
        ByVal cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
        ByVal formattedValueTypeConverter As System.ComponentModel.TypeConverter, _
        ByVal valueTypeConverter As System.ComponentModel.TypeConverter) As Object

        Dim columnEdit As ExComboItemColumn _
            = TryCast(Me.DataGridView.Columns(ColumnIndex), ExComboItemColumn)
        If columnEdit Is Nothing Then Return Nothing

        For Each cellItem As Object In columnEdit.Items
            If cellItem.ToString = formattedValue.ToString Then Return cellItem
        Next
        Return Nothing
    End Function

    ''' <summary>表示用に書式指定済みのセル値を取得します</summary>
    Protected Overrides Function GetFormattedValue( _
        ByVal value As Object, _
        ByVal rowIndex As Integer, _
        ByRef cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
        ByVal valueTypeConverter As System.ComponentModel.TypeConverter, _
        ByVal formattedValueTypeConverter As System.ComponentModel.TypeConverter, _
        ByVal context As System.Windows.Forms.DataGridViewDataErrorContexts) As Object

        If value Is Nothing Then Return ""
        Return value.ToString
    End Function
End Class

''' <summary>独自クラスを利用できるコンボボックス型DataGridViewColumn</summary>
<System.Diagnostics.DebuggerStepThrough()> _
Public Class ExComboItemColumn
    Inherits System.Windows.Forms.DataGridViewComboBoxColumn

    ''' <summary>コンストラクタ</summary>
    Public Sub New()
        MyBase.New()
        Me.CellTemplate = New ExComboItemCell
        Me.ValueType = GetType(ComboItem)
    End Sub

    ''' <summary>CellTemplateプロパティ</summary>
    ''' <exception cref="ArgumentException">ExComboItemCell以外が指定されました</exception>
    Public Overrides Property CellTemplate() As System.Windows.Forms.DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(ByVal value As System.Windows.Forms.DataGridViewCell)
            If Not (TypeOf value Is ExComboItemCell) Then
                Throw New ArgumentException("CellTemplateプロパティはExComboItemCellを設定する必要があります。")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property

End Class

''' <summary>独自クラスを利用できるコンボボックス型DataGridViewEditingControl</summary>
<System.ComponentModel.DesignTimeVisible(False)> _
<System.ComponentModel.ToolboxItem(False)> _
Public Class ExComboItemEditingControl
    Inherits System.Windows.Forms.DataGridViewComboBoxEditingControl

    ''' <summary>Enterイベント</summary>
    Protected Overrides Sub OnEnter(ByVal e As System.EventArgs)

        Dim nowIndex As Integer = Me.SelectedIndex
        Dim editColumn As ExComboItemColumn _
            = TryCast(Me.EditingControlDataGridView.Columns(Me.EditingControlDataGridView.CurrentCell.ColumnIndex), ExComboItemColumn)
        If editColumn Is Nothing Then Return
        Me.Items.Clear()
        For Each columnItem As Object In editColumn.Items
            Me.Items.Add(columnItem)
        Next
        Me.SelectedIndex = nowIndex
        MyBase.OnEnter(e)

    End Sub

End Class

ポイントは「面倒だからもともとのComboBoxセル関係を継承してしまう」というところ。
あと、これは微妙なんだけどExComboItemEditingControlのEnter時にアイテムの再設定を行っているんだけど、
これはセルのInitializeEditingControlメソッドで行うのがベターな気がするね。

DataGridViewのセルを拡張する(2) コンボボックスをアンバウンドで使う

DataGridViewを使おうと思う人なら、一度はひっかかりそうなのがコンボボックスセル。
これもねぇ、データバインディングして使う分には色々と資料やら掲示板やらがあるのでなんとかなるんだよねぇ。
ところがアンバウンドで、となると途端に資料が出てこないんだ、これが。

コンボボックスセルを使うために超えないといけない壁ってのがあるんだよね。

  • コンボボックスで扱うアイテムのクラス
  • 値と文字列との間で変換を掛ける

グリッド上でなくとも.Netのコンボボックスではアイテムにクラスを利用するので、その使い方に慣れれば最初の部分はOK。
まぁ二つ目の問題も最初の問題がクリアできている人にはたいした話じゃないんだよね。
「キーとなるのは何か?」という点さえつかめば、文字列と値との間での変換ってのはなんとかなると思う。

自分の場合「とりあえず」という状況だったのもあって、「コード+名称」をプロパティに持つクラスをコンボボックスの
アイテムとして扱うようにしていたな。大体こんな感じの。

''' <summary>コンボボックス表示設定用 クラス</summary>
Public Class ComboItem

    ''' <summary>コード部</summary><exclude />
    Protected _codeValue As String = ""
    ''' <summary>名称部</summary><exclude />
    Protected _codeName As String = ""
    ''' <summary>区切り文字</summary><exclude />
    Protected _separetedString As String = ":"

    ''' <summary>コンストラクタ</summary>
    Public Sub New()

    End Sub

    ''' <summary>コンストラクタ</summary>
    Public Sub New(ByVal formatedCodeValue As String, ByVal name As String, _
                   ByVal separetedString As String)
        _codeValue = formatedCodeValue
        _codeName = name
        _separetedString = separetedString
    End Sub

    ''' <summary>コード部 プロパティ</summary>
    Public Property Code() As String
        Get
            Return _codeValue
        End Get
        Set(ByVal value As String)
            _codeValue = value
        End Set
    End Property

    ''' <summary>名称部 プロパティ</summary>
    Public Property Name() As String
        Get
            Return _codeName
        End Get
        Set(ByVal value As String)
            _codeName = value
        End Set
    End Property

    ''' <summary>区切り文字 プロパティ</summary>
    Public Property SeparetedString() As String
        Get
            Return _separetedString
        End Get
        Set(ByVal value As String)
            _separetedString = value
        End Set
    End Property

    ''' <summary>インスタンスの文字列化 メソッド</summary>
    ''' <remarks>コンボボックスなどにおいてItemsに追加される文字列を取得する
    ''' メソッド。コード部の文字列+区切り文字+名称部の文字列を返却する</remarks>
    Public Overrides Function ToString() As String
        Return _codeValue + _separetedString + _codeName
    End Function

End Class

とりあえず~、という事であればこれで十分。業務系でいく場合、大体はコードと名称さえあればなんとかなってしまうだろうし。
普通のコンボボックスでアイテムの扱い方がわかるのがまず先決かと。

ちょっと長くなりそうなので、この後のDataGridView関連の拡張は別記事に・・・。

2007年8月18日土曜日

DataGridViewのセルを拡張する(1) グルーピングセルで練習

DataGridViewの拡張を行う練習みたいなものとしてグルーピングっぽい表示を行うようなものをやってみようかと。
ちなみに元ネタはこちら。超有名どころなので大体の人は見たことがあるでしょ。

DOBON.NET
DataGridViewの行をグループ化する: .NET Tips: C#, VB.NET, Visual Studio

これは「直前の行の値と同一の値なら表示しない」という練習にはもってこいのシンプルな機能。
なので順を追ってやってみることにする。今回の場合、調べたり考えたりしなければならないのは一箇所。

  • 「直前の行の値と同一なら表示しない」というのをどこで処理すればよいか?

DataGridView関係で勘違いしやすいのは、「セル値(Valueプロパティ)が表示されている」と思いやすいこと。
実際には「セル値の変換結果が表示されている可能性が高い」くらいだと思っていいかも。可能性が高い、と妙な言い方を
したのにも理由があって、やりようによってはセルの値に関係ないものを表示することができるからなんだよね。

もう一つポイントがあって、DataGridViewのセルを拡張するときには基本として3つのクラスがセットになっているのよ。

  • DataGridViewCellから派生したセルクラス
  • DataGridViewColumnから派生したカラムクラス
  • IDataGridViewEditingControlを継承した編集コントロールクラス

基本この3つ。今回のように表示のみ行うケースはセルとカラムの2クラスでOK。
そして大体の場合、何らかの処理を行うのは「セル」か「編集コントロール」かの2択だと思っていいかな。
名前からして編集コントロール側では「編集時の動作に関連する処理」を行い、セル側では「表示時の動作に関連する処理」を行うんだよね。
なので今回はセルクラスで「表示しない」というロジックを書くことになるねぇ。そこでDataGridViewCellについてMSDNを眺めてみると
こんな記述が。

DataGridViewCell.GetFormattedValue メソッド (System.Windows.Forms)
表示用に書式指定済みのセル値を取得します

日本語の表現としてどうよ、ってのはあるけれども意味合い的には一番近いね。実際にはセルに何らかの表示を行う際には
このメソッドで処理します。なので今回もこのメソッドで処理しましょう。

''' <summary>表示用に書式指定済みのセル値を取得します。</summary>
Protected Overrides Function GetFormattedValue( _
    ByVal value As Object, _
    ByVal rowIndex As Integer, _
    ByRef cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
    ByVal valueTypeConverter As System.ComponentModel.TypeConverter, _
    ByVal formattedValueTypeConverter As System.ComponentModel.TypeConverter, _
    ByVal context As System.Windows.Forms.DataGridViewDataErrorContexts) As Object

    If (rowIndex > 0) AndAlso (rowIndex <> Me.DataGridView.NewRowIndex) Then
        Dim previousCell As System.Windows.Forms.DataGridViewCell _
            = TryCast(Me.DataGridView.Rows(rowIndex - 1).Cells(Me.ColumnIndex), System.Windows.Forms.DataGridViewCell)
        If (previousCell IsNot Nothing) AndAlso (previousCell.Visible) AndAlso _
           (value IsNot Nothing) AndAlso (previousCell.Value IsNot Nothing) AndAlso _
           (value.Equals(previousCell.Value)) Then
            '直前の行と同一値ならばセルに文字を表示しない
            Return ""
        End If
    End If
    Return MyBase.GetFormattedValue(value, rowIndex, cellStyle, valueTypeConverter, formattedValueTypeConverter, context)

End Function

今回はシンプルな処理なので特に説明もいらなさそう・・・。やっていることとしては、「新規の行でない」「先頭行でない」場合に、
「一つ上の行の値」を比較して同一だったら空文字列を返却する。それだけなんだよねぇ。
最終的なソースはこんな感じ。

''' <summary>グループ表示を行うDataGridViewのセルのクラスです</summary>
''' <remarks>直前の行(一つ上の行)の値と比較し、同一の際は文字を表示しないセルです。</remarks>
Public Class GroupTextCell
    Inherits System.Windows.Forms.DataGridViewTextBoxCell

    ''' <summary>コンストラクタ</summary>
    ''' <remarks>GroupTextCellのコンストラクタを実行します</remarks>
    Public Sub New()

    End Sub

    ''' <summary>ReadOnlyプロパティ</summary>
    ''' <remarks></remarks><exclude />
    Public Overrides Property [ReadOnly]() As Boolean
        Get
            Return False
        End Get
        Set(ByVal value As Boolean)

        End Set
    End Property

    ''' <summary>表示用に書式指定済みのセル値を取得します</summary>
    Protected Overrides Function GetFormattedValue( _
        ByVal value As Object, _
        ByVal rowIndex As Integer, _
        ByRef cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
        ByVal valueTypeConverter As System.ComponentModel.TypeConverter, _
        ByVal formattedValueTypeConverter As System.ComponentModel.TypeConverter, _
        ByVal context As System.Windows.Forms.DataGridViewDataErrorContexts) As Object

        If (rowIndex > 0) AndAlso (rowIndex <> Me.DataGridView.NewRowIndex) Then
            Dim previousCell As System.Windows.Forms.DataGridViewCell _
                = TryCast(Me.DataGridView.Rows(rowIndex - 1).Cells(Me.ColumnIndex), System.Windows.Forms.DataGridViewCell)
            If (previousCell IsNot Nothing) AndAlso (previousCell.Visible) AndAlso _
               (value IsNot Nothing) AndAlso (previousCell.Value IsNot Nothing) AndAlso _
               (value.Equals(previousCell.Value)) Then
                '直前の行と同一値ならばセルに文字を表示しない
                Return ""
            End If
        End If
        Return MyBase.GetFormattedValue(value, rowIndex, cellStyle, valueTypeConverter, formattedValueTypeConverter, context)
    End Function

End Class

''' <summary>グループ表示を行うDataGridViewのカラムのクラスです</summary>
''' <remarks>直前の行(一つ上の行)の値と比較し、同一の際は文字を表示しないセルを利用する際に指定するカラムです。</remarks>
Public Class GroupTextColumn
    Inherits System.Windows.Forms.DataGridViewColumn

    ''' <summary>コンストラクタ</summary>
    ''' <remarks>GroupTextColumnのコンストラクタを実行します</remarks>
    Public Sub New()
        MyBase.New(New GroupTextCell)
    End Sub

    ''' <summary>CellTemplateプロパティ</summary>
    ''' <exception cref="ArgumentException">GroupTextCell以外が指定されました</exception>
    ''' <remarks>GroupTextCell以外を指定した際には
    ''' ArgumentException例外が発生します</remarks>
    Public Overrides Property CellTemplate() As System.Windows.Forms.DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(ByVal value As System.Windows.Forms.DataGridViewCell)
            If Not (TypeOf value Is GroupTextCell) Then
                Throw New ArgumentException("CellTemplateプロパティはGroupTextCellを設定する必要があります。")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property

End Class

練習には丁度いい感じだな。

共通的なEnterキー制御

Net上でよく見かけるサンプルだと、FormのKeyDownイベントとかでSelectNextControlメソッドを利用したフォーカス制御を行っているのが殆どだと思う。
でもその方式にはちょっと問題があって。

グリッド系コントロールなどキー制御を変更したくなるケースに対応するのが困難

なんだよね。グリッド系コントロール、それも業務系だと多そうなのは「Enterだと次のセルへ」にしたいケースが多いんじゃないかな?
なので現実的な方法としてはこうなると自分では思ってます。

  • グリッド系など特殊制御が必要なものはコントロール側で制御を行う
  • それ以外のコントロールはForm側で一括制御する

うん、一箇所で全てを、というのは逆に保守性が悪くなるというか場合によってはものすごくロジックが長大になることがあるね。
なので個人的な見解での対応を。

  • 特殊制御が必要なコントロールは特定のインターフェースを継承しておく
    これだけでForm側の制御ロジックがかなりスッキリ。

実際のロジックとしては、こんな感じかな。

Dim target As Control = Me.ActiveControl
If e.KeyCode = Keys.Enter Then
    If Not TypeOf target Is (インターフェース名) Then
        Me.SelectNextControl(target, Not e.Shift, True, True, True)
    End If
End If

本当のロジックはもう少し色々付け加える必要があるだろうけど、まずはこんな形。

伝票入力を目指すけどその前に

事前準備もかねてやっておく必要があることが。
それは「フォーム上で利用するカスタムコントロールの作成」。

一つ前の記事も似たような話だけど、今回はちょっと違っていて。
要するにキー部とか普通な項目とかの入力に使うコントロールをあらかじめ作っておこう、という話なんだよね。
実際問題、標準のコントロールでは結構定型的なロジックを用意する場面が多いと思う。
日付入力用とか数値入力とかそういった類もあるし、よく話題にあがる「Enterキーで次のコントロールへ移動」とか。

そのあたりを前もってやっておく必要がある、というよりやっておくと楽。

理由は簡単でそこで作成したカスタムコントロールはそのままDataGridViewの拡張に利用できるから。
何もしないで伝票入力に対応しようとするととてつもなく苦労するので是非。

ちょっと次は脱線するけど「Enterキーの処理を一括で記述することについて」です。

方向性の決定

無償な環境でやり続けるにはある程度の「気合と根性」がどうしても必要になるね。
一番ネックになるのは「機能を実現するためのコントロールの作成」をやる場面が多いってところ。ExpressEditionでは純粋にコントロールなサードパーティ製品は利用可能だけれども、VIsualStudioを拡張するようなタイプの製品は利用できない制限があるので、どうしてもどこかで気合が必要になるんだよねぇ。

そのために必要なのはまずはコントロールの拡張。

特に業務系のシステムではかなりのケースで必要になる伝票入力をどうするか、というのを考えておかないといけないね。大雑把に言うと次のような方向性かな?

  1. 明細部の入力を別途領域に用意して単一行単位で入力させる。
    明細自体はDataGridViewで横一行に表示する。
  2. DataGridViewで入力も行わせる。横スクロールで表示しきれない部分をカバー。
  3. 明細入力用の専用コントロールを独自に作る
  4. 明細入力用にDataGridViewのセル等を拡張する

恐らくはこのあたりの選択肢から、時間と技術力との兼ね合いで選択していく人が多いんじゃなかろうか。風の噂で聞いたことがあるけれど、某社(?)での.Net案件でもやっぱり1か2を選択するケースが多いみたいで。

まぁでも、先にこのあたりを決めておかないでシステム作ったりすると、大体は後で泣きを見るから早いうちに苦しむ必要がありますわ。

自分の中ではある程度の努力ができるなら(4)をお勧めします。一番難易度が高いのは(3)だね。どう見積もっても時間もかかるし、かなりの技術力が必要だと思うから・・・。

 

そんな感じなので、次からはDataGridViewで伝票入力ができるところを目標にしていきます。

費用をかけずにExpressEditionで業務システムを作成?

結論から言うと「十分に可能」。その場合次のような構成になるかな。

  • 開発環境(IDE)はVisualStudioExpressEdition
  • データベースはSqlServerExpressEdition
  • 帳票ツールはReportViewerパッケージ
  • 伝票入力は気合と根性でDataGridViewとお付き合い
  • データバインディングは利用できるけどExpressEdition環境だと面倒が多い

さすがにクリティカルな性能を要求されると厳しい(でも不可能じゃないね)けれども、恐らくはかなりの領域で作成できると思われるね。

 

もう少し詳しくいくとこんな感じで。

 

VIsualStudioのExpressEdition。基本的にはその中から「2つ」が必要になる。メインの開発用(C#とかVBとか)と帳票開発用にVIsualWebDeveloperが必要。ExpressEditionだと、C#やVB側で帳票設計ができないので、対応しているWebDeveloperがどうしても必要になるんだよね。
ダウンロードはここ(http://www.microsoft.com/japan/msdn/vstudio/express/)から。SqlServerのExpressEditionもここからダウンロードできます。

帳票としてReportViewerを利用するには二つ必要。
一つは「再配布パッケージ」。もう一つは「VisualWebDeveloper用レポートアドイン」です。

再配布パッケージは2007年8月の時点でここ(http://www.microsoft.com/downloads/details.aspx?FamilyID=e7d661ba-dc95-4eb3-8916-3e31340ddc2c&DisplayLang=ja)
レポートアドインは同じく2007年8月の時点でここ(http://www.microsoft.com/downloads/details.aspx?familyid=DF0BA5AA-B4BD-4705-AA0A-B477BA72A9CB&displaylang=ja)

どちらもアップデートされたりするケースが多いので、リンクが死んでいたらGoogle先生に聞いてみてください。

DomainControlerの役割削除

複数台のDCを用意して構築してあるActiveDirectoryで一台のサーバーのパーティション変更を行うことになった。その際に、DCが利用しているフォルダがあったので先にDCの機能を削除することに。削除自体はリモートからでもすんなり行えるんだけど、削除後に再起動しないで先にバックアップを行ったんだよね。その最中に一度リモートが切れてしまったんだけど・・・これが非常にマズイ。

DCの機能削除後に再起動しないでリモート接続を切ると、
その後はリモート接続が不可能になってしまう。

なぜかというと、現在ログイン中の状態は「ActiveDirectoryにDCとして参加している」状態なのに、OSの設定では「ActiveDirectoryにコンピュータとして参加している」に切り替わっているので矛盾が出ているんだよね。
一度こうなるとリモートでは無理。VNCでも既にワークステーションロック中なので無理。

てことでお客さんにお願いして直接再起動してもらうことに・・・それでもダメだったら月曜日は行ってこないといけないなぁ・・・。

2007年8月17日金曜日

ophcrack

最近gigazineで記事が公表されてしまったクラッキングツール。こいつでクラックできるパスワードは次の条件を満たすものみたい。

14文字以下のパスワードが設定されているワークグループで動作しているパスワードのハッシュを記憶させている

Windowsのデフォルト動作だと、14文字以下のパスワードは必ずハッシュの値も保存されているので、基本的には14文字以下のパスワードを利用しているアカウントは、コイツで確実に解析できるね。「パスワードのハッシュ」を知っている人じゃないとポリシーを設定してハッシュを保存しないようにする、なんてできないからなぁ・・・。

久々にこの手のツールで豪快に扱われたモノが現れたような気がする。

.Net FrameWork 3.0

色々考えているけども、やっぱり今回のシステムでは3.0を利用する方向で行こうかな、なんて思う。
といっても利用するのは目に見える部分のWPFではなく・・・。

WF(WindowsWorkflow)とSystm.IO.Packaging

Packaging関連ってのはOffice2007形式を扱うサンプルライブラリで利用していたからw
ポイントはやっぱりWFかな。今回のシステムでは、どうしてもビジネスロジックが複雑になりがちなので、可能な限り煩雑さをなくすことを考えていかないといけないと思ってる。そのための仕組みの一つがデータクラスなどのアーキテクト部分での対応なんだけど、これだけじゃあやっぱり不十分。いくらロジックをUI部から独立させたところで、やっぱり見ることのできる人ってのは限られるんだよね。
じゃあどうするか、となると今現在では一番やりやすそうなのがWindowsWorkflowってことになるかな、って思う。フローを目視できるってのはやっぱりとてつもないメリットかなぁ、と。

後はできるだけ早くに、実際にWorkflowが動作するあたりのアーキテクトを固めてしまって、できるだけ単純なモデルで利用できるようにしないとね・・・。

2007年8月10日金曜日

Object.Equalsメソッド

未だにはっきりとした動作がつかめていないメソッドの一つ。

多分クラスによって動作が異なるんだとは思うんだけど、「同じ値かどうか」を求めるメソッドではないのは確かだと思われ。ヘルプによるとこんな記述が。

組み込みの値型の場合、等値演算子 (==) ではオペランドの値が等しい場合に true が返され、それ以外の場合は false が返されます。string 以外の参照型の場合、== では 2 つのオペランドが同じオブジェクトを参照する場合に true が返されます。string 型の場合は、== は文字列の値を比較します。

一般的なクラスの場合だと、「同じオブジェクト(インスタンス)かどうか」という動作であって、Stringや値型は「同じ値かどうか」という動作になるってことだそうだ。

これを見ていて思ったのは「データクラスでEqualsメソッドをオーバーライドなりして実装するとして、内部的にはやっぱり「各フィールドの値が同一か」という聞き方になっていると楽かなぁ、なんて思った。
・・・使う場面がどれほどあるかはわからないけど。

2007年8月9日木曜日

C#とVB

実装の参考にと色々なところのソースを見ていると思う。

C#の方がかなり便利

VisualStudioでの機能からしてC#の方が便利なんだよなぁ。標準でリファクタリングの機能が提供されているし。言語レベルでもやっぱり便利。プロパティのセッターとゲッターでアクセス修飾子を別々に定義できるんだよね。それとソースの見た目に無駄が少ない。個人的な感覚かも知れないけど、あまり間違っていないと思う。

次のVisualStudioではC#9とVB9とそれぞれバージョンアップするけど、これから言語を学ぶってんなら間違いなくC#だなぁ。VBはどうしても記述の必要上、余分なコードが増えてしまうんだよね。DirectCastなんていい例だな。ライブラリ屋さんとしては、やたらめったら使う羽目になるんだけど本当にうざったい。

多分キャストについては、普通にプログラム組んでいるだけだと気づかないだろうな。インターフェースやら抽象クラスやら利用した汎用的なロジックを実装していないと、あまり利用する必要がないから。
それでもVB6に比べると比較にならないくらい楽なんだけどね。

2007年8月8日水曜日

数値のソート

実際にソートして例外見るまで気づかなかった。
今のところ拡張したDataGridViewの数値用セルではValueTypeをNullable(Of Decimal)に固定してはいるんだけど、実際にValueプロパティに設定される値はDecimalであることは保障されないのよね。これがデータバウンドとかValueプロパティでTypeConverter使うぞとか、そういった方式を利用しているなら「ありえねー」ってことで一蹴できたんだけど、今回の様に全てをアンバウンドで行っている以上は、必ず発生するんだよな、これが。

簡単に書くと次のようなケース。
DataGridView1.Rows(0).Cells(0).Value = 10000
DataGridView1.Rows(0).Cells(0).Value = 100000
こう書いた場合、最初の行はIntegerで入ってくるんだな。次の行はIntegerの限度を超えているのでLongとかDecimalに変換されて入ってくる。さて、こういった場合にどんな問題が起きるか?というと。
Dim originalValueInstance As Object = originalCell.Value
Dim comparedValueInstance As Object = comparedCell.Value
Dim execCellValue As IComparable = TryCast(originalValueInstance, IComparable)
Dim result As Integer = execCellValue.CompareTo(comparedValueInstance)
こんな風に比較ロジックを書いた場合に例外が出てしまうんだな。というのも、originalValueInstance とcomparedValueInstance の型が異なっているから。originalValueInstance がIntegerで、comparedValueInstance がDecimalなんてことになった場合、CompareToメソッド呼び出しのところで「二つの型が一致しねぇ!」と怒られてしまうわけだ。
さてどうすっか、と言われると「どちらかの型に変換して比較する」となるんだよね。幸いにもConvertクラスなんてものが用意されているのでこれを利用すれば型の統一は可能。ただ問題点が一つ。
AとB、大きい値が入る型はどっちだ?
型からインスタンス生成して数値系の型でほとんど実装されているMaxValueプロパティの値を比較するってのもあるけど、そもそもAもBも数値なのか?という点ではどうしても力技になってしまうんだよね。それなら最初から力技でやってしまっても、そうそう変わらないんじゃないかなぁ・・・と思ったり。
符号のありなしで困るんだけどね、どっちにしても。

2007年8月6日月曜日

Valueプロパティが深い

日付入力コントロールが何とか形になってきたので、日付系セルをやっているわけだけど、こいつが深い。いや、正しくは「セルのValueプロパティに関わる動作が深い」なんだろうなぁ。

元々DataGridViewでは編集時にセルのValueプロパティを変更しても、編集コントロール側には反映する機能はなかったんだよね。それに対応するためにCellValueChangedイベントで、編集中なら編集コントロールのTextを書き換える、ってなことをやっているんだけど。
どう書き換えてあげればいいかなんてDataGridViewやセルからは判らない
だよなぁ。編集じゃない部分はCellのGetFormattedValueメソッドあたりを利用すればいいんだけど、編集コントロールに至ってはどうしようもない。Valueに入っているオブジェクトをどう編集させるかってのは、完全に編集コントロール(IDataGridViewEditingControl)側の世界なんだよね。
自前で用意しているセルだけだってんなら、それようのプロパティやらイベントやら用意すれば済むんだけど、デフォルトのセルも使うからそういう手段は使えないし・・・。

Reflectorでフレームワーク内部を除いていてもValueから実際に表示させているところが雰囲気つかめないんだよぅ。難しいわ。

2007年8月3日金曜日

日付への変換(2)

NothingをDateTimeな変数に代入すると例外が発生する。
でもNothingをDateTimeにキャストするとDateTime.MinValueになって通ってしまう。

えー!?

日付への変換

日付数値用のTypeConverterを実装していてわかったこと。
空文字列(String.Empty)を日付に変換するとDateTime.MinValueになる。

えー?!

2007年8月2日木曜日

ContextMenuStrip

DataGridViewの拡張で、行列それぞれのヘッダ用のメニューを用意していたんだよね。で、普通にプロパティで設定するのはセルにだけ適用されるように、と。

・・・したつもりだったw

DataGridViewのContextMenuStripプロパティをオーバーライドして、MyBaseのプロパティ側にはデザイナで設定したインスタンスがわたらないようにしたんだけど、ところが実行してみるとデザイナで設定されたメニューが普通に表示されてしまうのよ。プロパティのオーバーライドって問答無用で継承元も呼び出されるんだっけか?

対応としてはOnColumnAddedとOnRowsAddedをオーバーライドして、そのタイミングで行列用メニューを設定することに。そうすることで目的の動作は達成できたからいいんだけどね。

どうにも気になる。

2007年8月1日水曜日

数値と文字

日付変換あたりを考えているうちにちょっと思ったこと。

そのオブジェクトが数値なのか文字なのか」を判断するスマートな方法がない

どうも力技で全部聞いてあげないといけないっぽい。数値型は共通の何かを派生させて作成されています、なんてことになっていれば楽だったんだけど、何しろ型だけに相手は構造体なんだよね。んでコイツラは継承云々関係無しに定義されているから、力技でしか判断は不可能なんじゃないかなぁ・・・。

面倒だねぇ。

2007年7月30日月曜日

グリッドのソートとフィルタ(2)

グリッドのソート機能をガシガシと設計実装中。
あらためてやってみると、実は結構頭を悩ませないといけないことが多いのがよーくわかった。

ライブラリの中で行えるソートってのはかなり色々考えておく必要があるので、普通にAPでやるように簡単にはソートを行えないんだよね。理由も簡単で、「比較対象となる値は本当に比較できるのか?」という部分。Stringだー、Integerだーという場合に限定して考えれば、それはたいした問題じゃない。だけどDataGridViewというコントロールである以上は、「セルの値はObject」というケースが多々あるわけで。つきつめると「Objectクラス同士では比較する方法はない」んだよね。そりゃそうなんだけど。比較する方法を判っているのは、その派生クラス側でしかないんだから。

てことでロジックとしてはこんな感じになりそう。

Nothingかどうかをチェックセルの値がIComparableを継承しているならCompareToメソッドを呼び出して、その結果が0(同一)でなければ結果として返却IComparableを継承していない際は、同一の値とみなして次の比較にうつる

大雑把に言えばこんな感じだろうね。実際に比較を行うのはそれぞれのクラスで行う必要があるから。

それで今の問題は「サブエディタ等の複数項目を保持しているクラスでの比較」。この場合は比較対象となる条件(というか項目)を渡してあげる必要があるんだけど、上のIComparableインターフェースと一緒に考えていくとどうもしっくりこない。その理由は「IComparableが単一の値と単一の値の比較」に用いるインターフェースだからなんだよなぁ・・・。

2007年7月28日土曜日

DataGridViewでソート

単純にソートする機能は元々持っていたから安心していたんだよね。

でもここ最近某MultiRowコントロールに近づけるために拡張したことによって、結構考えなければいけないハメに。MultiRowコントロールやそのほかのグリッド系コントロールは「セルをマージしてレイアウトを作成する」タイプなんだけど、今回自分が用意したのは「セルに好きなコントロールを表示する」方法だったので、セルに格納される値ってのが多種多様・・・つーかなんでもありw

今ではDataGridViewのセルの中にDataGridViewを出して、さらにその中に・・・なんてことも平気でできるようにしてしまったので、ことさらこの問題が顕著に。

気持ちとしてはExcelなどで行っている指定の方法がベターかな、なんて思っているのでそれに近い方法を用意しようとは思ったんだけど。項目名を指定させるところでちょっと悩む。
ヘッダのTextを名称として利用する、と統一とれれば楽なんだけどなぁ。今のところヘッダの拡張はやっていないからそこまで辿り着けていないんだよね。

しかしここまでDataGridViewを拡張して色々やっているけど、時代はもう3.0に突入しようとしているんだよね。2.0な世界をどこまでひっぱっていけることやら。

2007年7月27日金曜日

グリッドのソートとフィルタ

そろそろ、というかついにというか。残りに残っていたこの二つの話題を触る時がきてしまった・・・。

MultiLayoutのベータリリースによって、デザイン上の問題はほぼ対応できるメドがついたからなんだけど。

ソートもフィルタもどっちも地味に設計が難しい。元々のグリッド系コントロールの考え方としては、「ある項目でソート」「ある項目の値でフィルタ」というように、セルの値というのが非常に絡んでくる。ところが、だ。
MultiLayoutでは一つのセルに複数の値が設定されているんだよなぁ・・・。
そうなると、ソートを行う際に「そのセルのどの値」というところまで指定できないと、ユーザーが思うようなソートは掛けれないことになるんだよね。これはフィルタも同じで。

とりあえずはMultiLayoutの事を忘れてしまってソート処理から設計・実装していこうとは思う。AP側でソートの方法が固定できるなら、Decoratorパターンにしてしまってソート用のクラスを各自に実装してもらうのがいいんだろうけど。

2007年7月24日火曜日

ちょっとまてSharePointService

@ITの記事でSharePointService用にサイトのテンプレートが多数配布されていたことにいまさら気づいたorz

試しにインストールしてはみたけどその数と内容がすげぇ。

欠勤届および休暇スケジュール管理複数プロジェクト予算・進捗管理バグ データベースコール センター変更依頼管理法令遵守プロセス サポート サイト連絡先管理校閲用ドキュメント ライブラリイベント企画経費精算・承認ヘルプ デスク在庫管理IT チーム ワークスペース求人・面接管理サポート技術情報貸出図書物的資産管理プロジェクト管理ワークスペース部屋・備品の予約潜在顧客管理

・・・多すぎ。これを見て思ったけど、本当にこれからは「自前で色々作る」ではなくて「色々な製品を組み合わせて利用する」ってとこなんだなぁ、ってところ。大手がヤル気になったら中小企業じゃまともに対抗できんって。

2007年7月21日土曜日

ドラゴンナイト4

リメイクされてたのねぇ。色々と思い出深いタイトルだからちょっと調べてみたけど・・・なんというか今のエルフっぽい作品だなぁ。

戦闘シーンだけのリメイクだからねぇ。

やっていた当時は戦闘パートの難しさと、2週目がしっかりとした理由あっての事だったってのが好きだったねぇ。爆弾娘を一番鍛えたりw

んー、昔のまま出してくれた方が良かった気がするw

2007年7月20日金曜日

コントロールのキャプチャ

色々試した結果、どうやらCreateGraphicsメソッドの動作が根本の原因っぽい。ユーザーコントロールのインスタンス生成直後にCreateGraphicsメソッドを呼び出すと、変なGraphicsクラスのインスタンスを返してくれる。
というか、ユーザーコントロール自身がよくわからない動作なんだよなぁ。生成直後で表示されていないにもかかわらず、Visible=Trueとなっていたり。そのくせCreateGraphicsを行うと実際には表示されていないからデスクトップのキャプチャ結果が返ってくる。

仕方ないので呼び出し側で、想定される表示状態を指定してそれによってCreateGraphicsするかDrawToBitmapするかを分岐させるようにした。んー、なんか実装していて「これでいいの?」と悩むなぁ。

2007年7月16日月曜日

DataGridViewの編集

んー、いまいち流れがわからない箇所がある。

編集が終わってセルへ値を更新するタイミングがあるんだけど、サブエディタをしっかり作るに当たってそこがよくわからなくなってきた。編集コントロール側ではセルに対してどうのこうのはやらない、というのであればセルかDataGridView側から編集コントロールにアクセスしにくるはずなんだけどなぁ・・・。

前のフォームを利用したサブエディタの場合は、逆に編集コントロールからセルに値を更新していたんだよな。んー、よくわからない。