見出し画像

個サ作 #13 演習問題:あみだくじ 1

こんにちは。

前回はカレンダーLv.5をクリアしましたね。

今回はあみだくじを作ります。早速参りましょう。

※今回から「今回のゴール」「今回のふりかえり」は省略します。


あみだくじ 前編

今回のあみだくじの作成、2回やります。異なるロジックで2回です。

一度目は私が伴走します。ただし、これは不完全なものを実装します。完成度70%くらい。二度目はあなた自身で組んでいただきます。もちろん、最後には答え合わせをします。

まずは完成形の動作です。

では作っていきましょう。


事前準備

まずがわを作ります。新しいモジュールを用意しましょう。

下図のようにしてください。モジュール名は「Study4」です。

そして、今回はシートも新しくします。下図を真似してください。

シートを追加している様子
  • シートを増やしてシート名を「あみだくじ」に変更

  • 全セルを選択し、列幅を「2.38」に設定する

  • 「表示」タブから「目盛線」をOFFにする

  • 縮尺を40%にする

上図のGif画像ではこれらのことをしています。縮尺は30%でも50%でも好きな数字にしてください。あくまで目安です。ちなみに、[Ctrl]キーを押下しながらマウスのホイールを回しても変えられます。

次に以下のソースコードを下図のように入力ください。

Sub I_あみだくじ1()

End Sub
Sub J_あみだくじ2()

End Sub
Sub K_あみだくじ一括実行()

End Sub
Sub L_あみだくじ初期化1()

End Sub
モジュール「Study4」の現在のソースコード。赤枠がないけど反映してね

はい。「Study4」のモジュールに4つのマクロを書きました。

まだあります。

シート上に実行用のボタンを用意しましょう。「実行1」「実行2」「一括実行」「クリア」ボタンの4つで、先ほど書いたマクロとの対応付けは・・・

  • 実行1・・・I_あみだくじ1

  • 実行2・・・J_あみだくじ2

  • 一括実行・・・K_あみだくじ一括実行

  • クリア・・・L_あみだくじ初期化1

となります。#3でお教えしましたボタンからマクロを実行するやり方です。下図の要領でボタンを用意してください。

マクロ実行用のボタンを作成する様子。1つ作ったらあとはそれをコピーすると簡単

上図の要領で4つ用意すると・・・

ボタンを4つ作り整列させた状態

こんな感じになります。

ボタンのラベル変更、上図のGif画像ではテキストの上をクリックしていますが、右クリックから「テキストの編集」を選んでもできます。

ボタンの文言変更をボタン上で右クリックから「テキストの編集」選択でやろうとする様子

ボタンの複数選択は[Ctrl]キーを押下したままで左クリックしていくとできます。これをすると一括で設定ができるのでオススメです。

ボタンの大きさはお好みで変えてOKです。大きさは

赤枠のところでボタンのサイズを変更できる

ボタンを選択した状態にすると「図形の書式」タブが現れるので、その中の「サイズ」の欄から変更できます。


では次が最後です。あみだくじ、書いちゃいましょう!

次の8つのルールを守っていただければあとは自由にしていただいてOKです。

  • 縦線は10本まで

  • 横線の数は自由

  • 一つの縦線につき、同じ位置から左右に伸びる線を書かない

  • 縦線はすべて開始と終了の高さを合わせる

  • 開始セル、終了セルから伸びる横線を書かない

  • 横線は必ず縦線と縦線を繋ぐこと

  • 辿ったとき、進行方向が上向きになる線は書かない

  • 線の色は下図のものとする

下図が実際に線を用意している様子です。一度灰色に塗ったセルがあれば後はそのセルをコピー([Ctrl] + [C]キー)するのが良いです。

作図する様子。最初の1本だけ塗色したら後はコピーするといい

縦線と縦線の間隔がまちまちでも問題ありません。

私は以下のようにしました。

完成形では線の始まりと終わりに人名や役割を添えて本当のあみだくじっぽくしているのですが、今は開発段階なので控えておきます。あれは開発とは関係のない要素なのでなくても問題ありません。

もしも用意される場合はセルに直接値を書くのではなく、下図のようなテキストボックスの挿入で設定してください。

くじに人の名前を割り当てたりゴールを隠すときは図形を使ってね


はい、これで事前準備は整いました!

これまでも各課題ごとに伝えたい技術や考え方がありました。順次処理や反復処理、条件分岐、自作関数、正規表現などがそれにあたりますね。

今回はちょっと抽象的ですが、「ロジックの組み立て方」をこの演習問題を通して習得いただけたらなと思います。見ていただいたらわかる通り、ただ数字を羅列するだけのカレンダーよりは少々複雑です。

「I_あみだくじ1」と「J_あみだくじ2」で同じ機能だけど異なるロジックで組む、というように説明しました。

