制御の基本

制御の基本

前回まででは、本当に基本的なマクロを作りました。

どんどん作り始めている方は、あれだけの説明では十分に不便を感じていることと思います。
では、そろそろ次のステップへ行きましょう。不便を感じている程理解しやすいことでしょう。

基本的にマクロは一行づつ実行されていきます。
しかし、それだけでは同じようなことを何度もしたい場合や、セルの内容によって処理をかえたい場合などに非常に面倒です。同じようなことを何度も書かない といけなかったり、あらかじめセルの位置を決めてしまわないといけなかったりと、少し大きめのマクロでは面倒というよりも使えないといった方がいいかもし れません。

VBAでは、このような場合に必要となる構文(こういう規則で書いてねというやつです)が用意されています。くり返し(DO~LOOP、FOR~NEXT 等)、条件式(IF、SELECTなど)です。

これらについても、説明していきますが、その前にいくつか知っておかなければいけないことがありますので、今回は簡単なマクロをもとに説明していくことにしましょう。

 


今回のマクロ(動くボール)

やることは単純です。GO!という星形(ボタンと言っとく)をクリックすると、四角の中をボールが動き出します。線に当たると反転して動きます。そして、STOPボタンをクリックすると、ボールは止まります。これだけです。
非常に簡単なんですが、タイマーなんかを知らない人には作れませんし、もしかすると初心者向きではないかもしれません(入門書で見たことないし)。でも動きがある方が面白いんで、ちょっとだけがんばってみましょう。

