- PR -

【DataGridView】拡張したコントロールをホストする列が複数ある場合

投稿者投稿内容
reiko
ベテラン
会議室デビュー日: 2004/11/19
投稿数: 84
投稿日時: 2007-09-19 10:07
いつもお世話になります。

以前こちら
相談させていただいた件の続きです。

上記で相談させていただいた結果、
Windows フォーム DataGridView Cells でコントロールをホストする方法でコンボボックスを拡張しました。

そこで今回の質問になるのですが、
上記の方法で拡張したコンボボックスを複数列に使用したい場合(リスト値は違うコンボ)、
どのように設定をすればよいかわかりません。

今回拡張したコンボボックスでは、
コンストラクタにデータテーブルを渡し、
そのデータの内容をリストに追加するように作っています。


Dim prm As New DataTable
Dim prm1 As New DataTable

Dim dc1 As New ComboColumn(prm)
Dim dc2 As New ComboColumn(prm1)

grdData.Columns.Add(dc1)
grdData.Columns.Add(dc2)


のように設定した場合、dc1の列も、dc2の列も、
prm1のリスト値を持ったコンボボックスが表示されてしまいます。

試しに下記のように列を追加してみましたが、これもダメでした。

Dim dc1 As New ComboColumn(prma)
grdData.Columns.Add(dc1)
Dim dc2 As New ComboColumn(prma1)
grdData.Columns.Add(dc2)



このような場合、どのようにして列ごとに違うコンボボックスを表示するようにすれば良いのでしょうか。


同様の理由で、テキストボックスを拡張したコントロールをホストしている列もあるのですが、
これも最後の列で設定したテキストボックスの設定が全ての列で生きている状態です。

何卒ご指導よろしくお願い致します。

KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-09-19 10:22
手元に検証できる環境がないのですが、ちょっと考えにくい現象ですね。
dc1, dc2 は別のインスタンスですから、
その ComboColumn というクラスが正しく実装されていれば、
そんなことにはならないと思います。

内部で Shared のフィールドに格納してる・・・なんてことはないですよね?
とにかく、ComboColumn の実装を差し支えない部分で見せてもらえませんか?
reiko
ベテラン
会議室デビュー日: 2004/11/19
投稿数: 84
投稿日時: 2007-09-19 10:45
KIさん、早速のご回答有難うございます。

Shared・・・やってます(汗)
なるほど、Sharedですか・・・(泣)
ご指摘ありがとうございます。

すみません。Sharedの使い方があんまり良くわかってないのですが、
DataGridViewColumnのクラス(ComboColumn)から、
DataGridViewTextBoxCellのInitializeEditingControlで設定する値を渡す方法が
それしか思いつかなくて、やってました。

Sharedのヘルプを見てもまだついていけなくて・・・申し訳ありません。


基本的にサンプルのカレンダーコントロールの部分をコンボに直しただけの
ソースですが、末尾にソースを記載します。

今からもう一度Sharedのヘルプを頑張って読んでみます。


Imports System
Imports System.Windows.Forms


Public Class ComboColumn
Inherits DataGridViewColumn

Public Sub New(ByVal prma As DataTable)
MyBase.New(New ComboCell())

ComboCell.dt = prma

End Sub

Public Overrides Property CellTemplate() As DataGridViewCell
Get
Return MyBase.CellTemplate
End Get
Set(ByVal value As DataGridViewCell)

' Ensure that the cell used for the template is a CalendarCell.
If Not (value Is Nothing) AndAlso _
Not value.GetType().IsAssignableFrom(GetType(ComboCell)) Then
Throw New InvalidCastException("Must be a CalendarCell")
End If
MyBase.CellTemplate = value

End Set
End Property

'最大文字数受取用メソッド
Public Sub MaxLengthSet(ByVal value As Long)

ComboCell.txtLength = value

End Sub
End Class

Public Class ComboCell
Inherits DataGridViewTextBoxCell

Public Shared dt As DataTable 'コンボボックスセット用リスト
Public Shared txtLength As Long '手入力の際の最大文字数

Public Sub New()
End Sub

Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer, _
ByVal initialFormattedValue As Object, _
ByVal dataGridViewCellStyle As DataGridViewCellStyle)

Dim i As Integer

MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, _
dataGridViewCellStyle)



Dim ctl As New ComboBox
ctl = CType(DataGridView.EditingControl, ComboBox)
ctl.Items.Clear()



'ループで値を追加
ctl.Items.Add("")
For i = 0 To dt.Rows.Count - 1

ctl.Items.Add(dt.Rows(i).Item(0))
Next

ctl.DropDownStyle = ComboBoxStyle.DropDown
ctl.AutoCompleteMode = AutoCompleteMode.Suggest
ctl.MaxLength = txtLength '最大文字数