先ほどもお伝えしましたが、「I_あみだくじ1」の方は私と一緒にやりましょう。「J_あみだくじ2」はご自身で考えていただきます。

それでは参りましょう。


モジュールレベル変数とは

早速新しい要素の登場です。

まず次のソースをモジュールStudy4の先頭に入力ください。

'実行するくじを表すセル
Private tryLotRange As Range
赤枠のソースコードを反映してね

これはモジュールレベル変数といいます。

これまで変数を宣言するときは必ずマクロ(Subプロシージャ)や関数(Functionプロシージャ)の中で行ってきました。ですが今回は、それらの外で宣言していますね。

モジュールの冒頭でマクロや関数の記載が始まる前の部分のことを宣言セクションと言います。そこに書いたものはそのモジュール内であればどこからでも参照できる変数になり、これをモジュールレベル変数といいます。

変数にはスコープ(参照可能範囲)という概念があります。

今回はいつも「Dim」と書いている場所に「Private」と書きましたが、これを「Public」にすると他のモジュールからも参照できる変数になります。

モジュールレベル変数の可視性のデフォルトはPrivateなので実を言うと

'実行するくじを表すセル
Dim tryLotRange As Range

このように書いてもまったく同じ意味になります。PrivateをDimにしています。ただ、Public or Private でその可視範囲を明示してあげた方が読み手に優しいと言えます。だからPrivateとしています。

上記の公式リファレンスの「プライベート モジュール レベルのスコープ」セクションから抜粋

ほら、公式も言ってる!


モジュールレベル変数を列挙する

宣言セクションで宣言される変数(=モジュールレベル変数)についての説明はここまででクリアしました。ここからは先ほど宣言した変数tryLotRangeの説明と、他にも必要になる変数を挙げていきます。


まずは変数tryLotRangeトライロットレンジ

なぜこの変数名にしたか、からいきましょう。私も英語は得意ではないのですが、調べてみるとくじのことを「Lot」というそうです。ロト6とかね。

でも「くじ」だけでは作図いただいたように複数あって「「くじ」と言ってもどのくじを指しているのかわからない」ということになります。

そこで検証対象のくじはそれがどの結果に紐づいているかを明らかにする、というニュアンスで「tryトライ」を付与しました。挑戦する、的なね。

で、最後についてる「Rangeレンジ」はセルを意味しています。データ型も「Range」でして、前回の#12でCells.~はRangeオブジェクトとして使える、という話をしました。

これで「tryLotRange」という名前から引こうとしているくじが辿るセルを管理する変数なのだと、解釈いただければOKです。余談だけど、変数名からその変数のデータ型が推測できるととてもよいです。今回はGood。


では、残りの変数を一気に列挙します。追記してください。

'塗り替える色
Private repaintColor As Long
'あみだくじ範囲の最初の行
Private firstRow As Integer
'あみだくじ範囲の最後の行
Private lastRow As Integer
'あみだくじ範囲の最初の列
Private firstColumn As Integer
'あみだくじ範囲の最後の列
Private lastColumn As Integer
'実行するくじの番号(左からカウント)
Private tryLotIndex As Integer
'最小行(拡張クリア処理で使用)
Private minRow As Integer
'最大行(拡張クリア処理で使用)
Private maxRow As Integer
赤枠のソースコードを反映してね

ひとつずつ、簡単に説明しますね。

'塗り替える色
Private repaintColor As Long

まずはこちら↑、repaintColorリペイントカラー 。あみだくじの動きを想像してもらえばわかる通り、あらかじめ灰色になっているセルを異なる色でなぞっていく、という動きをします。その塗り替える色の情報を保持する変数です。

色は数値で表されるのですが、今回IntegerインテジャーではなくLongロング型を使用しています。初出ですね、Longロング型。

公式リファレンスを一部引用しますと下記のようにあります。

整数型の変数は、-32,768 から 32,767 の範囲の 16 ビット (2 バイト) の数値として格納されます。

整数データ型

実はIntegerインテジャー型では表現できる数値の範囲に限界がありまして、色を表す数値がこの範囲を超えてしまうことがあるんですね。色ってめちゃくちゃ多いですから。そうなると実行時にエラーが発生します。

これを回避するためにより大きな数値をカバーしているデータ型を使用します。その大きな数値を表現できるのがLongロング型なんですね。

Integerインテジャー整数型せいすうがたで、Longロング型は長整数型ちょうせいすうがたと言います。

Long (長整数型) 変数は、-2,147,483,648 から 2,147,483,647 までの符号付き 32 ビット (4 バイト) 数として格納されます。

Long データ型

上記は公式リファレンスからの引用です。範囲が一気に拡がりました。

でね、この説明を聞くと「だったらIntegerインテジャー型の変数はすべてLongロング型にしておけば無用な心配がなくなるのでは?」と思われたと思うんです。

