t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

九九、FizzBuzz、じゃんけんの3つをプログラミングの「永字八法」に認定する

漢字の「永」の字には、書に必要な以下の技法8種が全て含まれている。

  • 横画
  • 縦画
  • はね
  • 右上がりの横画
  • 左はらい
  • 短い左はらい
  • 右はらい

このことを永字八法(えいじはっぽう)というらしい。

カッコイイ!

つまり書の練習に「永」という漢字をチョイスすれば、必要な技法を効率よく身に付けることができるというわけだ。

さて、書の解説はこれくらいにしてプログラミングの話に移ろう。

はじめてプログラミングをする方は、まず書籍のコードを見ながらそのままそっくりタイプすることから入る。
これを俗に「写経」という。
thom.hateblo.jp

ではどんなコードを写経したら良いだろうか。
まずは「Hello, world!」から。これは大体どんなブログラミング言語でも共通だ。
書でも最初は漢字の「一」を書くと決まっている。

VBAではこう書く。

Sub Main()
    MsgBox "Hello, world!"
End Sub

そのあとは、文字列の結合、計算、変数、制御構文などの単純なテーマを学習していく。

では一通り基礎をやったら次はどうしようか。
書の「永字八法」に相当するような題材はないだろうか。
つまり、プログラミングに必要な技法が色々と含まれていながらそこまで複雑にならない初級の題材にふさわしいコード

今回3つピックアップしたのでそれぞれ紹介しようと思う。

九九

言わずと知れた九九。小学校で習うアレをプログラムでExcelシート上に書いてみようというコードがこちら。

Sub KuKu()
    Dim i As Integer
    Dim j As Integer
    For i = 1 To 9
        For j = 1 To 9
            ThisWorkbook.Sheets(1).Cells(i, j) = i * j
        Next
    Next
End Sub

制御構造として順次・反復が含まれ変数、オブジェクトと一通り扱う良い題材だ。
For文のネストを扱っているのも良い。

FizzBuzz

これもプログラマーには有名なネタ。1から順に数字を数え上げていくが、3の倍数ならFizz、5の倍数ならBuzz、3と5の倍数ならFizzBuzzと発言するという英語圏の遊びである。

Sub FizzBuzz()
    Dim i As Integer
    For i = 1 To 100
        If i Mod 15 = 0 Then
            Debug.Print "FizzBuzz"
        ElseIf i Mod 5 = 0 Then
            Debug.Print "Buzz"
        ElseIf i Mod 3 = 0 Then
            Debug.Print "Fizz"
        Else
            Debug.Print i
        End If
    Next
End Sub

制御構造は順次・選択・反復と3種類すべてが含まれ、剰余算、デバッグプリントまで扱う。
これも良い素材だと言えると思う。

じゃんけん

もはや説明するまでもない。

Sub JyanKen()
    Dim you As Integer
    Dim com As Integer
    Do
        you = CInt(InputBox("じゃんけんの手を数字で入力" & vbNewLine & _
            "1:グー、2:チョキ、3:パー"))
        com = WorksheetFunction.RandBetween(1, 3)
        
        Select Case com
        Case 1
            MsgBox "相手はグーを出しました。"
        Case 2
            MsgBox "相手はチョキを出しました。"
        Case 3
            MsgBox "相手はパーを出しました。"
        End Select
        
        If you = com Then
            MsgBox "あいこです。もう一度。"
        ElseIf (you = 1 And com = 2) Or (you = 2 And com = 3) Or (you = 3 And com = 1) Then
            MsgBox "あなたの勝ちです。"
        Else
            MsgBox "あなたの負けです。"
        End If
    Loop While you = com
End Sub

こちらも制御構造は順次・選択・反復と3種類すべてが含まれ、加えて入力処理、ワークシート関数の利用、型のキャストまで含まれる。

ちょっと複雑で考えることも増えるけれど、まだまだ初級ネタとして使えそうだ。

結論