'編集前のセルの値をコンボの初期値としてセット
ctl.Text = Me.Value

End Sub

Public Overrides ReadOnly Property EditType() As Type
Get
' Return the type of the editing contol that CalendarCell uses.
Return GetType(ComboBoxControl)
End Get
End Property

Public Overrides ReadOnly Property ValueType() As Type
Get
' Return the type of the value that CalendarCell contains.
Return GetType(String)
End Get
End Property

Public Overrides ReadOnly Property DefaultNewRowValue() As Object
Get
' Use the current date and time as the default value.
Return ""
End Get
End Property


End Class

Class ComboBoxControl
Inherits ComboBox
Implements IDataGridViewEditingControl

Private dataGridViewControl As DataGridView
Private valueIsChanged As Boolean = False
Private rowIndexNum As Integer

Public Sub New()
End Sub

Public Property EditingControlFormattedValue() As Object _
Implements IDataGridViewEditingControl.EditingControlFormattedValue

Get
Return Me.Text
End Get

Set(ByVal value As Object)
If TypeOf value Is [String] Then
Me.Text = value
End If
End Set

End Property

Public Function GetEditingControlFormattedValue(ByVal context _
As DataGridViewDataErrorContexts) As Object _
Implements IDataGridViewEditingControl.GetEditingControlFormattedValue

Return Me.Text

End Function

Public Sub ApplyCellStyleToEditingControl(ByVal dataGridViewCellStyle As _
DataGridViewCellStyle) _
Implements IDataGridViewEditingControl.ApplyCellStyleToEditingControl

Me.Font = dataGridViewCellStyle.Font
Me.BackColor = dataGridViewCellStyle.BackColor

End Sub

Public Property EditingControlRowIndex() As Integer _
Implements IDataGridViewEditingControl.EditingControlRowIndex

Get
Return rowIndexNum
End Get
Set(ByVal value As Integer)
rowIndexNum = value
End Set

End Property

Public Function EditingControlWantsInputKey(ByVal key As Keys, _
ByVal dataGridViewWantsInputKey As Boolean) As Boolean _
Implements IDataGridViewEditingControl.EditingControlWantsInputKey

Return True



End Function

Public Sub PrepareEditingControlForEdit(ByVal selectAll As Boolean) _
Implements IDataGridViewEditingControl.PrepareEditingControlForEdit

' No preparation needs to be done.

End Sub

Public ReadOnly Property RepositionEditingControlOnValueChange() _
As Boolean Implements _
IDataGridViewEditingControl.RepositionEditingControlOnValueChange

Get
Return False
End Get

End Property

Public Property EditingControlDataGridView() As DataGridView _
Implements IDataGridViewEditingControl.EditingControlDataGridView

Get
Return dataGridViewControl
End Get
Set(ByVal value As DataGridView)
dataGridViewControl = value
End Set

End Property

Public Property EditingControlValueChanged() As Boolean _
Implements IDataGridViewEditingControl.EditingControlValueChanged

Get
Return valueIsChanged
End Get
Set(ByVal value As Boolean)
valueIsChanged = value
End Set

End Property

Public ReadOnly Property EditingControlCursor() As Cursor _
Implements IDataGridViewEditingControl.EditingPanelCursor

Get
Return MyBase.Cursor
End Get

End Property

Protected Overrides Sub OnTextChanged(ByVal eventargs As EventArgs)

' Notify the DataGridView that the contents of the cell have changed.
valueIsChanged = True
Me.EditingControlDataGridView.NotifyCurrentCellDirty(True)
MyBase.OnTextChanged(eventargs)

End Sub

End Class

Ahf
大ベテラン
会議室デビュー日: 2006/08/16
投稿数: 172
投稿日時: 2007-09-19 11:36
確認はとれていないのですが、Sharedがビンゴのような気がします。

InitializeEditingControlメソッドで設定したい、ということでしたので、
コード:
Dim columnEdit As ComboColumn _
    = DirectCast(Me.DataGridView.Columns(ColumnIndex), ComboColumn )


MyBase.InitializeEditingControlの後にでもこう書いてみてごにょごにょしてみると
いけそうな気がしています。
KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-09-19 12:05
今回は Shared を使う必要など全くありません。
Shared の意味については、検索すればいくらでも出てきますので、
勉強しておくことをお勧めします。

本題の方ですが、いろいろややこしいです。
Shared を理解されてないレベルだと、少し辛いかも知れませんが、
方針だけ示しておきますので、頑張ってみてください。
きっといい勉強になると思います。

txtLength と dt の2つを外部から設定できるようにしたいのですよね。
ComboColumn, ComboCell のそれぞれに、この2つのプロパティを追加します。
ComboCell は、提示されたコードでは Public Shared のフィールドになっていますが、
これをやめてPublic のプロパティと Private のフィールドのセットにします。
dt ならこんな風に書きます。
コード:
    Private _dt As DataTable

    Public Property Dt() As DataTable

        Get
            Return _dt
        End Get
        Set(ByVal value As DataTable)
            Me._dt = value
        End Set
    End Property