うーん。これを説明するとまた長くなるのですが、システムとして想定していない値が与えられたらエラーにするのが正解、というケースもあるんですね。なんでもかんでも受け入れるのが良いわけではない、と。

はい、次いきましょう。firstRowファーストロウ, lastRowラストロウ, firstColumnファーストカラム , lastColumnラストカラムの4つでデータ型はすべてInteger型です。

'あみだくじ範囲の最初の行
Private firstRow As Integer
'あみだくじ範囲の最後の行
Private lastRow As Integer
'あみだくじ範囲の最初の列
Private firstColumn As Integer
'あみだくじ範囲の最後の列
Private lastColumn As Integer

これはですね、下図をご覧ください。

それぞれ赤枠の縦線・横線が何列目・何行目かを保持する変数です。上図の場合、

  • 変数firstRowの値は10

  • 変数lastRowの値は40

  • 変数firstColumnの値は7

  • 変数lastColumnの値は43

という内容になります。じゃあどうやってその値を持たせるのか?は「初期処理関数」の項で解説します。


次です。tryLotIndexトライロットインデックスはInteger型。

'実行するくじの番号(左からカウント)
Private tryLotIndex As Integer

下図をご覧ください。

実行ボタンが押下されたとき、何番目のくじが実行対象なのか?を保持する変数です。これがわからないとプログラムさんはどこから始めていいかわかりませんからね。


次が最後です。minRowミンロウ, maxRowマックスロウです。minはminimumミニマムの略で最小、という意味があります。

'最小行(拡張クリア処理で使用)
Private minRow As Integer
'最大行(拡張クリア処理で使用)
Private maxRow As Integer

実はこちらはあみだくじの実装では使いません。なので説明は割愛させてください。あみだくじ課題の終盤で少し触れます。お楽しみに。

ここまででモジュールレベルで宣言する変数の列挙が終わりました。お次は新しく出てくる要素「定数」です。

前段として、「変数」というのは変わる数・変えられる数、なんですよね。変数初出の#4でもそのように説明しました。

ですので、処理の中で値が変わったり、実行のたびに値が異なったりと、値が不定(=定まらない)であることを前提とした要素になっています。

では「定数」とはなにか。


定数とは

「定数」とは文字通り、定まった数(値)です。定まっている、ということは変わらないんですね。一度定義したら値が変わりません。変えようとするとエラーが発生します。

定数は処理中はもちろんのこと、何度実行しても同じ値です。

今回はこれを2つ使用するので宣言の仕方と使い方、どんな性格の値だったら変数ではなく定数として宣言するのかを覚えてください。


定数の宣言

以下のソースを宣言セクションに追記してください。

'塗り替えられる前のくじセル色
Const YET_TRY_COLOR As Long = 12566463
'塗りつぶしなしのセル色
Const NO_COLOR  As Long = 16777215
赤枠のソースコードを反映してね

では解説です。

まず、変数だったらDimなりPublicなりPrivateだった箇所に「Constコンスト」という単語を使用しています。これは「Constantコンスタント」の略で意味はそのまま「定数ていすう」です。

次に来るのが定数名です。今回は「YET_TRY_COLORイェットトライカラー」と「NO_COLORノーカラー」を定数名としました。

英単語の「Yetイェット」は「まだ」という意味があります。「Try」はさっきの説明からもくじを引く、の意味と(ここでは)しています。「Color」はご存じ「色」ですね。

ちょっと強引だけど、「まだ」に「未実行」のニュアンスがあるので、「まだ引いてないくじの色」という意味でこの定数名です。

NO_COLORノーカラーの方は色がない、という意味なので、後で出てくる図と併せて何を指しているかわかると思います。

さて、定数の登場に際してみなさん気になるのがその表記法ひょうきほうでしょう。

定数はアッパースネークケースで書いています(アッパースネークケースとは単語を構成するアルファベットがすべて大文字で単語ごとにアンダーバーで繋いでいるもののこと)。

これは言ってみれば慣例かんれいです。こういう風に書くことが多いです。なぜかというと、あらかじめ定数はこのように書く、という決め事があればソースコード中に登場したとき、定数だと直感的にわかるからです。

あぁ,書き換えちゃいけない値なのね、と瞬時に理解できます。かと思えば以下の公式リファレンスを少しのぞいてみてください。

接頭辞に「con」をつけるという手法で書いてある、なるほどね

2つの定数を宣言する例を載せていますが、どちらも「con」から始まっています。言わずもがな、「const」の「con」ですね。これも読み手に定数だよ、と伝えるためのテクニックです。

まあわかりゃなんでもいいんです。一応他サイトも貼って↓おきましょう。

慣例であることを示すキャプチャ。画像クリックでサイトを開くよ

で、説明に戻りまして・・・

