QA@IT

DataGridViewにユーザーコントロールを初期表示させたいです

78935 PV

VB.NET(.Netframework4.5)です。
テキストボックスやボタンを貼り付けたユーザーコントロールを作成し、それをDataGridViewで使用したいと思っています。
下記のURLの情報を参考に作成してみました。
http://msdn.microsoft.com/ja-jp/library/7tas5c80(v=vs.110).aspx
http://morado106.blog106.fc2.com/blog-entry-25.html

しかし、DataGridViewが最初に表示された時点では、普通のDataGridViewの表示のままです。
セルをダブルクリックして編集状態になって初めてユーザーコントロールが表示されます。

「作成するには、DataGridViewColumn と DataGridViewCell から派生するクラスを定義する必要があります。 また、IDataGridViewEditingControl から派生し、Control インターフェイスを実装するクラスを定義する必要もあります。」
と説明に有り、実際サンプルにそってその通りしました。
DataGridViewTextBoxCellを継承しているので、先述のような動きになっていると予想してます。

希望としては編集モードでなくても、最初からユーザーコントロールが表示されて欲しいのですが、何が足りないでしょうか?
ボタンやチェックボックスタイプのセルが初期表示できているところを見ると、ユーザーコントロールでもできると思います。
DataGridViewTextBoxCellではなくDataGridViewCellを継承し、DataGridViewTextBoxCellも使わないと、編集モードでさえ表示されませんでした。
描画のための処理が足りないと思うのですが、どういう箇所に手を入れていけばいいのかわかりません。

C#でも構いませんので、サンプル等の情報をお教えいただけませんでしょうか。
よろしくお願いします。

Shuさんのご指摘どおり、コードを載せます。
まずは表示できるようにしてからと思い、ユーザコントロールに値を設定したり
計算する処理等はまだ未実装です。

'Cellに与える値のクラス
Public Class MyOrginalCellValue
    Public Enum Value
        First
        Second
        Third
        Null
    End Enum
    Public MyValue As Value
End Class

'DataGridViewColumnを継承したカラムのクラス
Public Class MyOrginalCellColumn
    Inherits DataGridViewColumn

    Public Sub New()
        MyBase.New(New MyOrginalCell())
    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 (value IsNot Nothing) AndAlso _
                Not value.GetType().IsAssignableFrom(GetType(MyOrginalCell)) _
                Then
                Throw New InvalidCastException("Must be a MyOriginalCell")
            End If
            MyBase.CellTemplate = value

        End Set
    End Property

End Class

'DataGridViewTextBoxCellを継承したセルのクラス
 Public Class MyOrginalCell
    Inherits DataGridViewTextBoxCell

    Public Sub New()
        MyBase.New()
    End Sub

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

        ' Set the value of the editing control to the current cell value.
        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, _
            dataGridViewCellStyle)

        Dim ctl As MyControl = _
            CType(DataGridView.EditingControl, MyControl)

        ' Use the default row value when Value property is null.
        If (Me.Value Is Nothing) Then
            ctl.Value = MyOrginalCellValue.Value.Null
        Else
            ctl.Value = Me.Value
        End If
    End Sub

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

    Public Overrides ReadOnly Property FormattedValueType As Type
        Get
            Return GetType(System.String)
        End Get
    End Property

End Class

'DataGridViewのセルに表示させたいユーザコントロール
'デザインにはラベルやボタンを配置しています
Public Class MyControl
    Inherits UserControl
    Implements IDataGridViewEditingControl

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

    Private _Value As MyOrginalCellValue.Value
    Public Property Value() As MyOrginalCellValue.Value
        Get
            Return _Value
        End Get
        Set(ByVal value As MyOrginalCellValue.Value)
            _Value = value
        End Set
    End Property


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

        Get
            Return Me._Value
        End Get

        Set(ByVal value As Object)
            Try
                ' This will throw an exception of the string is 
                ' null, empty, or not in the format of a date.
                Me._Value = value
            Catch
                ' In the case of an exception, just use the default
                ' value so we're not left with a null value.
                Me._Value = MyOrginalCellValue.Value.Null
            End Try
        End Set

    End Property

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

        Return Me._Value

    End Function

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

        Me.Font = dataGridViewCellStyle.Font

    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

        ' Let the DateTimePicker handle the keys listed.
        Select Case key And Keys.KeyCode
            Case Keys.Left, Keys.Up, Keys.Down, Keys.Right, _
                Keys.Home, Keys.End, Keys.PageDown, Keys.PageUp

                Return True

            Case Else
                Return Not dataGridViewWantsInputKey
        End Select

    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 Sub OnValueChanged(ByVal eventargs As EventArgs)

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

    End Sub

    Private Sub lblState_Click(sender As Object, e As EventArgs) Handles lblState.Click

    End Sub
