2008年1月29日火曜日

今まで紹介した伝票入力用セルな手法を使うと・・・

以前までで色々書いてきたMultiLayoutと呼んでいるやり方を使うとこんなこともできたり。

Multi4_1

こんな状態の表示が・・・

Multi4_2

こんな風に切り替えられます。しかも特にロジックは必要なく。
方法としてはwebブラウザで利用するCSSを切り替える、みたいなやり方なので楽にできるんだよねぇ。

MCA Platform M11-201

今日、勤務先に合格通知が届きました。

これでMCAは2つもってることに・・・。しかもどちらもベータ試験で受けていたりするw
あとデータベースとアプリケーションをどうにかすれば、MCA Masterだけど・・・。

うむむ。どうすんべか。

ダイアログっぽくDataGridViewの編集を行う際の表示位置

前回ダイアログを表示するときにはちょっとロジックがいる、というのを書いた。

簡単に言うとこういったことをやる必要があるってとこで。

  • 編集するセルにあわせて表示
  • ただし表示するダイアログが親コンテナをはみ出す際にはずらして表示

言葉で書くと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プロパティとかにつっこむことによってちゃんと表示されるって寸法です。

Control.GetChildAtPointメソッド

マウスの座標から、その下にあるコントロールを取得するメソッドなんだけど・・・。
これ、対象となるコントロールの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で一度変換かましてあげればいいだけなんだけどね。
とりあえずの動きとしてはこれでいいので、後は除外されるコントロールを指定できるようにしておかないと・・・。

2008年1月28日月曜日

ダイアログっぽくDataGridViewセルの編集を行うために

