以前までで色々書いてきたMultiLayoutと呼んでいるやり方を使うとこんなこともできたり。
こんな状態の表示が・・・
こんな風に切り替えられます。しかも特にロジックは必要なく。
方法としてはwebブラウザで利用するCSSを切り替える、みたいなやり方なので楽にできるんだよねぇ。
DataGridView や DataRepeater にこだわりつつ
時代は Workflow Foundation などと言い続けていたら
これからは LogicFlow だろ! と思い始めてきた
色々浅く広くやっていく古い技術者の Blog
以前までで色々書いてきたMultiLayoutと呼んでいるやり方を使うとこんなこともできたり。
こんな状態の表示が・・・
こんな風に切り替えられます。しかも特にロジックは必要なく。
方法としてはwebブラウザで利用するCSSを切り替える、みたいなやり方なので楽にできるんだよねぇ。
前回ダイアログを表示するときにはちょっとロジックがいる、というのを書いた。
簡単に言うとこういったことをやる必要があるってとこで。
言葉で書くと2行なんだけどねw
実際にはこんな感じで表示座標を算出しないといけないのよ
Dim editCell As SubEditorDialogCell = TryCast(Me._parentDataGridView.CurrentCell, SubEditorDialogCell)
If (editCell Is Nothing) OrElse (editCell.EditorInstanceType Is Nothing) Then Return
subEditorInstance = TryCast(System.Activator.CreateInstance(editCell.EditorInstanceType), SubEditorDialog)
If subEditorInstance Is Nothing Then Return
'位置の割り出し
Dim cellRectangle As System.Drawing.Rectangle = editCell.DataGridView.GetCellDisplayRectangle(editCell.ColumnIndex, editCell.RowIndex, True)
Dim cellPoint As System.Drawing.Point = Me._parentDataGridView.PointToScreen(New System.Drawing.Point(cellRectangle.Left, cellRectangle.Top))
'表示位置調整
Dim editorSize As System.Drawing.Size = subEditorInstance.Size
Dim owner As System.Windows.Forms.ContainerControl = DirectCast(Me._parentDataGridView.TopLevelControl, System.Windows.Forms.ContainerControl)
Dim ownerSize As System.Drawing.Size = owner.ClientRectangle.Size
If (owner.Left + ownerSize.Width) < (cellPoint.X + editorSize.Width) Then cellPoint.X = owner.Left + ownerSize.Width - editorSize.Width
If (owner.Top + ownerSize.Height) < (cellPoint.Y + editorSize.Height) Then cellPoint.Y = owner.Top + ownerSize.Height - editorSize.Height
Dim afterCellPoint As System.Drawing.Point = Me._parentDataGridView.TopLevelControl.PointToClient(cellPoint)
ポイントとなるのは「親コンテナのクライアント領域」。これをコンテナのSizeとかからやると、コンテナにスクロールバーがでたり一部未表示になったりするか注意が必要。
まあこんな感じで表示位置を求めてあげれば、あとはLocationプロパティとかにつっこむことによってちゃんと表示されるって寸法です。
マウスの座標から、その下にあるコントロールを取得するメソッドなんだけど・・・。
これ、対象となるコントロールのVisibleがTrueじゃないと動かないっぽい。
今回の作業中に直接フォーカス移動させるときにぶつかったんだけど、とりあえず自前で同じような動きするメソッドを用意してみた。
PublicFunction GetChildControl(ByVal pt As System.Drawing.Point) As System.Windows.Forms.Control
For Each child As System.Windows.Forms.Control In Me.Controls
Dim childRectAs System.Drawing.Rectangle = New System.Drawing.Rectangle(child.Left, child.Top, child.Width, child.Height)
If childRect.Contains(pt.X, pt.Y)Then Return child
Next
Return Nothing
EndFunction
マウスの座標って、フォームとかのイベントで取得しているのはクライアント座標なので処理的にはたいしたことなかったかな。
まぁこれがスクリーン座標だったとしても、親Formで一度変換かましてあげればいいだけなんだけどね。
とりあえずの動きとしてはこれでいいので、後は除外されるコントロールを指定できるようにしておかないと・・・。
伝票レイアウトセル(2) 編集コントロール その5で触っている、編集開始時の処理。
このときはDataGridViewのEditingPanelに対して直接コントロールを追加することによって、UserControlを継承した編集コントロールを利用するようにしていたんだけど。
ダイアログで行う場合は、当然この方法だとマズイ場面が。
・・・それはダイアログのサイズ。
ダイアログで、となるとケースによっては元のセルよりも大きいものを利用する場面というのが当然でてくるわけで。
もっといってしまうと、親元のDataGridViewよりも大きいケースというのがありえるわけで。USのMSDNフォーラムにあるサンプルは「セルをダイアログの大きさに拡張する」という手法を使っているけど、それだとちとまずい。
(本当につきつめると親Formよりも・・・というのがあるんだよねぇ)
まぁそういうところがあるのでちょっと工夫する必要があるんだけど、今回は「親Formよりは小さいけどDataGridViewよりは大きいかも知れない」というケースまで対応する場合にどうするか、でいこうと思う。
んで、実は答えはすこぶる簡単。
Me.EditingControlDataGridView.TopLevelControl.Controls.Add(subEditorInstance)これだけ。前はEditingPanelにAddしていたのを、「親DataGridViewの所属するTopLevelControl」に変更してあげればほとんどいける。実際は「表示する位置」の問題があるので、もう少しロジックはいるけどね。それは次回以降で。
ちなみにこの場合、AddするコントロールはTopLevel=FalseにしておかないとAddできないので注意。
今までに色々やってきた方法は、DataGridViewの中に伝票を埋め込んでしまおうという見た目にわかりいい方法だったんだけど、単純に入力だけできればいい場合には別の方法もあるよねぇ。
それはダイアログ形式でエディタを表示するという方法。
セルの編集状態が開始した際に同じ場所にダイアログを表示して入力させるというやりかたなんだけど、この方法を利用した場合の問題点は何個かあるんだよね。
ちょっと今回から何回か続けてこのやり方の対応を書いていこうと思う。
今までのMultiLayoutとよばれるやり方に比べると、わかりやすいから需要があるしね。それにMultiLayoutをちょっと変更してあげれば対応できてしまうんで、実はそれほど難しい話題じゃないしw
せいぜい気をつけないといけないポイントが何箇所かあるくらいかな?
なんというかMSDNフォーラムの方で引き合いに出していただけて嬉しいやら恥ずかしいやら。
ただ自分ができているのは「セルの拡張」だけなので、「ヘッダの拡張」というのはちゃんとできてないんだよねぇ。
というのも純粋にヘッダーを表示するまでならセルの拡張と話は同じなので、どうにでもできるんだけど。
デザインのさせ方
こればっかりがまったくアイデアも何もない。セルと同様にUserControlを継承したクラスで・・・とすれば確かに表示まではどうにかなります。
でもそれだとデザインがえらいやりにくいんですよね。
ヘッダをデザインするときは、普通ディティール部のデザインとあわせてやることが多いだろうし。
そうじゃないと几帳面なところではつっかえされてしまいまするよ。
ExpressEdition以外であれば、デザイナ自体を作成して・・・という方向にいけるんだろうけど、何しろ間違った方向へと突き進むこのサイトは、原則ExpressEditionが対象w
ExpressEditionでできないことはやらない、という"( ゚,_ゝ゚)バカジャネーノ"、という意気込みなんですね。
そうなると考え付く対応ってのもかなり限られてしまい。
ぐらいなんだよなぁ・・・。手間の量からいくと「1」がまだマシ。使いやすさという点で「2」がベター。
どちらにしろ手間はかかりますね。このあたりまでくると、どうしても市販コンポーネントを採用する方へと流れてしまいます。
これもフルテキストに関連するし、今回使う予定なのでメモ。
ファイルイメージをそのままDBに落とすためには、項目の型がVARBINARY(MAX)かIMAGEになっている必要がある。VARBINARYやIMAGE列には単純にINSERT(UPDATE)は行えず、OPENROWSET関数を利用しなければならない。OPENROWSET関数は「ローカルファイルをインポート」するような動き(実際にはローカルファイルをOleDbとして扱う、だけど)のでOPENROWSET関数を含んだSQL文を実行する際にはデータベースサーバ上に対象となるファイルが存在していなければならない。インポートの際にはOPENROWSET関数をBULKオプション、SINGLE_BLOBオプション付で実行する。
ちゅうことで、データベースサーバ上の一時領域をどこかで保持しないといけないね。んでデータクラスでSQLを実行する直前までにファイルをアップロードしておく、と。
んー、単純にデータクラス内部にアップロードのロジックを用意するってのもやだねぇ。やっぱり、アップロードの部分は別クラスに切り出しておかないと、後々どうにも具合が悪そう。
不思議な現象が・・・。
XP上でメイリオを利用して紙に印刷をかけてみると、一部アルファベットが化けて出力されてきた。(今回はU→r、I→f、D→aとなっていた。要はUID→rfaとなってきたということで)
「?」と思ってPDFに出力してみると・・・問題ない。
プレビュー画面で見ても問題ない。
なんだこれ?
SqlServer2005の新機能で非常に便利な機能なOUTPUT句による更新結果の取得と返却。
現在進行中で困っているんだけども、何となくこういった理由でVisualStudioはOUTPUT句を使った更新後データの取得を行っていないんじゃないかな、ってのが。
それはトリガの存在。
トリガが存在するテーブルに対してOUTPUT句を利用する場合、必ずINTOも付与して結果を収納する変数を指定する必要があるんだよね。
(例外として、トランザクションでなければ大丈夫、というのはある・・・らしいけどManagementStudioでダイレクトに実行してもダメだったからMSDNの記載が違っているような気もする。読み違いの可能性は非常に高いけどw)
それで厄介なのはINTOで結果を変数に収納するということは、一つのINSERTなりUPDATEを実行するために、こんな感じのSQLにならないといけなくなるってことで。
BEGIN
DECLARE @Result TABLE (@id INT NOT NULL, @fieldvalue NVARCHAR(10) NULL);
INSERT INTO TESTTABLE (FIELDVALUE) OUTPUT INSERTED.* INTO @Result VALUES ('テストな値');
END
テストも何もさせていないコードなので間違っているかも知れないけど、おおむねこんな感じ。
INTOを指定しないOUTPUT句だと「全てのテーブルには使えない」からVisualStudioでは生成しないんだろうなぁ・・・。なので全てのテーブルで利用できるSCOPE_IDENTITY関数による取得と返却をやっているんだと思う。
つか、本当にどうしようか困っています・・・。
今の仕事だとトリガはほぼ全てに必須なんでねぇ。何か解決策はないものかなぁ。