End Class

'実際の動作はフォームにDataGridViewを貼り付け、Loadイベントに以下の
'処理を記述して確認しています。
Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        Dim col1 As New MyOrginalCellColumn()
        Me.DataGridView1.Columns.Add(col1)

        Dim idx As Integer
        ' DataGridViewの行追加(1行目)
        DataGridView1.Rows.Add()
        idx = DataGridView1.Rows.Count - 2
        DataGridView1.Rows(idx).Cells(0).Value = MyOrginalCellValue.Value.First

        For Each dgv In DataGridView1.Columns
            dgv.DefaultCellStyle.WrapMode = DataGridViewTriState.True
        Next

        DataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells

    End Sub

flied_onionさんのアドバイスからようやく1歩前進できました。
ユーザーコントロールをDataGridViewのセルの中に初期表示させられました。
Paintメソッドの中でユーザーコントロールのBitmapオブジェクトを取得して、
セルに貼り付けるという感じの手法です。
参考:http://blogahf.blogspot.jp/2007/08/blog-post_6170.html

ただ、まだ課題がありまして、ユーザーコントロールの中にボタンを配置しています。
そのボタンをクリックするには該当のセルを1回クリックし、さらにもう1回クリックして
編集モードにしてからじゃないと、ボタンのクリックができません。
UIとしては表示されているボタンを編集モードにしなくてもクリックできるようにしたいです。

DataGridViewのCellEnterイベントやCellMouseEnterイベントで編集モードになるように
SendKeys.Send("{F2}")や、DataGridView1.BeginEdit(False)をしてみましたが、
やはり1回セルをクリックしないとだめでした。