九九、FizzBuzz、じゃんけん。
いずれも基礎がたくさん詰まった良題である。
これらを写経することで、プログラミングに必要な技法を効率よく身に付けることができるのではないかと思う。

というわけで上記3点を「プログラミングの永字八法」として勝手に認定したいと思う。

ちなみに私の字はきたない。

VBAのコーディングガイドラインを作ってみた

VBAのコーディングガイドラインを作ってみた。

掲載先は私のメインサイト

VBA コーディングガイドライン - You.Activate

ガイドライン作成にあたり参考にしたのはこちらの4冊

VB.NETルールブック ?読みやすく効率的なコードの原則

VB.NETルールブック ?読みやすく効率的なコードの原則

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

良いコードを書く技術 ?読みやすく保守しやすいプログラミング作法 (WEB+DB PRESS plus)

良いコードを書く技術 ?読みやすく保守しやすいプログラミング作法 (WEB+DB PRESS plus)

もともとQiitaにもVBAガイドライン案は上がっているようだけど、少し私とは見解が違う部分があるので自分で作ることにした。

以下は別の方が作られたVBAのコーディングガイドライン
qiita.com

こちらは私のよりもはるかに広いトピックをカバーしていて非常に参考になる。
ただ私はエンドユーザーコンピューティングに限ってはマルチバイト変数の肯定派なのでその点がぶつかるのと、変数名の命名規則をもう少し掘り下げたかったので今回新たにガイドラインを作成した次第。

先に紹介した4冊でも識別子に適切な名前を付けることは非常に重要なこととして扱われており、またVB.NETルールブックではキャメル記法・パスカル記法などの使い分けについて具体的に書かれていたので、これらは是非とも盛り込みたかった。

結果的にガミガミとうるさいガイドラインになってしまった気もするけど、指針を求めてさまよっている人はこういうのあると助かるんじゃないかな。

みんなこれに従えなんていうつもりは毛頭無いけど、たたき台くらいにしていただけると嬉しい。

VBA 「これはマクロ化しない」という決断も必要

VBAを覚えてマクロを作れるようになると、業務を手当たり次第自動化したくなるものだ。

しかし安易な自動化は慎むべきかもしれない。

このように考えるようになったのは、以下の記事がきっかけだった。
自動化をどこまで進めるか|会社で役立つリスクマネジメント|社団法人横浜中法人会

長いので私が特に感銘を受けた部分だけを抜粋する。

まず1つ目はこちら。

過度の自動化は人間が持っている「何かおかしい」といったシステムやハードを疑うというプロセスをなくす面も見逃せないし、更に異常時における緊急対応能力を削ぐ

VBAをフル活用すればボタン一つですべて作業が完了してしまうようなフルオートメーションを作ることもできる。しかし行き過ぎた自動化によって業務理解が疎かになってしまってはいざという時に大きなツケを払う羽目になるかもしれない。

2つ目はこちら。

NASA(米国航空宇宙局)では自動化しない仕事として以下の4つを挙げている
●作業者が特有のスキル、生きがいを感じている仕事
●非常に複雑であるとか、理解困難な仕事
●自動化によって作業現場で覚醒水準が低下するような仕事
●自動化が不具合の時、作業者が解決不可能な仕事

これは本当に目からうろこだった。
これまで私は「何を自動化しないか」という観点で考えたことがなかったからだ。

それから私は効率一辺倒だったこれまでの考え方を改め、個別の案件について本当にマクロ化がベストなのかどうか意識するようになった。

もちろん、マクロを作るという結論にいたる場合も多い。
誰もが嫌がる単純作業の繰り返しはコンピューターの得意分野だから迷わずマクロ化するし、人に業務をお願いする際複雑すぎると受け取ってもらえないのでマクロで単純化したりもする。しかしその場合もフルオートではなく、少しは人間が介在する形にしておきたい。

自分が何をやっているのか自覚できなくなるほどの自動化、つまり「よくわからないけどボタンを押してるだけ」というマクロ完全依存は避けたい。

