- PR -

カスタマイズしたDataGridViewCheckBoxのオンオフができない

1
投稿者投稿内容
とある根性なし
ベテラン
会議室デビュー日: 2006/08/15
投稿数: 54
投稿日時: 2007-10-17 20:35
言語:VB2005
OS:WindowsXP

お世話になります。
ブログ「主婦と .NET と犬の記録」さんの下記のページやMSDNを参考に、
DataGridView のセルに TabStop機能を追加すべく奮闘中です。
http://blogs.wankuma.com/naoko/archive/2006/12/22/52885.aspx
本来、上記ブログ様へ質問するべきなのかもしれませんが、
ブログということもありますし、より多くの方に閲覧・参考にしてもらいため、
こちらに質問させていただきます。

現象についてなんですが、下記のようなソースを記述し実行すると、
表示されたCheckBoxをクリックしてもチェックのオンオフができず、
値が初期値のまま変更することができないのです。
クラスの継承の関係なのかもと思い調べてみても見当がつきません。

アドバイスいただけませんでしょうか。
よろしくお願いいたします。
※長くてごめんなさい。

下記ソース概要
================================================================================
・イベント Form1_Load
  フォームにTabStopDataGridViewを作成、TabStopCheckBoxColumnを追加します。
  CheckBoxの初期値を指定したレコードを数行を追加し、Falseの行の
  TabStopプロパティをFalseに設定。

================================================================================
・インターフェース ISupportTabStop
  プロパティ "TabStop" を宣言します。

================================================================================
・クラス TabStopCheckBoxCell
  クラス DataGridViewCheckBoxCellを継承し、ISupportTabStopを実装します。
  TabStopプロパティの初期化と、その他のプロパティの Clone を実行。

================================================================================
・クラス TabStopCheckBoxColumn
  クラス DataGridViewCheckBoxColumnを継承します。
  TabStopプロパティの初期化と、その他のプロパティの Clone を実行。

================================================================================
・クラス TabStopDataGridView
  クラス DataGridViewを継承します。
  TabキーまたはShift+Tabキーが押下された場合、移動先セルのTabStopプロパティを
  判定し、Falseなら次のセルへフォーカスを移し、Trueになるか最終行・列のセルまで
  繰り返します。

================================================================================

コード:
Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim dgView As New TabStopDataGridView
        Me.Controls.Add(dgView)
        dgView.Size = New Size(200, 300)

        Dim dgvCheckBox As New TabStopCheckBoxColumn
        dgvCheckBox.HeaderText = "Check"
        dgvCheckBox.Width = 50
        dgView.Columns.Add(dgvCheckBox)

        ' 行を追加する
        dgView.Rows.Add(True)
        dgView.Rows.Add(False)
        dgView.Rows.Add(True)
        dgView.Rows.Add(True)
        dgView.Rows.Add(False)

        ' Trueのセルにだけ止まるようにする
        For Each row As DataGridViewRow In dgView.Rows
            DirectCast(row.Cells(0), ISupportTabStop).TabStop = True
            If row.Cells(0).Value = False Then
                DirectCast(row.Cells(0), ISupportTabStop).TabStop = False
                row.Cells(0).ReadOnly = True
                row.Cells(0).Style.BackColor = Color.AntiqueWhite
            End If
            Trace.WriteLine(row.Cells(0).Value)
        Next
    End Sub
End Class