では、以下の手順で作ります。ユーザーインターフェース(GUIってやつですね)、マクロの作成、GUIとマクロのリンクの手順で作ります。

  1. 新しいブックを開きます。
  2. まず、ユーザーインタフェース(GUIってやつですね)の部分から作りましょう。
    ■の図形をシート上に貼りつけます。(図形描画ツールバーの四角形です。書式設定で、好きなように設定しておきます。
  3. この図形に名前をつけます。■をクリックして選択状態にすると、数式バーの名前ボックスに「四角形 1」とか出ていると思います。これを選択して「box」と入力してRETURNキーを押します。これで、この■の名前はboxになりました。
  4. 同じようにして●を貼りつけ、「ball」と名付けます。
  5. マクロとリンクするためのボタンを貼りつけます。図形はなんでもいいです。2つ貼りつけ、それぞれの名前を「btnGo」「btnStop」にします。

    • 次のマクロを新しいモジュールにコピー&ペーストします。
      Option Explicit

      Private Const TIMERPROC = "MoveBall" 'タイマープロシージャ名
      Dim nowTime As Date '現在時刻
      Dim runTime As Date 'タイマーセット時刻

      Type Position
       X As Long
       Y As Long
      End Type

      Type waku
       minPos As Position
       maxPos As Position
       movePos As Position
      End Type

      Dim g_Box As Shape
      Dim g_Ball As Shape
      Dim g_Waku As waku

      Sub btnGo_Click()
       With ActiveSheet
        Set g_Ball = .Shapes("ball")
        Set g_Box = .Shapes("box")
       End With

       With g_Waku
        .minPos.X = g_Box.Left + 10
        .minPos.Y = g_Box.Top + 10
        .maxPos.X = g_Box.Left + g_Box.Width – g_Ball.Width – 10
        .maxPos.Y = g_Box.Top + g_Box.Height – g_Ball.Height – 10
        .movePos.X = 5
        .movePos.Y = 5
       End With

       MoveBall
      End Sub

      Sub btnSTOP_Click()
       StopBall
      End Sub

      Sub MoveBall()
       With g_Waku
        If (.movePos.X < 0) Then
         If (g_Ball.Left <= .minPos.X) Then
          .movePos.X = -.movePos.X
         End If
        End If
        If (.movePos.Y < 0) Then
         If (g_Ball.Top <= .minPos.Y) Then
          .movePos.Y = -.movePos.Y
         End If
        End If

        If (.movePos.X > 0) Then
         If (g_Ball.Left > .maxPos.X) Then
          .movePos.X = -.movePos.X
         End If
        End If
        If (.movePos.Y > 0) Then
         If (g_Ball.Top >= .maxPos.Y) Then
          .movePos.Y = -.movePos.Y
         End If
        End If

        g_Ball.IncrementLeft .movePos.X
        g_Ball.IncrementTop .movePos.Y
       End With

       nowTime = Now()
       runTime = nowTime + TimeValue("00:00:01") / 2 '後に
       Application.OnTime runTime, TIMERPROC 'タイマーセット
      End Sub

      Sub StopBall()
       Application.OnTime runTime, TIMERPROC, schedule:=False 'タイマー解除
      End Sub

  6. 最後に、マクロとGUIのリンクです。ボタンbtnGoをクリックし、右ボタンを押してショートカットメニューからマクロの登録を選ぶと、上でペーストしたマクロが一覧表示されているはずです。その中から、btnGo_Clickを選びます。
  7. 同じようにして、ボタンbtnStopにbtnSTOP_Clickを設定します。
    これで終わりです。

では、GOボタンをクリックしてみて下さい。どうです、ボールが動きだしましたか?
それも、四角の中を動いていますか?動かない場合、エラーが出た場合はもう一度手順を確認してみて下さい。

たまにボールが止まったりしますよね。これは、仕方ないようです。まあ、本来Excelでこんなことする必要無いですからね。ちょっと、得意ではないことをやらせているのでぎこちないのは仕方ないようですね。動きをとめるには、STOPボタンをクリックして下さい。

 

さて、内容の説明に行きましょう。

GUIについては、特に必要無いですよね。マクロに行きましょう。

まず、変数名のパンチミスなどに備えます。これは、前に説明していますね。

Option Explicit

次に、Constを使って定数を宣言しています。定数とは文字どおりずっと同じ値です(数となっ ていますが、文字列もありです)、このモジュール内のマクロが実行される時には、左辺の値が出てきた時には右辺の値に置き換えて実行されます。つまり、こ こでは以降TIMERPROCと記述されている部分が出てきた場合には、それは、MoveBallであるとして、実行されます。
Privateは、これがこのモジュール内でのみ有効であることを示しています。Publicにすると、他のモジュールにもこの定数が適用されることになります。
さて、なぜこんな面倒なことをするのかと言うと、マクロを分かりやすくするためです。例えば、入力可能な数字の最大値として、100を使いたいとします。マクロ中に100という数字がいきなり出てきても、コメントがない限りこれが何の意味のある数字か判りませんよね。
でも、InputMaxとかいてあれば、なんとなく意味が判りますよね。そう言うことです。
もちろん、Const InputMax = 100 の一文が必要ですよ。

Private Const TIMERPROC = "MoveBall" 'タイマープロシージャ名

次は、変数の宣言です。

変数とは色々と変わる値を入れる所です(数となっていますが、文字列もありです)
定数は値ですが、変数は場所です。この違いに注意しましょう。
この変数は、Dim 変数名 As 型の様に、Dimステートメントを使って宣言します。
厳密に言うと、もっと色々な記述方法があるのでは、省略します。
Dimで宣言すると覚えておきましょう。

簡単に言うと、以下のnowTimeの場合、「ここで、nowTimeというDate型(日付けの値を入れることができる)変数(値を入れるところ)用意する。これ以降でnowTimeと記述した場合には、その変数に入れられている値を参照できる。」ということです。
変数と言う考え方は決して難しくないのですが、説明はしにくいんですね。

Dim nowTime As Date '現在時刻

同じように、「ここで、runTimeというDate型(日付けの値を入れることができる)変数(値を入れるところ)用意する。これ以降でnowTimeと記述した場合には、その変数に入れられている値を参照できる。」ということです

Dim runTime As Date 'タイマーセット時刻

 

※では、ここで変数と定数について少しだけ整理します。

変数だ、定数だと言われても最初は何のことかわからないでしょう。
とりあえずは、「どんなものか」、「何が違うのか」を感じ取りましょう。
あとは、実践する内に徐々に分かるようになるでしょう。
なお、以下では説明を簡単にするために複雑な部分は削っています。

定数 ← 比較→ 変数

Const 定数名= 値[ As データ型]

データ型を省略するとVariant型になります

構文

Dim 変数名 [As データ型]

データ型を省略するとVariant型になります

Const VAL=5
Const MSG = "値は"
Sub a()
  MsgBox MSG& VAL
  'VAL=10
  MsgBox MSG& VAL
End Sub
Dim VAL
Dim MSG
Sub a()
  VAL=5
  MSG="値は"
  MsgBox MSG& VAL
  VAL=10
  MsgBox MSG& VAL
End Sub
1,2行目は、定数の宣言と定義です。ここでは、「定数は値そのものです。これ以降にVALと書いてあったらそれは5ですよ。MSGと書いてあったらそれは"値は"ですよ。」ということをExcelに指示しています。
なお、定数はその名の通り、最初から最後まで一定な値ですので、変更はできません。
4行目ではメッセージボックスを表示していますが、MSG、VALの値はそれぞれ定義されていますから、それをつなげて表示されます。
次に5行目で、VALに10を代入しようとしていますが、定数の値を変更することはできませんので、コメントにしています。
試しにコメントをはずして実行してみましょう。エラーになるはずです。
で、6行目ですが、VALもMSGも値は変わっていないですから、先ほどと同じメッセージが表示されます。
例の説明 1,2行目は、変数の宣言です。ここでは、「VALという名前の入れ物と、MSGと言う名の入れ物を用意しておきますので、これ以降にVALと書いてあったらそれはVALという入れ物の中の値ですよ。MSGと書いてあったらそれはMSGという入れ物の中の値ですよ。」ということをExcelに指示しています。
なお、変数はその名の通り、変動する値ですので、変更はでできます。
4,5行目では、VAL,MSGそれぞれに値を入れています。6行目ではメッセージボックスを表示していますが、MSG、VALにはそれぞれ"値は"、5、が入れられましたから、それをつなげて表示されます。
7行目で、VALに10を代入しています。変数ではただ、入れ物が用意されているだけなので、値を入れ替えることができます。
で、8行目ですが、VALの中身は10にかえられたので、それが表示されます。

1回目:値は5
2回目:値は5


定数は値を変更できないので、同じ結果になる

結果

1回目:値は5
2回目:値は10


変数は値を変更できるので、結果が異なる

次は、ユーザー定義のデータ型の宣言です。
VBAでは、IntegerやStringなどいくつかのデータ型がすでに用意されていますが、これらだけでは不便な場合が多々あります。
このような場合にそれらの既存のデータ型を寄せ集めて、新しいデータ型を宣言することができます。C言語を御存じの方は構造体だと思って下さい。
詳しい説明は後日にしたいと思います。
ここでは、位置をあらわすPosition、枠のサイズ(ボールの移動サイズも)をあらわすwakuを宣言しています。 ちなみに、この名付けはよくあり ません。Positionは英語で、wakuはローマ字です。あなたが実際にプログラミングする場合はどちらかに統一するのが望ましいでしょう。ここで は、この説明のためにあえて、両方を混在しています。

Type Position
 X As Long
 Y As Long
End Type

Type waku
 minPos As Position
 maxPos As Position
 movePos As Position
End Type

枠、玉、枠のサイズを格納するための変数を宣言します。
Dim g_Box As Shape
Dim g_Ball As Shape
Dim g_Waku As waku

まず、GOボタンに設定するマクロを記述しています。
btnGoをクリックした場合に実行したいので、このような名前にしています。
ここから、End Subまでが、このマクロと言うことですね。では、中身の説明に行きましょう。
Sub btnGo_Click()

Withステートメントは、このブロック(Exd Withまで)では、その次に記述されたオブジェクト(ActiveSheet)について、基本的に記述していますと言うことをあらわします。これは、ま ず最初に覚えるべきステートメントだと思うのですが、市販の入門書などではそうとは思わない様です。(ちなみに、Excel5の頃のマクロ記録では Withステートメントは使われていませんでしたが、Excel98では使われています。また、Excel5の頃のVB(1.5辺りだっと思いますが)で はWithステートメントはありませんでしたが、現在では当たり前のようにあります。この事からもWithの必要性が想像できるでしょう)
ここでは、ボールと枠をあらわすオブジェクト(図形)を変数に代入しています。

 With ActiveSheet
  Set g_Ball = .Shapes("ball")
  Set g_Box = .Shapes("box")
 End With

Withには2つの効果があります。
もし、Withを使用しない場合は次のような記述になります。
Set g_Ball = ActiveSheet.Shape("ball")
Set g_Box = ActiveSheet.Shape("box")

これでも、悪くはないですがこれが何十行も続くとするとどうでしょうか?
結構見にくくなるのでないですか?
Withを使用して記述すれば、比較的見やすくできますね。これが、一つ。
もう一つは性能です。Withを使わない場合、Excelはすべての行でActiveSheetの値を求めなければなりません。ところが、Withの場合 は最初にActiveSheetの値は求められていて、そのWithブロック上に.で始まる記述があった場合はそのActiveSheetの値が使われる ので、処理効率が良い訳です。
ここではピンとこないかもしれませんが、ActiveSheetの部分が例えば、WorkBooks("ブック1").WorkSheets("Sheet1")であったとすればどうでしょうか?
Excelではブック1を求めて、その中のSheet1を求めるという作業が必要になりますがそれが、一回ですむのと複数回行うのとは大きな違いになりますよね。

Setステートメントはオブジェクトの代入をあらわします。ふつうの変数の代入では=を記述するだけで良いのですが、オブジェクトの場合はSetが必要になります。

 With g_Waku
まず、ボールが移動可能な位置の最小値を枠の少し内側に設定します。
  .minPos.X = g_Box.Left + 10
  .minPos.Y = g_Box.Top + 10

ボールが移動可能な位置の最大値を枠の少し内側に設定します。
  .maxPos.X = g_Box.Left + g_Box.Width – g_Ball.Width – 10
  .maxPos.Y = g_Box.Top + g_Box.Height – g_Ball.Height – 10

ボール移動サイズを5に設定します。
  .movePos.X = 5
  .movePos.Y = 5
 End With

そして、実際にボールを動かす処理を呼んでいます。
 MoveBall
End Sub

今度はSTOPボタンに設定するマクロを記述しています。
btnStopをクリックした場合に実行したいので、このような名前にしています。
ここから、End Subまでが、このマクロと言うことですね。では、中身の説明に行きましょう。
Sub btnSTOP_Click()
ボールの動きを止める処理を呼ぶだけです。
 StopBall
End Sub

ここがメインとなる処理、ボールを動かす処理ですね。
Sub MoveBall()
やってる事は単純です。ボールの位置と枠のサイズの位置を比較して、まだ動かしてもいい様ならボールの位置を動かすだけです。
 With g_Waku
X方向の移動サイズが0以下(つまり左向き)なら、左に動いていると言う事なので、
  If (.movePos.X < 0) Then
もう左向きに動かせないようなら、移動方向を右に変更します(つまりプラス方向)。
   If (g_Ball.Left <= .minPos.X) Then
    .movePos.X = -.movePos.X
   End If
  End If

Y方向の移動サイズが0以下(つまり上向き)なら、上に動いていると言う事なので、
  If (.movePos.Y < 0) Then
もう上向きに動かせないようなら、移動方向を下に変更します(つまりプラス方向)
   If (g_Ball.Top <= .minPos.Y) Then
    .movePos.Y = -.movePos.Y
   End If
  End If

X方向の移動サイズが0以上(つまり右向き)なら、右に動いていると言う事なので、
  If (.movePos.X > 0) Then
もう右向きに動かせないようなら、移動方向を左に変更します(つまりマイナス方向)
   If (g_Ball.Left > .maxPos.X) Then
    .movePos.X = -.movePos.X
   End If
  End If

Y方向の移動サイズが0以上(つまり下向き)なら、下に動いていると言う事なので、
  If (.movePos.Y > 0) Then
もう下向きに動かせないようなら、移動方向を上に変更します(つまりマイナス方向)
   If (g_Ball.Top >= .maxPos.Y) Then
    .movePos.Y = -.movePos.Y
   End If
  End If

これで、どちらの方向にどれだけ動かすかは設定したので、あとは以下のようにボールの位置を移動します。
  g_Ball.IncrementLeft .movePos.X
  g_Ball.IncrementTop .movePos.Y
 End With

さて、ここは少し難しいですが、今のように移動しただけで終わってしまうとちょっと動いただけで終わってしまいますね。ボールがどんどん動いて いるように見えるためには継続的に動かさなければなりません。そこで、タイマーを設定して0.5秒後にもう一度MoveBall()が実行されるようにし ます。この中でまたタイマーがセットされますね。
これにより、どんどん動いているように見える訳です。

まず、現在の時刻を取得します。時刻はNow()関数で取得できます。
 nowTime = Now()

次に、タイマーを起こす時刻を現在より0.5秒後になるような値をrunTimeに設定します。
0.5秒後と言うのはTimeValue("00:00:01")/2 で表せます。
 runTime = nowTime + TimeValue("00:00:01") / 2

で、実際にタイマーをセットします。Applicationオブジェクト(つまりエクセル)のOnTimイベントにいつタイマーを起こすか、どのプロシージャを起こすかを引き数として渡します。
TIMERPROCは、"MoveBall"であると定数宣言していましたね。
 Application.OnTime runTime, TIMERPROC 'タイマーセット
End Sub

Sub StopBall()

最後は、タイマーを止める処理です。これがないとボールがずっと動き続ける事になりますね。設定方法は簡単。セット時と同じようにonTime を使いますが、引き数に解除したいタイマーをセットした時間、そのプロシージャを渡し、最後にschedule:=Falseを渡します。
 Application.OnTime runTime, TIMERPROC, schedule:=False 'タイマー解除
End Sub

さて、以上でこのマクロの説明を終わりますが、説明をだいぶ省いてしまいました。
本当は説明を挟んでいきたかったのですが、余りにも多くなるとマクロが見えなくなるので止めました。各項目の説明は別に設けていきたいと思いますのでしばしお待ちください。

メールマガジンを始めようかと思っているので、それとの連動も考えています。

 


1999.8.8

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください