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

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

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