このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
今日は、Excel の選択範囲を読み出す処理を行なう。
Excel のセル範囲は、Range というオブジェクトだ。
Range を制した者は Excel を制す(?)と言われるほど、
複雑怪奇で魑魅魍魎の多機能オブジェクトだ。
セルにアクセスするためにはこれを使い倒す必要がある。
では、PuzzleSheetLoader の実装をはじめよう。
まず、偽コンストラクタ周辺。
コンストラクタではあまり処理をしないようにしよう。
Private m_oAnswerRange As Range
Public Sub Construct(ByVal AnswerRange As Range)
If AnswerRange Is Nothing Then Call Err.Raise(5)
Set m_oAnswerRange = AnswerRange
End Sub
AnswerRange で解答欄の領域を受け取り、
それを Private フィールドに記憶させて終わり。
Nothing のチェックだけはしておこう。
VBA の Nothing は、Java や C# の null に相当する。
Call Err.Raise(5) で、引数が無効エラーを送出する。
つぎは、一番簡単な Width と Height だ。
これは Solve マクロを作ったときに説明したので簡単。
Public Property Get Width() As Long
Width = m_oAnswerRange.Columns.Count
End Property
Public Property Get Height() As Long
Height = m_oAnswerRange.Rows.Count
End Property
次に簡単なのは、Answer かな。やることは次の通り。
・ArrayList の ArrayList を作る
・セルの状態を CellStateConstants として取得する
セルの状態を取得するのは、専用のヘルパメソッドにしよう。
GetCellState と言う名前にして、
引数に単一のセルを表す Range オブジェクトを渡すと、
CellStateConstants が返るって寸法だ。
判断基準は簡単に以下のようにしよう。
・セルの背景色に何か色がついている
⇒塗りつぶしセルとみなす
・セルが空の場合
⇒未解決(解答前など)のセルとみなす
・セルに「×」か「□」と記入されている
⇒チェック(塗りつぶされない)セルとみなす
・その他
⇒取りあえず塗りつぶしセルとみなす
よし、書いてみよう。
Private Function GetCellState(ByVal Cell As Range) As CellStateConstants
If Cell.Interior.ColorIndex <> xlColorIndexNone Then
GetCellState = CELL_FILLED
ElseIf IsEmpty(Cell.Value) Then
GetCellState = CELL_UNSOLVED
Else
If Cell.Value = "×" Or Cell.Value = "□" Then
GetCellState = CELL_CHECKED
Else
GetCellState = CELL_FILLED
End If
End If
End Function
セルの背景は、Interior プロパティで取得できる、
Interior クラスのインスタンスが管理しており、
ColorIndex プロパティが、xlColorIndexNone であれば、
塗りつぶされていないことになる。
セルの値は、Value プロパティで取得できる。
Excel は、セルの内容に応じて、
文字列・数値・日付など型を自動的に判断するので、
Value プロパティは Variant 型だ。
セルに値が入っていない場合、Empty が返るのだが、
Empty は Variant 専用の特殊なキーワードであるため、
そのまま比較することはできない。
Empty かどうか調べるためには、IsEmpty 関数を使う。
因みに Empty は数値として評価した場合は 0、
文字列として評価した場合は空文字列 "" となるため、
Cell.Value = Empty という風に比較してしまうと、
数値 0 が入ったセルが、条件式を満たしてしまうのだ。
では、これを使ってAnswer を作る。
Public Property Get Answer() As ArrayList
Dim oRows As ArrayList
Dim oLine As ArrayList
Dim oCell As Range
Dim x As Long
Dim y As Long
Set oRows = New ArrayList
For y = 0 To Me.Height - 1
Set oLine = New ArrayList
For x = 0 To Me.Width - 1
Set oCell = m_oAnswerRange(y + 1, x + 1)
Call oLine.Add(GetCellState(oCell))
Next
Call oRows.Add(oLine)
Next
Set Answer = oRows
End Property
戻り値は ArrayList の ArrayList となるので、
まずは行の ArrayList を格納する ArrayList が必要。
各行は CellStateConstants の配列を持つ ArrayList だ。
なので、行を先にループさせる必要がある。
Range オブジェクトから単一のセルを得るには、
インデクサにより配列表記を使うのが楽だ。
これは既定のプロパティによる機能である。
インデクサによるアクセスは、範囲の左上のセルを基準として
2 次元の配列としての位置で考えることができ、
行も列も 1 から始まる番号でアクセスすることができる。
つまり、m_oAnswerRange(1, 1) は範囲の左上のセルだ。
今回は、内部で扱う配列を 0 ベースで統一しているので、
縦横共に 1 を加算してやる必要がある。
Range クラスは、無茶苦茶柔軟に作られているので、
単一のセルを取り出すには他にも色々方法があるが、
上記が一番理解しやすいアクセス手法ではないだろうか。
さて、後はヒントだ。