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日水曜日

数値と文字

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

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

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

面倒だねぇ。