第2回 DataGridViewコントロールでマインスイーパ連載:.NETグリッド・コントロール大研究(3/4 ページ)

» 2006年10月21日 00時00分 公開
[遠藤孝信デジタルアドバンテージ]

■DGVコントロールの初期設定

 フォームのLoadイベント・ハンドラではDGVコントロールの初期設定を行う。ここではまず行ヘッダ、列ヘッダを非表示にし、ユーザーがセルのサイズを変更できないようにしておく。

 Loadイベント・ハンドラでポイントとなるのは、ゲーム・フィールドの作成部分である。非連結モードでは次のように記述するだけで、sx個の列オブジェクトとsy個の行オブジェクトが作成される。従って最終的には、sx * sy個のセルが作成されることになる。

dgv.ColumnCount = sx
dgv.RowCount = sy

行数と列数の指定によるセルの作成(Loadイベント・ハンドラ内)

 このようにして作成される列オブジェクトは、最も標準的な列であるテキストボックス列(DataGridViewTextBoxColumnオブジェクト)だ。この列に属するセルは、そのValueプロパティにセットした値が文字列として表示される。

 ただし、これだけでは行や列のサイズがデフォルトのままである。Gridスイーパではセルを正方形にしたいので、上記の2つのプロパティを設定する前に以下のコードにより、まず追加される行の高さを指定する。

dgv.RowTemplate.Height = cellSize ' 追加される行の高さ

行テンプレートにおける行の高さの指定(Loadイベント・ハンドラ内)

 DGVコントロールに行が追加される場合には、そのRowTemplateプロパティにセットされている行オブジェクトが行のテンプレート(ひな型)となるため、ここで行のスタイルを調整しておけば、以降の行の追加時にはそのスタイルがすべての行で使用されるようになる。

 一方、列の幅に関してはこのような列テンプレートが存在しないため、次のようにして、すべての列に対してWidthプロパティを設定する必要がある。

For Each col As DataGridViewColumn In dgv.Columns
  col.Width = cellSize
Next

各列の幅の設定(Loadイベント・ハンドラ内)

 もちろん行の高さに関しても、すべての行についてループによりHeightプロパティを設定してもよい。以上により、正方形のセルが敷き詰められたフィールドの出来上がりだ。

■ゲーム・フィールドの初期化

 DGVコントロールの初期設定が終われば、次にinitGameメソッドでゲーム・フィールドの初期化を行う。

 マインスイーパでは、各セルについて少なくとも2つの状態を管理する必要がある。爆弾があるかないかと、すでに開かれたかどうか、である(もっとも、すでに開かれた爆弾のあるセルというのは、その時点でゲームが終了なので存在しない)。

 Gridスイーパでは、この状態を各セルのValueプロパティの値で管理してしまう。つまり、すべてのセルのValueプロパティをまず、まだセルが開かれていないことを示すUNOPEN(=-2)に設定する。これによりセルの表示がすべて「-1」となってしまうが、その背景色と前景色を同じにすることにより、それが表示されないようにしている。これを行っているのがinitGameメソッドの次のループだ。

For x As Integer = 0 To sx - 1
  For y As Integer = 0 To sy - 1
    dgv(x, y).Value = UNOPEN ' 開かれていない状態
    dgv(x, y).Style.ForeColor = Color.WhiteSmoke
    dgv(x, y).Style.BackColor = Color.WhiteSmoke
  Next
Next

すべてのセルの初期化(initGameメソッド内)

 前回でも説明したように、各セルはDGVコントロールのインデクサにより、

dgv(x, y)

の形で指定できる(ここで「dgv」はDGVコントロールのインスタンス)。

 クリックされ開かれたセルのValueプロパティには、そのセルの周りの爆弾の数を代入することになるのだが(爆弾がなければ0ではなく空文字を代入する)、そのときは前景色と背景色を変更することによりセルを開いたように見せているというわけだ。

 次に、乱数で爆弾の位置を決め、その位置のセルのValueプロパティをMINE(=-1)に設定する。

Dim rnd As New Random
Dim doneNum As Integer = 0

' 爆弾を配置し終えるまでループ
While doneNum < numMine

  ' 乱数で爆弾の位置を決める
  Dim x As Integer = rnd.Next(sx)
  Dim y As Integer = rnd.Next(sy)

  ' 爆弾がなければ爆弾を配置
  If CInt(dgv(x, y).Value) <> MINE Then
    dgv(x, y).Value = MINE
    doneNum += 1
  End If
End While

爆弾の配置(initGameメソッド内)

 セルのValueプロパティを設定する場合には、単に文字列や数値を代入すればよいが(表示時には文字列に変換されて表示される)、ValueプロパティはObject型なので、その値を取り出すときには、

CInt(dgv(x, y).Value)

のように、元の型にキャストする必要がある。

 ところで、この時点で爆弾を配置するのは本当は正しくない。これでは最初に開いたセルがいきなり爆弾というナンセンスな状況があり得るためだ。本来であれば1つ目のセルが開かれた後で爆弾を配置すべきなのだが、今回はそのような処理は割愛した。

 initGameメソッドでは、すでに開いたセルの数numCellOpenedを0にし、ゲーム中かどうかを示すフラグgameStartedをtrueに設定しておく。

■セルの選択を禁止

 DGVコントロールでは、通常のセルの値は、そのセルのスタイルのForeColorプロパティやBackColorプロパティで設定された色で表示されるが、グリッド上で選択されているセルについては、これらのプロパティの色ではなく、SelectionForeColorプロパティやSelectionBackColorプロパティの色が使用される。

 このためGridスイーパでは、セルが選択されたときにもセルに書き込んだ値(-1や-2)が見えないように、選択されたセルの色も同時に設定する必要がある。そうしないと次の画面のように答えが見えてしまう。

選択すると見えてしまうセルの値
選択されたセルの色は通常のセルの色とは別に設定されているため、その色も変更しておかないと、このような状態になってしまう。

 しかしこれらの設定はコードを煩雑にするので、今回はセルの選択自体をできなくしてしまっている(これによりフォーカスが移動しても、セルが選択されないようになる)。

 ただしDGVコントロールには、現在選択されているセルの選択を解除するためのClearSelectionメソッドが用意されているが、セルの選択を禁止するような機能は用意されていない*

* MultiSelectプロパティをFalseに設定すれば、複数のセルの選択は禁止できる。


 そこで今回は、選択されているセルが変化したときに発生するSelectionChangedイベントのタイミングでClearSelectionメソッドを呼び出して、セルが選択されてもすぐにそれを解除している。SelectionChangedイベント・ハンドラは次のようになる。

Private Sub dgv_SelectionChanged(ByVal sender As Object, ByVal e As EventArgs) Handles dgv.SelectionChanged
  ' セルを選択状態にさせない
  dgv.ClearSelection()
End Sub

セルが選択されないようにするSelectionChangedイベント・ハンドラ

 ClearSelectionメソッドの呼び出しにより現在選択されているセルが変化するが、このときは幸いにしてSelectionChangedイベントは発生しないようだ。

 ちなみに、選択されているセルをコードから設定するには、セルのSelectedプロパティをTrueに設定すればよい。蛇足になるが、SelectionChangedイベント・ハンドラでClearSelectionメソッドを呼び出した後にこれを行うとスタック・オーバーフローが発生してプログラムは停止してしまう。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。