何をマクロ化して、何をマクロ化しないか。その具体的な指針はまだ自分の中でも模索中で固まっていないのだけれど、効率化以外の視点も常に考えながら日々の業務改善に臨みたい。

最後に、VBAを絶賛学習中の方はこの記事は無視して何でもかんでも手当たり次第自動化してみると良い。学習テーマの確保はなかなか難しいので、みすみすチャンスを潰すこともない。

なんでも作れるようになったら、改めてこの問題について考えていただけると良いかなと思う。

VBA SmartArtを使ってシートごとに作業ステップを表示するバーを作成するマクロ

今回はVBAでSmartArtを使ってシートごとに作業ステップを表示するバーを作成してみようと思う。

作成物のイメージはこのようなもの。
f:id:t-hom:20170119111553p:plain

今回マクロで作成するのは上部のStepを表示するバーの部分だけ。

シート2ならStep2がハイライトされる。
f:id:t-hom:20170119105223p:plain

ひとつの業務に使用する一連のマクロをExcelブックにまとめてしまうことがよくある。
実行順が決まっており、それぞれシートに分かれている場合にこのようにステップが表示されていると気が利くと思われるかもしれない。

手でも作れるけれど、色の塗り分けが面倒なのと、ステップの変更などで何度も入れ替えたりする必要が出てくるかもしれないのでここはマクロでさくっと作れるようにしておきたい。

ということで早速完成コードの紹介。

Sub Stepバー作成()
    Dim sheetNo As Long
    For sheetNo = 1 To ThisWorkbook.Worksheets.Count
        'Stepバー生成
        Dim ws As Worksheet: Set ws _
            = ThisWorkbook.Worksheets(sheetNo)
        Dim sh As Shape: Set sh _
            = ws.Shapes.AddSmartArt(Application.SmartArtLayouts( _
                "urn:microsoft.com/office/officeart/2005/8/layout/hChevron3"), 5, 5)
        sh.Height = 20
        sh.Width = 500
        
        '一旦ノードを全削除
        Dim i As Long
        For i = 1 To sh.SmartArt.Nodes.Count
            sh.SmartArt.Nodes.Item(1).Delete
        Next
        
        'ブックの数だけノードを追記しつつ、
        '対象ブックのStepだけオレンジに、それ以外はグレーアウト
        Dim stepNo As Long
        For stepNo = 1 To ThisWorkbook.Worksheets.Count
            With sh.SmartArt.Nodes.Add
                .Shapes(1).Fill.ForeColor.RGB = IIf(sheetNo = stepNo, _
                    XlRgbColor.rgbOrange, _
                    XlRgbColor.rgbLightGray)
                .TextFrame2.TextRange.Text = "Step" & stepNo
            End With
        Next
    Next
End Sub

使い方は、シートをあらかじめ必要な枚数分追加しておいてこのマクロを実行するだけ。
するとシートの枚数に応じたStep数でバーが作成される。

このマクロはShape型、SmartArt型、SmartArtLayout型、SmartArtNode型の4つの関係を押さえておくと理解しやすい。が、これらについてはMSDNなどに説明を譲る。

オブジェクトブラウザやTypeName関数で、それぞれが持つプロパティがどのようなデータ型を返すのかを把握するのが理解のポイントである。

Application.SmartArtLayoutsにURLみたいな引数を渡している部分があるが、これはSmartArtの種類を示すIDである。覚えられるものではないし、マクロの記録で調べられるのでそこは気にしなくて良い。

あ、後で気づいたけど、この手のマクロはパワポのほうが活用できそう。

VBA デザインしたユーザーフォームを元にNewで複数のフォームインスタンスを作る。

VBAでユーザーフォームはふつう、単一のオブジェクトとして扱う。

たとえば以下のようなフォームを作ったとしよう。
f:id:t-hom:20170117201737p:plain

オブジェクト名は「frm成績」としたので、このフォームを表示させるコードはこうだ。

Sub hoge()
    frm成績.Show
End Sub

この時フォームは、「frm成績」オブジェクトとして扱われいてる。

さて、たとえばこのフォームを3つ同時に起動させたいとする。
どうすればいいか。