'塗り替えられる前のくじセル色
Const YET_TRY_COLOR As Long = 12566463
'塗りつぶしなしのセル色
Const NO_COLOR  As Long = 16777215

定数名の次に来ているのが、データ型ですね。「As Long」の部分。実はここは省略できます。私も普段は省略してて、定数名に直接代入するように書いた方が自然ですよね。データ型に代入してる見た目、違和感だから。

ただまあデータ型を書けるんだぜ、という学習の意味で今回は付けました。省略できる、というのは#4で説明した暗黙的型変換みたいに、VBAが代入値から型を推定すいていしてくれるからです。

そして最後に値の代入ですね。定数の場合、宣言と同時に値を設定します。そしてこれ以降に値を変更することはできません

では今回定義した「YET_TRY_COLOR」と「NO_COLOR」がなにを指しているのか?も把握しておきましょう。

定数YET_TRY_COLORとNO_COLORが示す色を説明したい図

はい、塗り替えする前のくじを示す線を「YET_TRY_COLOR」、なにもセルに色を設定していないセルを「NO_COLOR」としています。

で、今回この定数は

'塗り替えられる前のくじセル色
Const YET_TRY_COLOR As Long = 12566463
'塗りつぶしなしのセル色
Const NO_COLOR  As Long = 16777215

このように定義しているのですが、「12566463」と「16777215」という数字は一体どこから出てきたんだと、思われますよね。

次で解説します。


開発用Subプロシージャ

とある色がどんな数値で表現されているかを知りたい、そういう時はそれを調べる用の簡易マクロを作って実際に実行するという手をよく使っています。

次のマクロをどこでもいいので、書いてください。

Sub a()
    Debug.Print Selection.Interior.Color
End Sub
関数aを実装している様子。赤枠のソースコードを反映してね

このソースが何をしてるかと言いますと、まず「Debug.Print」はもうご存じですね。その後に続く値をイミディエイトウィンドウに書き出す、というものです。

では次の「Selection」。これは今ワークシート上で選択しているオブジェクトを指しています。今回はセルを想定しています。そしてその後は「Interior.Colorインテリアカラー」と書いていますね。これはセルの背景色を示すプロパティです。

ではですね、グレーセルと塗りつぶしなしのセルを選択した状態で、それぞれこのマクロを実行してみましょう。

選択セルの色を確かめている様子。マクロaにカーソルがある状態で実行ボタンを押している

はい、いかがでしょう。それぞれの定数に代入している値がイミディエイトウィンドウに出力されましたね。こういう風にして欲しい値を手にすることができます。

で、用が済んだらマクロaは消せばいいのですが、この後でもう一度出番があるので、まだ置いておいてください。

これまでに「変数名は丁寧につけてほしい」ということを伝えてきているのですが、さすがに開発用に一時だけ使うようなものにまで気を配っていると疲れるのでこんなときは「a」など適当なものでOKです。


はい、これで宣言セクションで用意する変数と定数が揃いました。あみだくじ実装の次のステップに参りましょう。


初期処理関数

今の段階でどんな風にこのあみだくじ処理を組み立てるか、おぼろげながらでも想像はついているでしょうか。

このあみだくじをプログラムに実行させようとすると、まず基本的な情報を与えないといけませんね。先ほどまでの項で宣言した変数たちの値です。

そのために初期処理を行います。文字通り、最初に行う処理。

今回は2つあみだくじのマクロを作りますが、この初期処理に関しては共通になります。

共通になる、ということはマクロ(Subプロシージャ)の中に書かないで、初期処理を担当する関数を用意し、それぞれのマクロから呼び出す、という形をとります。


さて、回りくどくてすみませんが、モジュールレベル変数への値設定よりさらに前にしておきたいことがあります。これが無事に済んでいないなら値の設定をするべきではない、という代物しろものです。

カレンダーでもやってきたのでもうお分かりですね。そう、エラーチェックです。

ユーザが「実行」ボタンを押下したとき、ちゃんといずれかのくじの開始位置が選択されているのか?それをチェックしないといけません。

これ↓は完成形のソースコードで実行する様子ですが、

それぞれスタート地点を選択した状態で実行ボタンを押下している様子

いずれも、くじの先頭セルを選択した状態でボタンを押下しています。これが正しく実行するパターンですが、そうでないパターンもあるんですね。

その場合は、「おかしいですよ」と教えてあげましょう。

まずはがわを作ります。

'あみだくじ実行の初期処理
Function init()

End Function
赤枠のソースコードを反映してね