''' <summary>
''' ISupportTabStopインタフェースを宣言し、インタフェースのメンバを定義
''' </summary>
Public Interface ISupportTabStop
    Property TabStop() As Boolean
End Interface

#Region "TabStopCheckBox"

''' <summary>
''' DataGridViewCheckBoxCell型に ISupportTabStopを追加したデータ型を持つ、TabStopCheckBoxCellクラスを宣言
''' </summary>
Public Class TabStopCheckBoxCell
    Inherits DataGridViewCheckBoxCell
    Implements ISupportTabStop

    ' TabStop プロパティの値
    Private m_tabStop As Boolean

    ' 追加したプロパティの値を代入・取得するプロシージャを定義
    Public Property TabStop() As Boolean Implements ISupportTabStop.TabStop
        Get
            Return Me.m_tabStop
        End Get
        Set(ByVal value As Boolean)
            Me.m_tabStop = value
        End Set
    End Property

    ' TabStopCheckBoxCellのコンストラクタ(インスタンス生成時の初期化)
    Public Sub New()
        Me.m_tabStop = True
    End Sub

    ' セルが追加される時の初期化メソッドをオーバーライド
    Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer, _
        ByVal initialFormattedValue As Object, _
        ByVal dataGridViewCellStyle As DataGridViewCellStyle)

        ' 標準の初期化処理を行い、セルを初期化
        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)

        ' セルを追加する列のプロパティを取得して、新しいセルの TabStopプロパティを初期化
        Dim column As TabStopCheckBoxColumn = _
            DirectCast(MyBase.DataGridView.Columns(MyBase.ColumnIndex), TabStopCheckBoxColumn)
        Me.TabStop = column.TabStop

    End Sub

    ' EditType プロパティをオーバーライドして、戻り値の型情報で TabStopCheckBoxEditingControlを返す
    Public Overrides ReadOnly Property EditType() As System.Type
        Get
            Return GetType(DataGridViewCheckBoxCell)
        End Get
    End Property

    ' Clone メソッドをオーバーライドして、追加したプロパティも clone されるようにする
    Public Overrides Function Clone() As Object
        Dim cell As TabStopCheckBoxCell = DirectCast(MyBase.Clone(), TabStopCheckBoxCell)
        cell.TabStop = Me.TabStop
        Return cell
    End Function
End Class