伝票レイアウトセル(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で行うためのもうひとつの方法

今までに色々やってきた方法は、DataGridViewの中に伝票を埋め込んでしまおうという見た目にわかりいい方法だったんだけど、単純に入力だけできればいい場合には別の方法もあるよねぇ。

それはダイアログ形式でエディタを表示するという方法。

セルの編集状態が開始した際に同じ場所にダイアログを表示して入力させるというやりかたなんだけど、この方法を利用した場合の問題点は何個かあるんだよね。

  1. セルの大きさよりも大きいダイアログを利用する場合
  2. 単純に行うとモーダルダイアログとなる

ちょっと今回から何回か続けてこのやり方の対応を書いていこうと思う。
今までのMultiLayoutとよばれるやり方に比べると、わかりやすいから需要があるしね。それにMultiLayoutをちょっと変更してあげれば対応できてしまうんで、実はそれほど難しい話題じゃないしw

せいぜい気をつけないといけないポイントが何箇所かあるくらいかな?

2008年1月25日金曜日

DataGridViewのヘッダー・・・

なんというかMSDNフォーラムの方で引き合いに出していただけて嬉しいやら恥ずかしいやら。
ただ自分ができているのは「セルの拡張」だけなので、「ヘッダの拡張」というのはちゃんとできてないんだよねぇ。

というのも純粋にヘッダーを表示するまでならセルの拡張と話は同じなので、どうにでもできるんだけど。

デザインのさせ方

こればっかりがまったくアイデアも何もない。セルと同様にUserControlを継承したクラスで・・・とすれば確かに表示まではどうにかなります。

でもそれだとデザインがえらいやりにくいんですよね。
ヘッダをデザインするときは、普通ディティール部のデザインとあわせてやることが多いだろうし。
そうじゃないと几帳面なところではつっかえされてしまいまするよ。

ExpressEdition以外であれば、デザイナ自体を作成して・・・という方向にいけるんだろうけど、何しろ間違った方向へと突き進むこのサイトは、原則ExpressEditionが対象w
ExpressEditionでできないことはやらない、という"( ゚,_ゝ゚)バカジャネーノ"、という意気込みなんですね。

そうなると考え付く対応ってのもかなり限られてしまい。

  1. プロパティとして直接数値入力させてデザインを調整させる
  2. 完全に外部APとしてデザイナを用意する

ぐらいなんだよなぁ・・・。手間の量からいくと「1」がまだマシ。使いやすさという点で「2」がベター。
どちらにしろ手間はかかりますね。このあたりまでくると、どうしても市販コンポーネントを採用する方へと流れてしまいます。

2008年1月21日月曜日

ファイルをVarBinary(MAX)列にINSERT、UPDATEする

これもフルテキストに関連するし、今回使う予定なのでメモ。

ファイルイメージをそのまま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に出力してみると・・・問題ない。
プレビュー画面で見ても問題ない。
なんだこれ?

2008年1月18日金曜日

OUTPUT句を利用するのが難しい・・・

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関数による取得と返却をやっているんだと思う。



つか、本当にどうしようか困っています・・・。

今の仕事だとトリガはほぼ全てに必須なんでねぇ。何か解決策はないものかなぁ。

UI Testふたたび

昔はVisualTest。ちょっと前だとNUnitForms。
そんなツールを使ってやっていたUIの自動テストに新しいものが。
といってもWPF用なんだけどな。

テストの実行: Microsoft UI オートメーション ライブラリ -- MSDN Magazine, February 2008

対象や方法は違ってもやってることは皆同じ。
まぁこういった類は、何かしらのアシスト系ツールが出てはじめて利用者が増大するフシがあるからねぇ。まだしばらくは様子見でいいかも。

2008年1月17日木曜日

IT Pro道場 アプリケーションプラットフォーム編

行って来ましたIT Pro道場。

今回はWebアプリケーション系とはいえ、アプリケーションプラットフォームという食指を非常にそそる内容だったので、自分の仕事が大変な状況の中(w)行って来ました。

全体的にあまり細部まで下って話そうというスタンスではなかった・・・ハズだったけど、時々熱が入ったのか中々込み入った話やら、(・∀・)ニヤニヤしてしまうセリフもこぼれてきたりと、色々楽しめたなぁ。
特に松崎さんの方がいろいろと言ってくれているのは面白かったw

ところで、最後の抽選でチョロQ当たったんだけど、「チョロQでごめんなさい」と言われるとちょっとヘコみますw

O/Rマッピングツール

【VB.NET開発コンポーネント・O/Rマッピングツール】ObjectService

リレーショナルデータベースの設計をキッチリやった場合には、多かれ少なかれクラスとの間で存在理由とか色々な点で違いが発生する(インピーダンスミスマッチ)ので、設計上でマッピングを行って関連付けをする工程が必要になるんだよね。

これはそもそもの設計手法としての問題(データ指向なRDBとオブジェクト指向との差異)なので解決するには、データベースをooDB(cacheとか)へ変更しオブジェクトをそのままデータベースに永続化できるようにしてしまうか、「何かを行って相互間で橋渡しをする」しかないんだよね。

でもって自分はooDBはあまりわかっていない(w)ので、マッピングを行う方向でしか設計できないわけなんだけど、その際の負荷を減らしてくれそうな製品がこれ。
Javaとかだと既にHibernateとかiBatisとか色々とフレームワークが整っているんだけど、.Netではまだまだ少ないのが実態。NHibernateとかS2Da0.NetとかiBatis.NetとかJavaで利用されているフレームワークの移植版が殆どかな。

そんな中で見つけたのがコイツだったんだけど、こいつはVisualStudioの環境にマージしてくれるので他のツールに比べると使いやすさはかなりいいね。後は実際の実行速度とかがどんなもんかとか、今回みたいな複雑なテーブル設計した際にどうなるかとかだけど、さすがに今はそこまで試せない・・・。

ただ今後はこういったツールは絶対必要だと思っているんだよね。
そのための布石というかなんというか、社内用ライブラリのDataComponentクラスはそれ自体を永続化する機能というのは持たせているし、定義を外部から取得するインターフェースも用意している。このあたりは、もう少しS2DaoとかSpringとか研究して、同等機能は持たせたいんだよねぇ・・・。
ただ「自分以外に使うか?」と言われると・・・w

2008年1月11日金曜日

SQLCLRで外部プログラムの起動

何点か悩んだところが多かったのでちょっと記録もこめて。

  • アセンブリの権限は「無制限」な必要がある
  • データベースのTRUSTWORHYプロパティはONにしておく必要がある
  • データベースの所有者欄が表示されている状態でないといけない

最高に悩んだのは3つめ。SqlServerのインスタンスにおけるセキュリティ関係で、明確にログインアカウントを作成しておきデータベースのセキュリティでも明確にそのアカウントを作成しておかないと、所有者のところに表示がかかる状態になってくれないんだよね。

そしてそうなってないと、無制限に設定できないわけだ。

そこさえクリアすれば、肝心のSQLCLRとして作成するロジック自体はフツーに作ってOK。

2008年1月10日木曜日

MeCabによるフルテキスト検索対応

いろいろ悩みつつもなんとかテストできた。結構トラブルがあったので忘れないようにメモ。

1:SQLCLRでのアセンブリ権限
今回のように外部プログラムを実行する場合、権限は「無制限」でないと実行できない。そのためにはデータベースのプロパティの設定、またはログイン周りでの権限設定が必要。今回はデータベースのプロパティ設定にて対応した。

-- TRUSTWORTHYプロパティの設定
ALTERDATABASE KEIWASET TRUSTWORTHY ON

そして今回は実際にログインするアカウントには管理者権限を与えていないので、SQLCLRビルド時は権限を「安全」にして配置を行う。

2:データベースの所有者
今までは気にしていなかったけど、「SqlServerのセキュリティにはWindowsグループで登録されていて、そこに属するユーザーでデータベースを構築した」際には、データベースの所有者欄に何も表示されていない状態になる。このままだとアセンブリの権限設定を行うことができないので、明示的に所有者を再設定する必要がある。
権限設定に必要な条件として、「SqlServerのセキュリティに登録されているログイン(≠グループ)」であり、その「ログインがデータベースの所有者となっている」必要があるみたい。
SqlServerのセキュリティに明示的にアカウントを追加し、その後ManagementStudioなどからデータベースのプロパティを開き、ファイルのページにある所有者を明示的に設定する。

SQLCLRの中身としては、今までの開発とほぼ同じなので楽々。気にしないといけないのは、デフォルトで参照設定できるアセンブリは限られているってところくらい。
そこを除けばT-SQLでゴリゴリ書くよりも大分楽だし、できることがたくさん増えるのですばらしいですわ。
T-SQLで外部プログラムを実行するためには、悪名高いXP_CMDSHELLを使う必要が高い(もう一つはOLEを利用するのでかなり限定されてしまう)し、しかも標準入出力を使うことができないからどうしても面倒が多いんだよね。ということで実際のSQLCLRで作成したMeCabを起動するためのロジックを。

Imports System  
Imports System.Data  
Imports System.Data.SqlClient  
Imports System.Data.SqlTypes  
Imports Microsoft.SqlServer.Server  
Imports System.Runtime.InteropServices  

Partial Public Class UserDefinedFunctions   

  Private Const _mecab_InstallPathAsString ="D:\Tool\MeCab\Bin\" 
  Private Const _mecab_FilenameAsString ="MeCab.Exe" 
  Private Const _mecab_WakatiOptionAsString ="-O wakati"  

  <Microsoft.SqlServer.Server.SqlFunction()> _ 
  PublicSharedFunction UP_CLR_DevideStrings(ByVal targetStrings As SqlString) As SqlString 
    ' コードをここに追加してください 
    If targetStrings.ToString.Trim.Equals("")Then Return Nothing 

    Dim resultStringAsString 
    Using mecabProcessAsNew System.Diagnostics.Process 
      With mecabProcess 
        With .StartInfo 
          .RedirectStandardOutput =True 
          .RedirectStandardInput =True 
          .CreateNoWindow =True 
          .UseShellExecute =False 
          .WorkingDirectory = _mecab_InstallPath 
          .FileName = _mecab_InstallPath + _mecab_Filename 
          .Arguments = _mecab_WakatiOption 
        EndWith 
        .Start() 
        .StandardInput.WriteLine(targetStrings) 
        resultString = .StandardOutput.ReadLine 
      EndWith 
    End Using 
    ReturnNew SqlString(resultString) 
  EndFunction 
EndClass

現地環境のときにもきっと同じ事で悩みそうだから忘れないようにしないとね。

2008年1月8日火曜日

DataGridViewでセル間のドラッグドロップ

よくある質問としては「DataGridViewで行の並び替えをマウスで行いたい」というのがあるんだけど、
意外と少ないのは「同じDataGridViewの中でセルの値をドラッグドロップしたい」というやつ。

どうしても業務AP作っている時間が長ければ長いほど、キーボードメインになってしまうので
こういった発想自体出てこないというか、やったらいけないと思い込んでしまうんだよね。
完全にオペレータ向けのGUIならともかく、一般的な社員も使うなら問題ない、むしろ歓迎されるんだけどw

ということでセル間のドラッグドロップだけど、その前に注意点が。

編集モードはEditOnEnter禁止

当たり前だけど編集状態=編集コントロールがアクティブだからセルを操作することができないんで。
くだらなさそうだけど、実際にひっかかってしまった人がここにいますw

処理としてはDataGridViewのDrag~系イベントのみの記述で事足りるね。
DragOverイベントで引数として渡ってくるdrgeventのEffectプロパティに対して適切なエフェクトを設定してあげて、DragDropイベントで元セルから先セルへ値をコピーなり移してあげればOK。

ただ一点面倒なことがあって、ドラッグ元セルの値の種類を判断してうんぬんしようとすると大変なことになるのよ。
というのもデフォルトで用意されている各種Cellクラスでは保持する値のTypeがどれもこれもObjectでありやがるからで、
ここでは割り切ってセルの値を移動(またはコピー)としてしまった方がいいね。

それであれば、(先セル).Value = (元セル).Valueと書くだけでいけるから。アクションが移動なら、その後に(元セル).Value = Nothingとかそんな類の処理をいれてあげれば大丈夫。

2008年1月7日月曜日

SqlServerでのフルテキスト検索

ちょっと今回は違う話題で。

SqlServerは結構前からフルテキスト検索という、感覚的にはネットで検索するようなイメージで検索できる仕組みが用意されているんだよね。
ただそういった機能が用意されているにも関わらず実際に使ったことは殆どなかったわけで。

色々試してみると中々面白いんだな、コレ。

やりようにもよるけど、データ量が多くなればなるだけLIKEを使った検索よりも速度は速い。
(LIKEでレスポンスをあげるには後方一致用のインデックスを用意する必要がある)
そしてもってxlsファイルやdocファイル、pptファイルも検索対象にできてしまうのでやりようによってはかなり面白いことができるねぇ。

ついでにいうと、わかち書きとかを自分でカスタマイズできるらしいので、気力と根性があれば・・・!

VS2008いれてみた

ということで年末年始の間に、自宅PCにインストールしてみました。

ExpressEditionでどこまでサポートされているか、ってところをメインに確認してみたんだけど、WPFはちゃんとサポートされていたなぁ。WFはサポートしていないみたいだけど。

それでもってインストールするとついでにメイリオフォントもインストールされるので、VSでの表示がWPFの時だけ面白い表示に。
普通の場合は今までと同じ見た目なんだけど、WPF触るときだけダイアログのあたりがメイリオの表示になるので、ちょっとだけ違和感がw
しかし無償で用意できる環境でここまでできてしまうってのがスゲェなぁ。