ComboColumn のコンストラクタでは、引数で渡された DataTable を自分自身(ComboColumn)の dt に設定します。
そして、ComboColumn の CellTemplate プロパティのセッターで、渡された値(value)が Nothing でない場合に、
ComboCell にキャストして、その dt, txtLength プロパティに ComboColumn のものを設定します。

また、MSDNの DataGridViewCell クラスに以下のようにあります。

引用:

継承元へのメモ : DataGridViewCell からクラスを派生させて新しいプロパティを追加する場合は、Clone メソッドをオーバーライドして、クローン操作時に新しいプロパティをコピーする必要があります。また、基本クラスの Clone メソッドも呼び出して、基本クラスのプロパティが新しいセルにコピーされるようにする必要があります。



この通りに Clone メソッドをオーバーライドして、dt, txtLength の2つのプロパティがコピーされるようにします。

以上が正しく実装できれば動く・・・と思います。検証は今できないのですが。

本題とは関係ありませんが
引用:

コード:
    Public Sub MaxLengthSet(ByVal value As Long)

        ComboCell.txtLength = value

    End Sub




本当に Long を使う必要がありますか?
Integer, Long がそれぞれ何バイトか調べてみてください。
reiko
ベテラン
会議室デビュー日: 2004/11/19
投稿数: 84
投稿日時: 2007-09-19 15:05
Ahfさん、KIさん、ありがとうございます。

アドバイスいただいたとおりにやってみて、
無事リストが違うコンボボックスを設定する事が出来ました。

まず、KIさんにアドバイス頂いたとおりに、ComboColumn、ComboCell の両方に
必要なプロパティを追加しました。

その後Ahfさんにアドバイス頂いたとおりに
InitializeEditingControlメソッドに
コード:
Dim columnEdit As ComboColumn _
    = DirectCast(Me.DataGridView.Columns(ColumnIndex), ComboColumn )

Me.Dt = columnEdit.Dt
Me.MaxLength = columnEdit.MaxLength

ctl.Items.Add("")
For i = 0 To Dt.Rows.Count - 1
   	ctl.Items.Add(Dt.Rows(i).Item(0))
Next

ctl.MaxLength = MaxLength 



のようにしてみました。
有難うございました。とても助かりました。



>KIさんへ

Long型の件、ご指摘有難うございます。
私が新人だった頃、VB6だったかVB5だったかの時代に、
先輩から「IntegerよりLongの方が処理が早いから、Integerは使わないでね」と
教えられ、以来ずっとIntegerのデータを格納するような変数でもLongを宣言する
癖がついていました。.NETではそんなの関係ないのですかね。
・・・というかVB5,VB6時代でもそんな事はなかったとか!?

早速Integerに直しました・・・。


あと、出来れば勉強のために教えていただきたいのですが、
実は

引用:

ComboColumn のコンストラクタでは、引数で渡された DataTable を自分自身(ComboColumn)の dt に設定します。
そして、ComboColumn の CellTemplate プロパティのセッターで、渡された値(value)が Nothing でない場合に、
ComboCell にキャストして、その dt, txtLength プロパティに ComboColumn のものを設定します。



の方法も試してみたんです。

私はこのように書いてみたのですが、KIさんの意図する事を書けてますでしょうか・・・。
Setの部分にブレークポイントを置いてみたのですが、Set部分が
通る事はなくて・・・。

コード:
    Public Overrides Property CellTemplate() As DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(ByVal value As DataGridViewCell)

   
            ' Ensure that the cell used for the template is a CalendarCell.
            If Not (value Is Nothing) AndAlso _
                Not value.GetType().IsAssignableFrom(GetType(ComboCell)) Then
                Throw New InvalidCastException("Must be a CalendarCell")
            End If

            Dim ComCel As ComboCell = CType(value, ComboCell)

            ComCel.Dt = Me.Dt
            ComCel.MaxLength = Me.MaxLength

            'MyBase.CellTemplate = value
            MyBase.CellTemplate = ComCel

        End Set
    End Property




KI
大ベテラン
会議室デビュー日: 2007/01/10
投稿数: 239
投稿日時: 2007-09-19 16:56
引用:

私はこのように書いてみたのですが、KIさんの意図する事を書けてますでしょうか・・・。
Setの部分にブレークポイントを置いてみたのですが、Set部分が
通る事はなくて・・・。


方向はほぼ問題ありません。
ただ一点、value が Nothing のときを If文で分けておかないと NullReferenceException が出ると思います。