''' <summary>
''' TabStopプロパティを持つ DataGridViewColumnを定義
''' </summary>
Public Class TabStopCheckBoxColumn
    Inherits DataGridViewCheckBoxColumn

    ' 作成する列の初期化(コンストラクタ)
    Public Sub New()
        Me.CellTemplate = New TabStopCheckBoxCell
    End Sub

    ' DataGridViewColumnの CellTemplateをオーバーライドして動作を置換えする
    Public Overrides Property CellTemplate() As System.Windows.Forms.DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(ByVal value As System.Windows.Forms.DataGridViewCell)
            ' value ≠ Nothing かつ valueが TabStopCheckBoxCellであるか判定
            If Not (value Is Nothing) AndAlso _
                Not value.GetType().IsAssignableFrom(GetType(TabStopCheckBoxCell)) _
                Then
                ' 条件を満たさない場合
                Throw New InvalidCastException("Must be a TabStopCheckBoxCell")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property

    ' TabStopCheckBoxCell.TabStop プロパティの定義
    Public Property TabStop() As Boolean
        Get
            Return DirectCast(MyBase.CellTemplate, TabStopCheckBoxCell).TabStop
        End Get
        Set(ByVal value As Boolean)
            DirectCast(MyBase.CellTemplate, TabStopCheckBoxCell).TabStop = value
        End Set
    End Property

    Public Overrides Function Clone() As Object
        Return MyBase.Clone()
    End Function
End Class
#End Region

#Region "TabStopDataGridView"

''' <summary>
''' DataGridView クラスを継承する TabStopDataGridView クラスを定義する
''' </summary>
Public Class TabStopDataGridView
    Inherits DataGridView

    ' TabStopDataGridView のコンストラクタ(クラスの初期化)
    Public Sub New()
        Me.InitializeComponent()
    End Sub

    ' 空のコンテナを作成
    Private components As System.ComponentModel.IContainer = Nothing

    ' TabStopDataGridView のデコンストラクタ(クラスの破棄)
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        ' componentsにインスタンスが生成されていたら破棄する
        If disposing AndAlso Not Me.components Is Nothing Then
            Me.components.Dispose()
        End If
        ' TabStopDataGridViewを破棄
        MyBase.Dispose(disposing)
    End Sub

    ' TabStopDataGridViewの初期化
    Private Sub InitializeComponent()
        Me.components = New System.ComponentModel.Container()
    End Sub

    ' 表示されている(Visible)列の中で、先頭の列(GetFirstColumn)のインデックスを取得する。
    Private Function GetFirstColumnIndex() As Integer
        Dim column As DataGridViewColumn = _
            Me.Columns.GetFirstColumn(DataGridViewElementStates.Visible)
        Dim index As Integer = Convert.ToInt32(IIf(column Is Nothing, -1, column.Index))

        Return index
    End Function

    ' 表示されている(Visible)行の中で、先頭の行(GetFirstRow)のインデックスを取得する。
    Private Function GetFirstRowIndex() As Integer
        Return Me.Rows.GetFirstRow(DataGridViewElementStates.Visible)
    End Function

    ' 表示されていて既定の状態の(Visible, None)列の中で、末尾の列(GetLastColumn)のインデックスを取得する。
    Private Function GetLastColumnIndex() As Integer
        Dim column As DataGridViewColumn = _
            Me.Columns.GetLastColumn(DataGridViewElementStates.Visible, DataGridViewElementStates.None)
        Dim index As Integer = Convert.ToInt32(IIf(column Is Nothing, -1, column.Index))

        Return index
    End Function

    ' 表示されている(Visible)行の中で、末尾の行(GetFirstRow)のインデックスを取得する。
    Private Function GetLastRowIndex() As Integer
        Return Me.Rows.GetLastRow(DataGridViewElementStates.Visible)
    End Function

    ' タブによる右方向の移動先を検索する
    Private Function GetNextTabPosition(ByVal currentCellAddress As Point) As Point
        Dim nextCellAddress As Point = New Point(-1, -1)
        Dim nextColumnIndex As Integer = -1

        ' 1列以上ある場合
        If Me.CurrentCellAddress.X <> -1 Then
            ' 移動先のセルを、表示中で ReadOnly等になっていない(Visible, None)セルの中から取得する(GetNextColumn)
            Dim column As DataGridViewColumn = Me.Columns.GetNextColumn(Me.Columns(currentCellAddress.X), _
                    DataGridViewElementStates.Visible, DataGridViewElementStates.None)
            If Not column Is Nothing Then
                ' 取得できたらそのセルの列位置を取得
                nextColumnIndex = column.Index
            End If
        End If
        If nextColumnIndex = -1 Then
            ' 列の末尾の場合、次の行の先頭に位置付け
            nextCellAddress.X = Me.GetFirstColumnIndex()
            nextCellAddress.Y = Me.Rows.GetNextRow(currentCellAddress.Y, _
                                        DataGridViewElementStates.Visible)
            If nextCellAddress.Y = -1 Then
                ' 行の末尾の場合、先頭行にジャンプ
                nextCellAddress.Y = Me.GetFirstRowIndex()
            End If
        Else
            ' 取得結果を設定
            nextCellAddress.X = nextColumnIndex
            nextCellAddress.Y = currentCellAddress.Y
        End If
        Return nextCellAddress
    End Function

    ' 右方向のタブ移動先が見つかるか判定する
    Private Function CanTabToNextCell() As Boolean
        Dim found As Boolean = False
        Dim currentCellAddress As Point = Me.CurrentCellAddress

        ' 移動先のセルが見つかるまで(found=Falseの間)ループ
        Do While (Not found)
            ' 現在のセル位置から移動先セル位置を取得
            Dim nextCellAddress As Point = Me.GetNextTabPosition(currentCellAddress)
            ' 移動先セルが ISupportTabStopを実装しているか判定
            If TypeOf MyBase.Rows(nextCellAddress.Y).Cells(nextCellAddress.X) Is ISupportTabStop Then
                Dim tabstopcell As ISupportTabStop = _
                    DirectCast(Me.Rows(nextCellAddress.Y).Cells(nextCellAddress.X), ISupportTabStop)
                ' TabStop=Trueなら移動先確定
                If tabstopcell.TabStop Then found = True
            Else
                ' 実装していなかったら移動先確定
                found = True
            End If
            ' 移動先未確定か、行・列位置が先頭に戻った場合、移動先未検出で処理終了
            If Not found AndAlso _
               nextCellAddress.X <= currentCellAddress.X AndAlso _
               nextCellAddress.Y <= currentCellAddress.Y Then
                Return False
            End If
            currentCellAddress = nextCellAddress
        Loop
        Return True
    End Function

    ' 右方向へのカレントセルの移動
    Private Function TabToNextCell(ByVal keyData As Keys) As Boolean
        ' 行・列がなければ移動先なし
        If Me.GetFirstColumnIndex() = -1 OrElse Me.GetFirstRowIndex() = -1 Then Return False
        ' 移動先が見つからなかった
        If Not Me.CanTabToNextCell() Then Return False
        ' フォーカスを移動先に設定
        Return Me.TabToCell(keyData)
    End Function

    ' タブによる左方向の移動先を検索する(GetNextTabPosition参照)
    Private Function GetPreviousTabPosition(ByVal currentCellAddress As Point) As Point
        Dim previousCellAddress As Point = New Point(-1, -1)
        Dim previousColumnIndex As Integer = -1

        If Me.CurrentCellAddress.X <> -1 Then
            Dim column As DataGridViewColumn = Me.Columns.GetPreviousColumn(Me.Columns(currentCellAddress.X), _
            DataGridViewElementStates.Visible, DataGridViewElementStates.None)
            If Not column Is Nothing Then
                previousColumnIndex = column.Index
            End If
        End If
        If previousColumnIndex = -1 Then
            previousCellAddress.X = Me.GetLastColumnIndex()
            previousCellAddress.Y = Me.Rows.GetPreviousRow(currentCellAddress.Y, _
                                    DataGridViewElementStates.Visible)
            If previousCellAddress.Y = -1 Then
                previousCellAddress.Y = Me.GetLastRowIndex()
            End If
        Else
            previousCellAddress.X = previousColumnIndex
            previousCellAddress.Y = currentCellAddress.Y
        End If
        Return previousCellAddress
    End Function

    ' 左方向のタブ移動先が見つかるか判定する
    Private Function CanTabToPreviousCell() As Boolean
        Dim found As Boolean = False
        Dim currentCellAddress As Point = Me.CurrentCellAddress

        Do While (Not found)
            Dim previousCellAddress As Point = Me.GetPreviousTabPosition(currentCellAddress)
            If TypeOf MyBase.Rows(previousCellAddress.Y).Cells(previousCellAddress.X) Is ISupportTabStop Then
                Dim tabstopcell As ISupportTabStop = _
                DirectCast(Me.Rows(previousCellAddress.Y).Cells(previousCellAddress.X), ISupportTabStop)
                If tabstopcell.TabStop Then found = True
            Else
                found = True
            End If
            If Not found AndAlso _
               previousCellAddress.X >= currentCellAddress.X AndAlso _
               previousCellAddress.Y >= currentCellAddress.Y Then
                Return False
            End If
            currentCellAddress = previousCellAddress
        Loop
        Return True
    End Function

    ' 左方向へのカレントセルの移動
    Private Function TabToPreviousCell(ByVal keyData As Keys) As Boolean
        If Me.GetFirstColumnIndex() = -1 OrElse Me.GetFirstRowIndex() = -1 Then Return False
        If Not Me.CanTabToPreviousCell() Then Return False
        Return Me.TabToCell(keyData)
    End Function

    ' カレントセルの移動
    Private Function TabToCell(ByVal keyData As Keys) As Boolean
        Dim found As Boolean = False
        Dim currentCellAddress As Point = Me.CurrentCellAddress

        ' 移動先のセルが見つかるまで(found=Falseの間)ループ
        Do While (Not found)
            ' DataGridView既定のタブキー処理を行う(カレントセルの移動)
            Dim processed As Boolean = MyBase.ProcessTabKey(keyData)
            ' 移動先がない(末尾のセル)場合処理終了
            If Not processed Then
                ' カレントセルを移動前のセルに戻す
                MyBase.Rows(currentCellAddress.Y).Cells(currentCellAddress.X).Selected = True
                Return False
            End If

            If TypeOf Me.CurrentCell Is ISupportTabStop Then
                ' TabStopプロパティが実装されている場合、プロパティ=Trueなら移動先到達
                Dim tabstopcell As ISupportTabStop = DirectCast(Me.CurrentCell, ISupportTabStop)
                If tabstopcell.TabStop Then Return True
            Else
                ' TabStopプロパティが実装されていない場合、その時点で移動先到達
                Return processed
            End If
        Loop
        Return False
    End Function

    ' DataGridView での移動に使用されるキーを処理をオーバーライドする
    Protected Overrides Function ProcessDataGridViewKey(ByVal e As System.Windows.Forms.KeyEventArgs) As Boolean
        ' TabかShift+Tabの場合
        Select Case e.KeyData
            Case Keys.Tab, Keys.Enter
                If Me.TabToNextCell(e.KeyData) Then
                    Return True
                Else
                    ' 次のセルが見つからない場合、Tab+Cntl で次のコントロールにフォーカスを移す
                    Return MyBase.ProcessDialogKey(e.KeyData Or Keys.Control)
                End If
            Case Keys.Shift Or Keys.Tab
                If Me.TabToPreviousCell(e.KeyData) Then
                    Return True
                Else
                    ' 次のセルが見つからない場合、Tab+Shift+Cntl で前のコントロールにフォーカスを移す
                    Return MyBase.ProcessDialogKey(e.KeyData Or Keys.Control)
                End If
        End Select
        ' 上記以外は既定の処理へ
        Return MyBase.ProcessDataGridViewKey(e)
    End Function

    Protected Overrides Function ProcessDialogKey( _
            ByVal keyData As Keys) As Boolean
        'Enterキーが押された時は、Tabキーが押されたようにする
        If (keyData And Keys.KeyCode) = Keys.Enter Then
            Return Me.ProcessTabKey(keyData)
        End If
        Return MyBase.ProcessDialogKey(keyData)
    End Function
