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
|