まずフォームのShowModalがTrueのままでは3つ同時に表示させることはできないので、これをFalseにしておく。
f:id:t-hom:20170117202340p:plain

ShowModalとは、フォームを表示させた際にそのフォームを閉じるまで次に進めないか、それとも次のコードをそのまま実行させるかというプロパティで、FalseにするとフォームをShowしたあと閉じられるのを待たずに次のコードに進む。

そして記述するコードは次のとおり。

Sub hoge()
    Dim f As frm成績
    Set f = New frm成績
    f.Show
    Set f = New frm成績
    f.Show
    Set f = New frm成績
    f.Show
End Sub

実行するとフォームは1つに見えるが、重なっているだけでずらしてみるとこのとおり。
f:id:t-hom:20170117203051p:plain

つまりユーザーフォームはオブジェクト(インスタンス)でありながら、クラスのようにも振る舞うことができるのだ。なんとも不思議な特性である。クラスモジュールはインスタンス化して初めてつかえる。シートなどのオブジェクトモジュールは単一オブジェクトとしてのみ扱え、Newはできない。対してフォームモジュールは単一オブジェクトとして扱うこともできるし、クラス(オブジェクトのひな形)として扱うこともできるのだ。


さて、これが何に使えるのかというとそこはみなさんの創造力次第。

私がひとつ思いついたのは、データをフォームに読み取ってカードのように並べて表示させたいケース。

単一フォームで対応するのは難しいが、この手法なら大丈夫。

たとえばSheet1に以下のようなデータがあるとする。
f:id:t-hom:20170117203344p:plain

Sheet1モジュールに以下のコードを記入し、

Enum 列
    名前 = 1
    国語
    数学
    理科
    社会
End Enum

Sub FormからFormをNew()
    Dim C As Collection
    Set C = New Collection
    Dim frm As frm成績
    
    Dim i
    For i = 2 To 4
        Set frm = New frm成績
        frm.lblName = Cells(i,.名前).Value
        frm.txt国語 = Cells(i,.国語).Value
        frm.txt数学 = Cells(i,.数学).Value
        frm.txt理科 = Cells(i,.理科).Value
        frm.txt社会 = Cells(i,.社会).Value
        frm.Show
        frm.Left = frm.Left + (i - 3) * frm.Width
    Next
End Sub

実行するとこのとおり。
f:id:t-hom:20170117203710p:plain

あるいはデータを格納するためのPersonクラスを作成し、そのクラスにフォームのインスタンスを持たせるとか。。
そうすれば各Personインスタンスがそれぞれ別個のユーザーインターフェースを持つことができる。

他にも工夫次第で色々と面白いことができそうな気がする。

VBA 参照設定とCreateObjectを定数1つで切り替えるテクニック

VBAで外部のオブジェクトを扱うには、事前バインド方式と遅延バインド方式の2種類がある。事前バインドとはいわゆる参照設定のことで、遅延バインドとはCreateObject関数によるオブジェクト生成を指す。

さて、どちらが良いかと言われると一長一短なので困る。
メリット・デメリットは概ねこんなかんじ。

      事前バインド(参照設定) 遅延バインド(CreateObject)
メリット  型がきっちり決まるのでコーディング中に入力補完の恩恵を受けられる。 コードだけで完結するのでネットで公開するのが楽。
デメリット コードだけで完結しないのでネットにコードを掲載する場合に説明が面倒くさい。ライブラリのバージョン違いなどで別のPCでは参照設定しなおさないと動作しないケースがある。 実行するまで型が決まらないのでコーディング中に入力補完の恩恵を受けられない。

事前バインディングのほうが若干スピードも速いんだけれど、微々たるものなので今回は無視する。

では具体的に例を挙げて説明する。

以下はどちらもFileSystemObjectを利用してCドライブ配下のフォルダ数を取得するプログラムだ。