End Class

かずくん
ぬし
会議室デビュー日: 2003/01/08
投稿数: 759
お住まい・勤務地: 太陽系第三惑星
投稿日時: 2007-10-18 09:26
長すぎて、コード追ってないけど、

その行、列、セルのどこかがReadOnlyになってない?

または、どのようにデータを流し込んでるか分からないけど、
デーセットデザイナを通して作ったDataSetから流し込んでいるのなら、
DataTableの列定義がReadOnlyになってない?
とある根性なし
ベテラン
会議室デビュー日: 2006/08/15
投稿数: 54
投稿日時: 2007-10-18 10:04
レスありがとうございます。

ソースやっぱり長いですよね。
載せようか悩んだんですが^^;
私以外にも DataGridView に TabStop を作りたい方がいらしたら、
ソースは多いほうが参考になるかと思いまして。


データは Rows.Add(Boolean) で直接流しています。
ReadOnly については、
行列セルともに明示的に False をセットして確かめてみましたが、
やはりダメでした。

引き続きよろしくお願いいたします。
とある根性なし
ベテラン
会議室デビュー日: 2006/08/15
投稿数: 54
投稿日時: 2007-10-18 18:58
お世話になっております。

前述のソースについてですが、TabStop プロパティの追加を諦めました。
タブによる移動の時には、ReadOnly プロパティの値を元に移動させることにします。

上記ソースは正常に動作しない部分の抜粋なんですが、
実際にテストしているソースは ImageCell なども追加してテストしています。
ところがこの環境でテストしていると、
カスタマイズしないDataGridViewとCheckBoxCellで実行してもCheckBox が動作しなかったりして、
わけがわからなくなってしまいました。
ちなみに不良動作するのは ImageColumn の後ろに CheckBoxColumn を配置した場合です。
カスタマイズしない CheckBox を ImageCell以外の Column の後ろに置く場合は
ちゃんと動作してるんですけども><
ちなみに TabStopCheckBox は新しいプロジェクトを作成してテストしても動きませんでした・・・

残念ですがこれで閉めます。
ありがとうございました。


[ メッセージ編集済み 編集者: とある根性なし 編集日時 2007-10-18 19:01 ]
1

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