標準のDataGridViewButtonColumnのように最初からボタンをクリックしたいのですが、
どのようにすればよいのかアドバイスいただけないでしょうか。

  'セルクラスに追加したPaintメソッド
    Private _controlImage As Bitmap = Nothing

    Protected Overrides Sub Paint( _
ByVal graphics As Graphics, _
ByVal clipBounds As Rectangle, _
ByVal cellBounds As Rectangle, _
ByVal rowIndex As Integer, _
ByVal cellState As DataGridViewElementStates, _
ByVal value As Object, _
ByVal formattedValue As Object, _
ByVal errorText As String, _
ByVal cellStyle As DataGridViewCellStyle, _
ByVal advancedBorderStyle As DataGridViewAdvancedBorderStyle, _
ByVal paintParts As DataGridViewPaintParts)


        MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, _
                     value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)


        Using myControl As New MyControl

            If myControl Is Nothing Then Return

            myControl.Top = myControl.Height * -1
            myControl.Left = myControl.Width * -1
            If (Me.DataGridView IsNot Nothing) AndAlso _
               (Me.DataGridView.TopLevelControl IsNot Nothing) Then
                Me.DataGridView.TopLevelControl.SuspendLayout()
                Me.DataGridView.TopLevelControl.Controls.Add(myControl)
            End If

            myControl.BackColor = cellStyle.BackColor
            myControl.ForeColor = cellStyle.ForeColor
            myControl.Font = cellStyle.Font

            'イメージキャプチャのメソッドを呼び出す 
            _controlImage = myControl.CreateControlImage(False)

            If (Me.DataGridView IsNot Nothing) AndAlso _
               (Me.DataGridView.TopLevelControl IsNot Nothing) Then
                Me.DataGridView.TopLevelControl.Controls.Remove(myControl)
                Me.DataGridView.TopLevelControl.ResumeLayout()
            End If

        End Using


        'イメージの描写 
        graphics.DrawImage(_controlImage, cellBounds.X, cellBounds.Y, _controlImage.Width, _controlImage.Height)
        '枠線が必要な際は描写 
        If (paintParts And DataGridViewPaintParts.Border) = DataGridViewPaintParts.Border Then
            MyBase.PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle)
        End If
    End Sub

    'ユーザーコントロールクラスに追加したビットマップ取得メソッド

    Private Const SRCCOPY As Integer = &HCC0020

    ''' <summary>BitBlt Win32Apiの定義</summary><exclude />
    <System.Runtime.InteropServices.DllImport("gdi32.dll")> _
    Private Shared Function BitBlt(ByVal hdcDest As IntPtr, _
            ByVal nXDest As Integer, ByVal nYDest As Integer, _
            ByVal nWidth As Integer, ByVal nHeight As Integer, _
            ByVal hdcSrc As IntPtr, _
            ByVal nXSrc As Integer, ByVal nYSrc As Integer, _
            ByVal dwRop As Integer) As Boolean
    End Function

    Public Function CreateControlImage(ByVal visibleStatus As Boolean) As System.Drawing.Bitmap
        Dim _controlImage As New System.Drawing.Bitmap(Me.Width, Me.Height)

        If visibleStatus Then
            'ダミーコントロールの追加
            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()

            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
                    MyControl.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

            Me.Controls.Remove(dummyControl)
        Else
            '非表示時のキャプチャ
            Me.Visible = True
            Me.DrawToBitmap(_controlImage, New System.Drawing.Rectangle(0, 0, Me.Width, Me.Height))
        End If

        Return _controlImage
    End Function

  • まず作成したコードを載せた方がよいかと思います。
    -

回答

自身で描画する必要があります。MyOrginalCellでPaintメソッドをオーバーライドしてそこで描画してください。

四角で塗りつぶす簡単な描画の例は以下の様になります。

protected override void Paint(

    System.Drawing.Graphics graphics, 
    System.Drawing.Rectangle clipBounds, 
    System.Drawing.Rectangle cellBounds, 
    int rowIndex, DataGridViewElementStates cellState, 
    object value, 
    object formattedValue, 
    string errorText, 
    DataGridViewCellStyle cellStyle, 
    DataGridViewAdvancedBorderStyle advancedBorderStyle, 
    DataGridViewPaintParts paintParts) {

    base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, 
        value, formattedValue, errorText, 
        cellStyle, advancedBorderStyle, paintParts);

    graphics.FillRectangle(Brushes.DarkCyan, cellBounds);
    graphics.DrawRectangle(Pens.Blue, cellBounds);

}

VB.NETだと多分以下の様な感じ

    Protected Overrides Sub Paint(
                        ByVal graphics As Graphics, _
                        ByVal clipBounds As Rectangle, _
                        ByVal cellBounds As Rectangle, _
                        ByVal rowIndex As Integer, _
                        ByVal cellState As DataGridViewElementStates, _
                        ByVal value As Object, _
                        ByVal formattedValue As Object, _
                        ByVal errorText As String, _
                        ByVal cellStyle As DataGridViewCellStyle, _
                        ByVal advancedBorderStyle As DataGridViewAdvancedBorderStyle, _
                        ByVal paintParts As DataGridViewPaintParts)

        MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, _
                     value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)

        graphics.FillRectangle(Brushes.DarkCyan, cellBounds)
        graphics.DrawRectangle(Pens.Blue, cellBounds)

    End Sub

参考として、どぼんさんのサイトを挙げておきます。

http://dobon.net/vb/dotnet/datagridview/maskedtextboxcolumn.html

補足:ここではセルの編集に使用するコントロールを独自にホストする方法だけを紹介し、セルの描画についてはDataGridViewTextBoxCellクラスに全て任せてしまいます。カスタムセルクラスでセルを自分で描画する方法は、こちらで紹介しています。

上記「こちら」のページでセル描画のサンプルがあります。

.NETの DataGridViewTextBoxCell自身もPaintPrivateの中でDrawTextなど行っているようですね。
参考までにこちらもリンクを載せておきます。

編集 履歴 (0)
  • なるほど、貴重な情報のご提供ありがとうございます。
    まだ詳細を理解するのはこれからですが、筋道は見えた気がします。時間かかると思いますが試して結果をご報告したいです。
    -
  • アドバイスを頂いて初期表示させるところまでできました。ありがとうございました。また別の問題が有り、元の質問文に追記しました。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。