Sub 事前バインド方式()
    '↓参照設定しているので固有型が使用できる。
    Dim fso As FileSystemObject
    '↓参照設定しているので固有型を用いたNewによるオブジェクト生成ができる。
    Set fso = New FileSystemObject
    '↓コーディング中にプロパティ・メソッドの入力候補が表示される。
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

Sub 遅延バインド方式()
    '↓参照設定していないので固有型は使用できない。
    Dim fso As Object
    '↓参照設定していないので固有型を用いたNewによるオブジェクト生成はできない。
    Set fso = CreateObject("Scripting.FileSystemObject")
    '↓コーディング中にプロパティ・メソッドの入力候補が表示されない。
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

さて、プログラムを書くときは入力候補が表示されたほうが助かるので開発中は参照設定、配布するときはバージョンの違いなどを吸収してほしいのでCreateObjectを使いたいとする。

いちいちコードを書き直すのは面倒なので何か良い手はないか。

そこで今回ご紹介するのは、プリプロセッサディレクティブを用いて定数1つで事前バインド方式と遅延バインド方式を切り替える方法だ。

なにやらややこしい単語であるが、プリ(事前の)プロセッサ(プロセスの)ディレクティブ(指示)ってことで、もう少し簡単にいうと「このマクロを実行する前にあらかじめやっておいてね」という指示になる。

種類もそんなに無いのですぐ覚えられると思う。

ではサンプルコード。

#Const REF = True

Sub 定数REFにより切り替え可能()
#If REF Then
    Dim fso As FileSystemObject
    Set fso = New FileSystemObject
#Else
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
#End If
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

