.Netで選択肢を複数選べるコントロールを使いたかった。普通はListBoxでしょう。しかし、表示はしてるが、選択はできない、Enabled=Falseのような選択肢も用意しないといけないという要件。文字の色も変えないといけない。ネットで調べたけど、リストボックスではそれはできなさそう。
そこで、DataGridViewを使う方向でやってみた。かなり面倒だけど、それなりのことはできたようだ。枠線を消したり、行列のヘッダーを消すことで見た目をListBoxのようにすることはできる。問題は、複数選択するために、Ctrlキーを押さないで、マウスクリックだけでできること。普通、DataGridViewはCtrlキーを押しながらじゃないと、クリックした時点でそれまでに選択していたセルが解除される。そして、無効状態のというか、非活性として表示しているセルをクリックしても選択されないようにすること。
ネットで調べまくり、ある程度方針がわかった。前者に対しては、DataGridViewを継承したオリジナルのコントロールを定義し、そこでマウスの動作を取得して、Ctrlキーを押した状態にすること。そうすればマウスをクリックするだけで複数選択できる。後者は文字の色を変え、セルクリックイベントで文字色が無効状態の色の時は選択を解除する方法。なんとなくできそうだけど、具体的なソースコードまでは見つけられなかった。試行錯誤のすえ、1日以上かかった。
まずはオリジナルのDataGridView。WndProcメソッドをオーバーライドするだけなんだけど、WIn32APIを使うための宣言も。DataGridViewの上にマウスカーソルが来たらCtrlキーを押した状態、離れたらCtrlキーを離す。離すのを忘れるとプログラムを終えても押したままの恐ろしい状態になる。CtrlキーはSendKeyじゃだめだった。
Public Class MyDataGridView
Inherits DataGridView
Public Declare Sub keybd_event Lib "user32" (ByVal bVk As Integer, _
ByVal bScan As Integer, _
ByVal dwFlags As Integer, _
ByVal dwExtraInfo As Integer)
'Windowsメッセージを受け取るメソッドをオーバーライド
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case &H200 'DataGridView上をマウスが動いている時
'コントロールキーを押した状態にする
Call keybd_event(Keys.ControlKey, 0, 0, 0)
MyBase.WndProc(m)
Case &H2A3 ''DataGridViewからマウスが離れた時
'コントロールキーを離した状態にする
Call keybd_event(Keys.ControlKey, 0, 2, 0)
MyBase.WndProc(m)
Case Else
MyBase.WndProc(m)
End Select
End Sub
End Class
多分、ビルドしたらツールボックスに上記のコントロールが出てくるので、フォームに貼り付け。それに気づかない時は、プログラムでフォームにコントロールの追加でやってた。
オリジナルのDataGridViewを貼り付けたフォームに以下のプログラムを書く。
Imports System.ComponentModel
Public Class MyForm
'
'無効時のフォントカラー
Private DisabledColor As System.Drawing.Color = Color.Gray
Private Sub MyForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'ここではプログラムで列追加
Dim col As New DataGridViewTextBoxColumn()
MyDataGridView1.Columns.Add(col)
'DataGridViewの書式設定(ListBoxのように見せる)
MyDataGridView1.AllowUserToAddRows = False
MyDataGridView1.AllowUserToDeleteRows = False
MyDataGridView1.ReadOnly = True '編集状態にはさせない
MyDataGridView1.DefaultCellStyle.Font = New Font("MS UI Gothic", 12)
MyDataGridView1.RowHeadersVisible = False
MyDataGridView1.ColumnHeadersVisible = False
MyDataGridView1.CellBorderStyle = DataGridViewCellBorderStyle.None '枠線消す
MyDataGridView1.AllowUserToResizeColumns = False
MyDataGridView1.AllowUserToResizeRows = False
MyDataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect '行選択モード
'項目を追加
MyDataGridView1.Rows.Add(3)
MyDataGridView1.Rows(0).Cells(0).Value = "1つ目の項目"
MyDataGridView1.Rows(1).Cells(0).Value = "2つ目の項目"
MyDataGridView1.Rows(2).Cells(0).Value = "3つ目の項目"
'3つ目は無効状態に見立てる
MyDataGridView1.Rows(2).DefaultCellStyle.ForeColor = DisabledColor
End Sub
Private Sub MyForm_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
'最初の表示時は行を未選択にする(Loadイベントでは反映されない)
MyDataGridView1.CurrentCell = Nothing
End Sub
Private Sub MyDataGridView1_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles MyDataGridView1.CellFormatting
'無効の項目は選択を無効とする(ForeColorの色で判別)
If MyDataGridView1.Rows(e.RowIndex).DefaultCellStyle.ForeColor = DisabledColor Then
MyDataGridView1.Rows(e.RowIndex).Selected = False
End If
End Sub
End Class
不活性にする処理はCellEnterなどのCellFormatting以外のイベントでも試してみたけど、動きが変でCellFormattingに落ち着いた。これで多分目的は達成できている。実際には表示する選択肢の数や不活性にするかどうかは外部から与えて、動的にできるようにしたし、選択肢の数に応じてDataGridViewの大きさを変えるようにした。さらにこのフォームをコンボボックスのリストのように使うためにモードレスで別フォームから呼んで、選択した項目を呼び出し元で取得したりとか、もっと手間をかけている。
書いてみるとさっぱりしてるけど、一から調べて自分で作ると時間がいくらあっても足りんなー。これだって全体から見たら1%にも満たないもんなあ・・・。