セッターを通らない件については、すみません、私が勘違いしていました。
調べてみましたが、DataGridViewColumn から DataGridViewCell へ追加したプロパティを渡すのは、
CellTemplate でやるのではなく、DataGridViewColumn に追加したプロパティのセッターで
MyBase.CellTemplate に対して行うのがだ正しいようです。

コード:
    Private _dt As DataTable

    Public Property Dt() As DataTable

        Get
            Return _dt
        End Get
        Set(ByVal value As DataTable)
            Me._dt = value
            Dim cell As ComboCell = DirectCast(MyBase.CellTemplate, ComboCell)
            cell.Dt = value
        End Set
    End Property



一方で、MSDNによると

引用:
DataGridViewColumn.CellTemplate(MSDN)より引用:
セル テンプレートのプロパティを変更しても、列の既存のセルのユーザー インターフェイス (UI) に
直ちに反映されることはありません。
この変更は、列が再生成されたとき (列を並べ替えたり DataGridView.InvalidateColumn メソッドを
呼び出したりした場合など) に、初めて反映されます。


とのことなので、列ヘッダーをクリックしてソートした場合などに、セッターを通ると思いますので、
CellTemplate の処理追加はやはり必要です。

ただし、実は reiko さんが今回行った実装と、私が(少し足りませんでしたけど)示した実装は違います。
(どちらも誤りではありませんし、正しく動作するはずです。)
私の示した実装をする場合は CellTemplate の処理追加は必要ですが、
reikoさんの実装の場合は不要です。
また、reikoさんの実装の場合は、ComboCell にはプロパティの追加も不要ですし、
Clone メソッドのオーバーライドも不要です。
この2つの実装の違いについて説明しておきます。

reiko さんの実装では ComboCell.InitializeEditingControl で ComboColumn の Dt プロパティを参照しています。
一方、私の示した実装では、ComboCell.InitializeEditingControl では、
自分自身(ComboCell)の Dt プロパティを参照する形になります。
この場合、ComboCell にもプロパティを追加する必要があります。
Clone メソッドのオーバーライドも必要ですし、
ComboColumn の追加プロパティのセッターや、CellTemplate プロパティのセッターに
特殊な処理を入れなければならないのは、説明した通りです。

reiko さんの実装の方が簡単なのですが、.NET標準の DataGridViewCell の多くは
私の示した実装を取っています。
例えば、DataGridViewComboBoxColumn に DataSource というプロパティがありますが、
DataGridViewComboBoxCell にも DataSource という、対応するプロパティがあります。
この方法を取るメリットは、行ごとにプロパティの値を変更したいという要求に対応できる点です。
ですから、こちらの方が汎用的だとは言えます。

こういった点も考慮して、どちらの実装にするかを決めればよいと思います。


引用:

先輩から「IntegerよりLongの方が処理が早いから、Integerは使わないでね」と
教えられ、以来ずっとIntegerのデータを格納するような変数でもLongを宣言する
癖がついていました。.NETではそんなの関係ないのですかね。


VB6の Integer より Long が速いという噂は聞いたことがあります。
おそらくVB6のIntegerが16ビット、Longが32ビットであることから、
現在主流となっている32ビットCPUでは Long の方が扱いやすいという話から
来ているものだと思います。

VB2005では、Integerが32ビット、Longが64ビットに変更されていますので、
こうした心配は全く意味がありません。
もっといえば、JITコンパイラがうまく最適化してくれると思うので、
全く関係ないと思います。

このケースは、迷わず Integer を使えばよいです。

引用:
・・・というかVB5,VB6時代でもそんな事はなかったとか!?


調べてみたところ、そんなことはなかった説の方が有力みたいですね。
VB6から実行すると差が出ますが、コンパイル時に最適化されるので、
exeから起動すると差が出ないとか…
reiko
ベテラン
会議室デビュー日: 2004/11/19
投稿数: 84
投稿日時: 2007-09-20 09:23
KIさん、詳しい解説本当に有難うございます。
とても勉強になりました。

私の方法ではComboCellにはプロパティの設定は不要なのですね。
・・・た、確かになくてもいけますね ^^;

Cloneメソッドの件も、実は試しにコメントアウトしても
正常に動いていたので疑問に感じていたのです。

でもMSDNにそう書いているのだし、他のサイトでも
「プロパティを追加した時はClone メソッドのオーバーライドが必要」と書いてるし
何かしらの理由で必要なんだろうなあ・・・という感じだったのです。
そういう事だったのですね。なるほどです。

Long,Integerの件もこれからは迷わずLongを使う事にします。
ご指摘いただけて助かりました。
ずっとInteger使わないで生きていくところでした(笑)


今日中にはアドバイス頂いた方法を試せると思うのですが、
たぶん夕方になってしまいそうなので、お礼だけ先に投稿しました。

結果は改めて報告させていただきます。
本当に有難うございました。

スキルアップ/キャリアアップ(JOB@IT)