これまでは関数を作ったとき、与えられた引数を使って処理を行いましたが、引数は不要なら省略もできる、という話でしたね(#9にて)。

また、今回はモジュールレベル変数としたことで、引数として受け取らなくても外部で宣言された変数を使用できる、という特徴もあります。

関数名は「initイニット」としました。これはですね、コーディングの通例みたいなものでして、初期処理はだいたい「init」もしくは「initializeイニシャライズ」を使います。

「initialize」には「初期化する」という意味がありますから、最初に行う処理の関数名によく用いられます。「init」は「initialize」の略ですね。


今回のエラーチェックは3つあります。正しい選択位置から処理を開始したい、という意図は共通しますが、そのためにはいくつかの観点からチェックする必要があるんですね。

次、参りましょう。


エラーチェックの実装だどん

では、

くじの開始位置付近だけを抜粋したキャプチャ。これだけ見るとなんじゃこりゃ感

こういうくじの開始位置の並びがあったとき、どんなチェックをすれば正しい選択だった時のみ処理を開始させられるかを考えてみましょう。

  • 選択セルがグレーセルであること

  • 選択セルが単一であること

  • 選択セルがあみだくじ範囲における1行目であること

はい、もう答えです。これらが今回考慮すべき開始条件です。

 

グレーセルというのは先ほどやった定数「YET_TRY_COLOR」の値で表される色のセルですね。

単一セルの選択というのはエクセルって複数セルの選択ができるじゃないですが。あのドラッグした状態。そうではなくてひとつのセルだけが選択されているかどうか?をチェックします。


ではひとつずつソースコードに落とし込んでいきます。その前の前提も併せてやりましょう。


セルの背景色チェック

まずは以下のソースコードをinit関数の中に書いてください。

    '実行くじの番号初期値
    tryLotIndex = 0
赤枠のソースコードを反映してね

何番目のくじが実行されたのか?を示す変数の初期値設定です。ここではまだ何も設定されていないので「0」を設定します。

ここで「0」を設定する理由は以下のが2つあります。

  • エラーチェックで使用するから(次回#14で詳細に触れます)

  • モジュールレべル変数は実行時の値が実行後も残るから

モジュールレベル変数は一度マクロを実行した後、値が変数に残り続けるので、明示的に初期値を入れてあげる必要があるんです。


続いて、以下のソースコードを追記ください。

    'スタート位置のセルを設定
    Set tryLotRange = Selection
赤枠のソースコードを反映してね

この処理で変数tryLotRangeに今選択されているセルを代入します。さっき関数aで登場したSelectionセレクションです。代入するものがオブジェクトである場合は先頭に「Set」をつける、という説明は#12でしましたね。

この変数tryLotRangeがもっているセルに関する情報(プロパティ)をここからの各エラーチェックで評価していきます。おまえは実行するに足る情報なのかい?どうなんだい?ということです。

ではまずはセルの背景色チェックです。

くじを赤線で囲った図。赤線の中が選択されていたらOK、そうでないならNG

上図のうち赤線の中を選択している状態ならOK、それ以外を選択していたらNG、とするチェックを行います。

先ほど、定数 YET_TRY_COLOR と NO_COLOR に設定する値を用意する件で

Sub a()
    Debug.Print Selection.Interior.Color
End Sub

こんなソースを使いましたよね。なので、セルの背景色を示す値にアクセスするにはこの「Interior.Colorインテリアカラー」を使えばいい、という点はすんなりご理解いただけるでしょう。

では実装です。開始位置として適切なセルが選択されているかを背景色の観点からチェックする条件式は以下のソースコードを追記ください。

    If tryLotRange.Interior.Color = NO_COLOR Then
        MsgBox "あみだくじの開始位置を選択してください。"
        Exit Function
    End If
赤枠のソースコードを反映してね

このようになります。条件式の左辺が今選択中のセルの背景色右辺が塗りつぶしなしのセルの背景色です。それが等しかったら開始位置として不適切な場所が選択されていると、そういう理屈ですね。

ここで動作確認もしちゃいましょうか。マクロ「I_あみだくじ1」に以下を追記してください。

    Call init
init関数を呼び出す処理を実装した状態。赤枠のソースコードを反映してね

これまで関数を呼び出してきたときってね

        If isDayWritable(year, month, day) Then

(↑カレンダーLv.4, 5)とか

Dim year As Integer: year = getRegexpFirstHit(Cells(1, 6).Value, "^\d{4}(?=年)")

(↑カレンダーLv.5)と、関数が返してくる値が必ずありました。でも戻り値なしというパターンもありなんです。

今回、init関数に引数がないという点について、わざわざ渡さなくてもモジュールレベル変数として宣言しているから関数側からアクセスできる、という話をしましたね。

これは戻り値にしても同じ話で、戻り値としてマクロ側で受け取りたい値もモジュールレベルで宣言した変数に関数側で値を代入すればわざわざ戻さなくてもマクロ側でその値を使用することができます。

ですので、戻り値がない、すなわち何も受け取る必要がない関数を呼び出すときは「呼び出す」の文字通り「Callコール」を関数名の前につけます。

    Call init

こっちの方が呼んでます感がありますね。

また、引数がないときは後ろに括弧を付ける必要がありません。init()ではなくinit(ただし、プログラミング言語によっては引数無しでも括弧は要ります)。

では動かしてみましょう。成功パターン, エラーパターンどちらもやります。成功パターンはなにも起こらない、が期待値です。

背景色チェックを動作確認する様子

OKですね。次いきましょう。


単一セル選択チェック

エクセルでは複数セルを同時に選択することができますが、これを許してしまうとくじの色塗りを進める処理の時に支障があるので、事前に弾く仕組みを実装します。

単一選択と複数選択を図示したもの

参考までに、上図が単一選択状態と複数選択状態の違いです。

前項でやったセルの背景色チェックでも変数tryLotRangeがプロパティとしてもつ背景色情報にアクセスしました。もう察しがつきますかね。

今回も同様に変数tryLotRangeがプロパティにもっている今いくつのセルが選択されているか?の情報にアクセスします。

では実装しましょう。下記を追記ください。

    If tryLotRange.Cells.count > 1 Then
        MsgBox "セルの選択は単一にしてください。"
        Exit Function
    End If
選択セル数のチェックを実装した状態。赤枠のソースコードを反映してね

こちらです。変数tryLotRangeに続けて「.Cells.count」とすることで選択中のセル数を返します。「Cells.」で「セルの」と読み取れるので「count」をそのまま「数」と読むとわかりやすいですね。

で、その値が1よりも大きい数字だったら複数のセルが選択されていると判断できます。こちらも動作確認してみましょう。

前回同様、正常時は何も起こらない、が期待値です。

はいOK。グレーセルが選択セルの最初、最後、途中にくるパターンを試しているのは、その場合、色の方はどう判定されるのかなと思ったからです。

確認したところ、複数セルを選択した場合の.Interior.Colorの値は0でした。なるほど・・・。

選択セル数のチェックは正常に動きましたね。1マスだけ選択した場合は何も起きませんでした。

では次いきます。


選択位置1行目チェック

ここまでくるとさらに察しがついているでしょう。

選択中のセル情報を保持する変数tryLotRangeトライロットレィンジ氏・・・ちゃんともってます。今選択中のセルが何行目にあるかを管理するプロパティがあるんです。

と、なると今回のポイントは

あみだくじの開始位置を先端から末端までドラッグしている状態

このドラッグしている行は何行目なのか?をプログラムが動的に取得することです。「動的」とは状況に応じて取得する、ということです。

さて私、あみだくじを作図するときにこう言いました。

次の8つのルールを守っていただければあとは自由にしていただいてOKです。

太字はここでのみ装飾しています

そして、その8つのルールの中に〇行目から始めて〇行目で終わってくれ、というものはありませんでした。ですから10行目から始めても100行目から始めても1000行目から始めてもいいんです。

作図いただいたあみだくじの開始行を取得するためにはまずどこからどこまでの範囲に作図されているのか?を知る必要があります。

そして便利なものが用意されているんですわ!前出の関数aが残っていますよね。彼をですね、以下のようにしてください。

Sub a()
    'Debug.Print Selection.Interior.Color
    ActiveSheet.UsedRange.Select
End Sub
元あった背景色値出力のソースをコメントアウトし、新たに1行追加する

元あったセルの背景色を出すソースコードをコメントアウトし、新たに1行追加しています。説明の前にまずは動作確認してみましょうか。

関数aを実行する様子。実行した瞬間、あみだくじの範囲が選択されている

はい、いかがでしょう。あみだくじの範囲が選択状態になりました。

ActiveSheet.UsedRange.Select

この1行のうち、最後の「Select」はカレンダーのときから使いまくってるものです。選択状態にする、という命令ですね。

今回のポイントはその前にある「ActiveSheet.UsedRange.」です。「UsedRangeユーズドレィンジ」は直訳すると使用されている範囲、ですね。そうです、これは値を入れたり書式を変えたりしたセルを範囲で返してくれるプロパティです。

ではその前の「ActiveSheetアクティブシート」は何か。これはですね、「UsedRangeユーズドレィンジ」はシートの情報を明示的に指定してあげないと使いない、という性質があるのでこれに基づいてつけています。

ActiveSheetアクティブシート」が示すのは現在開いているシートです。

というわけで、ここまでくるとこの↓ソースコードで

ActiveSheet.UsedRange.Select

選択された範囲内の最初の行が何番目かを調べるとそれをそのままエラーチェックに使うことができます。

実装しましょう。下記を下図のように実装ください。

    firstRow = ActiveSheet.UsedRange.Item(1, 1).Row
    If tryLotRange.Row <> firstRow Then
        MsgBox "あみだくじの開始位置を選択してください。"
        Exit Function
    End If
現在のinit関数の全体像。バランス的にプロジェクトウィンドウも撮ったよ。赤枠のを反映してね

If文の前にモジュールレベル変数として宣言した変数firstRowになにかを代入していますね。これこそがあみだくじ開始行です。

    firstRow = ActiveSheet.UsedRange.Item(1, 1).Row

としていますが、「ActiveSheet.UsedRange」によって選択された範囲も結局のところセルの集合体でしかないので、これまでやってきた「Cells(行, 列)」みたいな書き方でその中にアクセスします。

今回は「Item(1, 1)」としているので、選択範囲内の1行目1列目のセルの「.Row(行)」を取得しています。CellsではなくItemと書いてます。

選択範囲内の1行目1列目を示す図

上図の場合なら変数firstRowには「10」の値が代入されます。あなたの作図によっては9だったり11だったり、様々です。

さて、変数firstRowの値がはっきりしたところで条件式を見てみましょう。

    If tryLotRange.Row <> firstRow Then
        MsgBox "あみだくじの開始位置を選択してください。"
        Exit Function
    End If

まずこの項の冒頭で変数tryLotRangeには現在何行目が選択されているかを示すプロパティがある、という話をしました。それが上記ソースにもある「.Rowロウ」というプロパティです。

それを変数firstRowと比較しており、式に使う演算子は「<>」です。カレンダーで作ったisDayWritable関数ぶりの登場ですね(#10)。これは等しくないかどうかを検証する比較演算子ひかくえんざんしです。

等しくないかどうか、なので等しくなければTrue等しければFalseという結果になります。

今回であれば等しくないということは開始行が選択されていない、つまりNGケースなので、Trueとしてエラーを発生させます。

それではお約束の動作確認です!例によって正常ケースは何も起きない、が期待値です。

開始位置を選べばなにも起きず、くじの2行目を選択しているとエラーが発生する様子

はい!正常動作しています。これでエラーチェックの実装は完了しました。

結構便利なプロパティが用意されていて、それらを上手に利用すればどうとでもなるんだ、という感想ですね。

この調子で次行きましょう!

・・・と、言いたいところですが、今回はここまでです。

次回は本格的な実装に進んでいきましょう。


今回の補足

ひとつめの内容は少し難しいかもしれません。補足は補足なので、よくわからなくても大丈夫です。

値渡しと参照渡し

今回、モジュールレベル変数というものが初めて出てきましたが、すんなりと理解できましたでしょうか。もしも違和感を残している方はこちらの説明を読んでみてください。

少し逸れた説明をしますが、最後はモジュールレベル変数の補足になるのでご容赦を・・。

これまでに関数をCallコールするときに引数を渡す、という処理を何度か書いてきていますね。実は引数の渡し方には2つ種類があります。それが

  • 値渡あたいわた

  • 参照渡さんしょうわた

です。次のソースコードをモジュールStudy4の最下部に追記しましょう(見てるだけでも大丈夫です。あとで消すソースですので)。

Sub I_引数の渡し方検証()
    Dim value As Integer: value = 10
    Debug.Print value
    Call 値渡し(value)
    Debug.Print value
    Call 参照渡し(value)
    Debug.Print value
End Sub
Function 値渡し(ByVal argValue As Integer)
    argValue = argValue * 10
End Function
Function 参照渡し(ByRef argValue As Integer)
    argValue = argValue * 10
End Function

これ、何をしてるソースかというと最初に宣言した変数valueの値を3度にわたってイミディエイトウィンドウに出力しています。

  • 一度目は変数定義したばかりの状態

  • 二度目は関数「値渡し」を経た状態

  • 三度目は関数「参照渡し」を経た状態

です。関数「値渡し」と「参照渡し」が登場しますが、どちらも戻り値はありません。それぞれ同じ処理をしていますが、異なる点は引数名の前に記載した「ByValバイバル」と「ByRefバイリフ」だけです。一度動かしてみましょう。

マウスカーソルが見てほしいところを案内しています。

さていかがでしょう。参照渡しを経た3度目だけ変数valueの値が100になっています。この挙動を例に値渡しと参照渡しを説明します。

関数へ値を渡すときの値渡し。これは値をコピーしたものを渡します。引数として受け取った値を関数が処理するわけですが、受け取ったのは呼び出し側にある変数の複製なので、呼び出し元の値はなにも影響を受けません。

次に参照渡しです。

参照渡しを説明するにはまず変数が値を持つということは内部で何が起きているか?をイメージいただかねばなりません。#4の説明で変数について

初学者向けの参考書ではよく「箱」と表現されます。値をもっておく、いれておくから箱です。中身を入れ替えること、何も入れないこともできます。

個人サイトの作り方 #4より

このようにお伝えしました。コンピュータのメモリ内のどこかに箱を用意して値を入れるんですね。変数を宣言した段階でひとつ箱が誕生しましたよ、と。

でね、厳密には変数というのはこの箱ではないんですよ。そう説明した方がわかりやすいから初学者には便宜的に箱と言っていますが実態はちょっと違います。

ではなにかというと変数がもっているのは箱への参照です。下記のページが図解を交えてこのことを説明してくれているので、よかったら見てみてください(第三者様のサイトです)。

「箱」と呼んでいるのも厳密にはコンピュータのメモリ上のある領域のことなんですね。

ここまでくると関数へ変数を参照渡しする場合の挙動について、想像がついたかもしれません。

値渡しだったら「ほい、値だよ」と言って値を渡す(内部的にはコピーが働く)のですが、参照渡しは呼出し元から関数に向けて「きみもあの箱を見てくれ」と参照情報を渡します。

つまり、呼び出し元と関数が見てる箱は同じものなんです。ということは当然、関数でその箱の中身を変えると呼び出し側も影響を受けますね。同じ箱を見てるわけですから。

このプログラミングのルールを再現したのが先ほどもお送りした・・・

マウスカーソルが見てほしいところを案内しています。

こちらです。戻り値はないのに3度目の出力は100になっています。関数側で100が代入され、呼出し元でもその箱を見にいっているからこのようになる、ということですね。

ここまでくるとモジュールレベル変数の理解も簡単かと思います。

モジュールレベルで宣言した変数(=用意した箱)を各マクロや関数で使うとき、どこから使おうともみんな(あらゆる場所に書いてる変数)同じ箱を見にいくという動きを内部でしているんですね。

さて、もう一つだけ補足です。これまで引数を用いる関数を使うとき、「ByValバイバル」も「ByRefバイリフ」も指定しませんでした。しかしこれにも当然デフォルトは用意されています。どちらでしょう。

上記のリンク先ページを一部抜粋したもの

こたえはByRefバイリフです。

このデフォルトはプログラミング言語によって異なるのでご注意ください。

ByValバイバルのValはValueバリューの略、ByRefバイリフReferenceリファレンスの略です。

この項、めちゃめちゃプログラマ的なお話ですね。

ソースコードを追記しておためしいただいた方、そのソースコードは削除するかコメントアウトしておいてください。私は削除します。


UsedRangeの仕様

今回、実行時のセル選択位置があみだくじ範囲の1行目かどうかを確認するエラーチェックのところでUsedRangeを使用しました。

本編ではサクッと進みましたが、注意点があるので解説しておきますね。

まず、もう一度下図を見てください。

関数aを実行する様子。実行した瞬間、あみだくじの範囲が選択されている

UsedRangeが示す範囲を選択状態にする処理です。あみだくじ範囲を綺麗に選択してくれるからすごく都合よく話が進んだ感じがしますね。

ですが、こちらの意図を汲み取って選択してくれたわけではありません。このUsedRangeプロパティは使用範囲を示す、というものです。

ポイントは何をもって「使用」とするかですね。セルに値を入れたり装飾したり、書式を変更することが「使用」にあたります。

ためしに下図をご覧ください。

最初の関数a実行は[F5]キーの押下でしています
  • 上のセルには入力形式変更

  • 右のセルには文字色変更

  • 左のセルには値入力

  • 下のセルには罫線を設定

を実施しました。

そして関数aを実行するとどうでしょう。選択範囲が上下左右とも広がりましたね。UsedRangeプロパティはなにかしら変更が加えられたセルを上下左右それぞれ最も端のものまで入るように四角で囲んだ範囲を示します。

これが意図しないところまで範囲として判定されている場合の対処策を伝えておきます。先ほどの続きをご覧ください。

あみだくじの上が1行減ってしまったので、あとで1行追加しました。

はい、それぞれ対処した後にもう一度関数aを実行すると無事、あみだくじをぴったり囲む形に戻りましたね。

これ、身に覚えはないのに何かの拍子に指が当たったとか、範囲に含んでしまってたとかで、予期しない範囲を示すことがあるから要注意なんです。

もしも思い通りに動作しなかったら上図みたいに関係ないセルを行ごと、列ごと削除してみてください。値が入っているだけの場合は値を削除するだけで対象外になります。

「すべてクリア」アクションのご案内

ためしてないけど、たぶんこの↑「すべてクリア」をセルに適用しても使用範囲から外せると思います。


おわりに

おわりです!

まだあみだくじに仕掛かったばかりなので、ちょっと物足りないかと思いますが、次いきましょう。

では美味しいもの食べたり、散歩したり、趣味に講じたり、眠ったり、気分転換したらまた戻ってきてください。もちろんこのまま進むもヨシ!

次の回でまたお会いしましょう。ありがとうございました。


この記事が気に入ったらサポートをしてみませんか?