この、ナンバーサイン(#)で始まっている部分がプリプロセッサディレクティブだ。
このマクロは実行の前にまず定数REFによってIf文が判定され、以下のように整形される。
※整形といっても内部的なものなので、実際にコードが書き換わるわけではない。

Sub 定数REFにより切り替え可能()
    Dim fso As FileSystemObject
    Set fso = New FileSystemObject
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

もし#Const REF = Falseとしていたら、こうなる。

Sub 定数REFにより切り替え可能()
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

それから実行されるのだ。

あるいはオブジェクトの生成部分はまとめてしまってもよい。

#Const REF = True

Sub 定数REFにより切り替え可能()
#If REF Then
    Dim fso As FileSystemObject
#Else
    Dim fso As Object
#End If
    Set fso = CreateObject("Scripting.FileSystemObject")
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

整形するとこのようになる。

Sub 定数REFにより切り替え可能()
    Dim fso As FileSystemObject
    Set fso = CreateObject("Scripting.FileSystemObject")
    Debug.Print fso.GetFolder("C:\").SubFolders.Count
End Sub

fsoは固有型で宣言しているが、オブジェクトの生成はCreateObjectでしている。
結局入力補完が効くかどうかは変数の型で決まるのでオブジェクトの生成自体はどちらでも良い。

こうすると参照設定にしておいて、配るときだけ定数REFをFalseにすれば良くなるのでメンテナンスはしやすい。
まぁ、このために行数が増えるのはすこし難点ではあるが。。

VBA ウォッチウインドウとステップ実行でオブジェクト変数の仕組みを学ぶ

以前クラスモジュールの入門記事でオブジェクト変数はタグのようなものだと書いた。
https://thom.hateblo.jp/entry/2016/12/31/013555#オブジェクト変数は箱じゃなくてネームタグでイメージしよう

具体的な仕組みは以下の記事で書いた。
thom.hateblo.jp

とはいえメモリの動きなどは座学ではなかなかイメージしづらいので、今回はウォッチウインドウとステップ実行を使って具体的なメモリのアドレスを確認していこうと思う。

使用するのは、VarPtr関数とObjPtr関数だ。

ウォッチウインドウを使用する前に、基本的な使い方を押さえておこう。
まずVarPtrは、変数が指す実際のメモリアドレスを取得するための関数だ。

次のコードを実行してみてほしい。

Sub 基本型の変数()
    Dim L As Long
    L = 10
    Debug.Print "変数Lはアドレス" & VarPtr(L) & "を指している。"
    Debug.Print "アドレス" & VarPtr(L) & "に" & L & "が入っている。"
End Sub

私の環境ではイミディエイトウインドウに次のように表示された。

変数Lはアドレス1896880を指している。
アドレス1896880に10が入っている。

アドレスの数値は実行環境やタイミングによって変わるので皆さんの環境ではまた違った結果になると思う。

基本型の変数はこのように、「変数→メモリアドレス→中身」という仕組みになっている。
対してオブジェクト型の場合は、「変数→メモリアドレス→メモリアドレス→中身」という仕組みだ。
1つ目のメモリアドレスの中身に2つ目のメモリアドレスが入っていて、間接的に中身を指している。

この2つ目のメモリアドレスを取り出すための関数がObjPtrである。

次のコードで確かめてみよう。

Sub オブジェクト型の変数()
    Dim O As Collection
    Set O = New Collection
    O.Add 10
    Debug.Print "変数Oはアドレス" & VarPtr(O) & "を指している。"
    Debug.Print "アドレス" & VarPtr(O) & "にアドレス" & ObjPtr(O) & "が入っている。"
    Debug.Print "アドレス" & ObjPtr(O) & "に" & TypeName(O) & "が入っている。"
    Debug.Print TypeName(O) & "のItem(1)に" & O.Item(1) & "が入っている。"
End Sub

結果はこのようになった。

変数Oはアドレス1896880を指している。
アドレス1896880にアドレス88747728が入っている。
アドレス88747728にCollectionが入っている。
CollectionのItem(1)に10が入っている。

つまり「変数O→1896880→88747728→Collection」という構成。

さて間接参照になっているということを理解したところで、ウォッチウインドウでその動きを見ていこう。
対象のオブジェクトは今回は自作することにした。

まずクラスモジュールを挿入し、以下の1行を記入する。今回クラスモジュール名はClass1のまま変更しない。

Public Value As Long

これはValueというパブリック変数のみを持つクラスで実用性はまったくないが今回はオブジェクト変数の仕組みを知るためなのでこれで良い。

次に標準モジュールに以下のコードを書く。

Sub hoge()
    Dim a As Class1
    Set a = New Class1
    a.Value = 10
    
    Dim b As Class1
    Set b = New Class1
    b.Value = 20

    Debug.Print a.Value
    Debug.Print b.Value
End Sub

次に、表示メニューからウォッチウインドウを表示させ、右クリックメニューからウォッチ式の追加をクリックする。
f:id:t-hom:20170116001050p:plain

そして式欄に「VarPtr(a)」と入れ、OKを押す。
f:id:t-hom:20170116001406p:plain

するとこのように一行ウォッチ式が追加される。
f:id:t-hom:20170116001638p:plain

同じようにして「VarPtr(b)」「ObjPtr(a)」「ObjPtr(b)」も追加しよう。

このように表示されたら準備完了。
f:id:t-hom:20170116001842p:plain

F8キーでステップ実行を開始すると、早速VarPtr(a)とVarPtr(b)の値が確定する。
f:id:t-hom:20170116002014p:plain
どうやら変数のアドレスは宣言文より前、実行直後に決まるようだ。

ちなみにステップ実行では、黄色く表示されているところがこれから実行するコードである。すでに実行されたコードと勘違いしやすいので注意。

Set a = New Class1の実行がおわると、ObjPtr(a)にアドレスが入る。
f:id:t-hom:20170116002506p:plain
つまりアドレス88522536に実際のオブジェクトが生成されたということ。
こういう状態:「変数a→1896880→88522536→オブジェクト」

そしてSet b = New Class1の実行が終わると、ObjPtr(b)にもアドレスが入る。
f:id:t-hom:20170116002801p:plain

別のオブジェクトなのでアドレスも異なる。

では次に、コードを書き換えて実行してみる。

Sub hoge()
    Dim a As Class1
    Set a = New Class1
    a.Value = 10
    
    Dim b As Class1
    Set b = New Class1
    b.Value = 20
    
    Dim c As Class1
    Set c = a
    Set a = b
    Set b = c

    Debug.Print a.Value
    Debug.Print b.Value
End Sub

ウォッチ式にも「ObjPtr(c)、VarPtr(c)」をそれぞれ追加しよう。

ステップ実行で下図の位置まですすめる。
f:id:t-hom:20170116003308p:plain
※この時点でSet c = aはまだ未実行であることに注意。

現時点では、変数aが88521736にあるオブジェクトを指していて、変数bは88522536にあるオブジェクトを指している。

次にSet c = aを実行すると、変数cも88521736にあるオブジェクトを指すようになる。
f:id:t-hom:20170116003620p:plain

つまり、変数aと変数cは全く同一のオブジェクトを参照していることになる。

次にSet a = bを実行すると、変数aは88521736を参照するのをやめて、変数bと同じ88522536のオブジェクトを参照するようになった。
f:id:t-hom:20170116003752p:plain

最後にSet b = cが実行されると、変数bは88522536の参照をやめて、変数cと同じ(つまり当初変数aが参照していた)88521736を参照するようになる。
f:id:t-hom:20170116003908p:plain

これで変数aが指すオブジェクトと変数bが指すオブジェクトが入れ替わったことがわかる。

さて、次はコレクションを使って見ていこう。

コードは以下のとおり。

Sub hoge()
    Dim a As Class1
    Dim col As Collection
    Set col = New Collection
    For i = 1 To 3
        Set a = New Class1
        col.Add a
    Next
End Sub

ウォッチ式はいったんすべて削除し、新たに「ObjPtr(a)」「ObjPtr(col.Item(1))」「ObjPtr(col.Item(2))」「ObjPtr(col.Item(3))」「i」を追加しておく。

ステップ実行を1周目のFor分の終わりまですすめる。
f:id:t-hom:20170116004755p:plain
すると変数aとcol.Item(1)は同じオブジェクトを参照していることがわかる。

次に2周目のSet a = New Class1実行直後まですすめる。
f:id:t-hom:20170116004924p:plain
すると変数aが指すオブジェクトが変わったが、col.Item(1)は先ほど保持させたオブジェクトのままである。

次の行を実行するとcol.Item(2)に今aに入っているオブジェクトが保持された。
f:id:t-hom:20170116005043p:plain

以下、3周目の終わり。
f:id:t-hom:20170116005158p:plain

変数aは使い回ししてもコレクションのそれぞれのアイテムは別のオブジェクトを保持していることになる。

「コレクションは変数を保持する」という勘違いがよく見受けられるが、コレクションが保持するのは変数ではなくその変数の中身である。

「コレクション.Add 変数」という書き方が混乱の原因だと思う。

これは基本型の変数で試してみるとわかる。

Sub hoge()
    Dim a As Long
    a = 10
    
    Dim col As Collection
    Set col = New Collection
    
    col.Add a
    a = 20
    
    Debug.Print col.Item(1)
End Sub

もしコレクションが変数aそのものを保持するなら、イミディエイトウインドウには20が出力されるはずだが、実際には10が出力される。

以上

ウォッチウインドウについて

ウォッチウインドウはローカルウインドウほど簡単ではないけれど、ローカルウインドウと比べて、

  • 指定したものだけをウォッチできる
  • 任意の式をウォッチできる

という特徴がある。

ローカルウインドウが基本的に変数しか見られないのに対し、ウォッチウインドウで使える任意の式はかなり強力だ。

ウォッチウインドウをうまく使うには、「式」とは何なのかが分かってないと辛い。
thom.hateblo.jp

私は最初、式って何なのか分かってなかったので、数学風に「a + b =」とか、Excel風に「= i * 2」と入力してエラーがでて使用を諦めていた。分かってしまえば簡単なんだけれど、これらはVBAでいう式ではない。イコールは不要だ。イコールを使用するのは比較演算子としてBoolean型を返す式を作るときだけで、この場合は右辺・左辺が両方必要になる。

その他、具体的な使い方の例。
thom.hateblo.jp

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。