2006 年 5 月 10 日 19 時 5 分

PuzzleSheetLoader #2: セルの読み込み


このアーカイブは同期化されません。 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 クラスは、無茶苦茶柔軟に作られているので、
単一のセルを取り出すには他にも色々方法があるが、
上記が一番理解しやすいアクセス手法ではないだろうか。

さて、後はヒントだ。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.