このページに含まれるプログラム、ファイル等については、
クラスとは変数管理などを簡単にするために作っておく窓口のようなものです。あえて作る必要もないけど、手順の統一・簡略化に威力を発揮するといったところでしょうか。私の中では。例えば、一時的に記憶するためのセルを作るために
Public rng As Range Private Sub Class_Initialize() Dim temp As Range Set temp = Selection 'ワークシートを作成した時点で選択範囲が変わるので先に記憶しています。 Set rng = ThisWorkbook.Worksheets.Add.Cells(1) temp.Worksheet.Activate '先に記憶した選択範囲を選択しなおします。 temp.Select End Sub Private Sub Class_Terminate() On Error Resume Next 'Worksheetが削除されている場合のためです。 Application.DisplayAlerts = False rng.Worksheet.Visible = xlSheetVisible '可視状態でないと削除できないようです。 rng.Worksheet.Delete Application.DisplayAlerts = True End Sub
Sub test() Dim temp As New RangeClass Selection.Cells(1).Copy temp.rng Selection.Cells(2).Copy Selection.Cells(1) temp.rng.Copy Selection.Cells(2) End Sub
Private Sub Class_Initialize() With Selection Set rng = ThisWorkbook.Worksheets.Add.Cells(1) .Worksheet.Activate .Select End With End Sub
前回、一時保管用のセル範囲を簡単に管理するクラスを作りましたが、クラス内のRange変数が自由に扱えるために保存用の範囲とちがう範囲を指定しなおせるという欠点がありました。クラスの内部変数が自由に扱えるということは例えて言えば、銀行で預金客が引き下ろしにきたら、金庫を開けて出し入れさせるようなものです。クラスモジュール内ではプロパティという内部変数を取り扱う窓口を作ることができます。このプロパティはVBAにはじめからあるオブジェクトのプロパティと同じように使うことが出来ます。前回のプログラム中の
Public rng As Range
の部分を次のように替えてください。
Private rng As Range
Public Property Get r() As Range
Set r = rng
End Property
rngを外から触れないようにして、代わりにrという窓口を作りました。このプロパティは一時保管用のワークシートの左上のセルを返します。例えば、
Dim temp As New RangeClass
temp.r = 2
とすることによって、一時保管用のセルを作って2を代入します。
このプロパティは範囲の参照は出来ますがSetは使えませんので、間違えて参照先を変更することがなくなります(エラーになります)。よくVBAのヘルプにある「値の取得のみ可能です。」を実現していることになります。
(これでクラス内のエラーが全て防げるかというとそうではありません。temp.r.Worksheet.Deleteという禁断の技(笑)が存在します。この技を防ぐにはセル範囲を返すのではなく、セル範囲の値等を返すという手段をとるしかなくなると思います。それでは面倒くさいし、機能が制限されてしまうので、そこまで安全策は取っていません。Worksheetを参照出来なくするような、何か良い方法があれば教えてください。)
さて、ここでもうひとつサンプルプログラムを作ってみました。コピーした範囲と大きさのちがう範囲にペーストしたい場合に使えます。マクロボタンを作って使ってください。セル範囲をコピーし、ペーストしたい範囲を選択してマクロボタンを押します。
Sub ResizePaste() On Error Resume Next Dim temp As New RangeClass Dim nowSelection As Range Set nowSelection = Selection With temp.r .PasteSpecial .Resize(nowSelection.Rows.Count, nowSelection.Columns.Count).Copy End With With nowSelection .Activate .PasteSpecial End With End Sub
前回(9/14)にも書きましたが、クラスモジュールは操作のための窓口を作ってやろうというものです。私が良く見に行っているSOFT-XさんのHPでShapeのTextは256文字以上を一度に書き込み/読み込みすることができない、ということが書かれていましたので、これをクラスモジュールで簡単に読み書きできるようにしてみましょう。
Option Explicit Private MyShape As Shape Private Const Limit As Long = 254 Property Set Shape(Target As Shape) Set MyShape = Target End Property Property Get Shape() As Shape Set Shape = MyShape End Property Property Let Text(Text As String) Const Dummy As String = "a" Dim l As Long, Repeat As Long, Pos As Long If Not MyShape Is Nothing Then With MyShape.TextFrame Repeat = Len(Text) \ Limit .Characters.Text = "" For l = 0 To Repeat Pos = l * Limit + 1 .Characters(Pos).Insert Mid(Text, Pos, Limit) Next l End With End If End Property Property Get Text() As String Dim l As Long, Repeat As Long Dim temp As String If Not MyShape Is Nothing Then With MyShape.TextFrame Repeat = .Characters.Count \ Limit For l = 0 To Repeat - 1 temp = temp & .Characters(l * Limit + 1, Limit).Text Next l If .Characters.Count Mod Limit <> 0 Then temp = temp & .Characters(l * Limit + 1).Text End If End With End If Text = temp End Property Property Let Characters(Optional Pos As Long = 0, Optional Length As Long = 0, Text As String) Dim temp As String If Not MyShape Is Nothing Then temp = Me.Text If Pos = 0 Then Me.Text = temp & Text Else Me.Text = Left$(temp, Pos - 1) & Text & Right$(temp, Len(temp) - Pos - Length + 1) End If End If End Property Property Get Characters(Optional Pos As Long = 0, Optional Length As Long = 0) As String Dim temp As String If Not MyShape Is Nothing Then temp = Me.Text If Pos = 0 Then Characters = temp Else Characters = Mid$(temp, Pos, Length) End If End If End Property
Sub test() Dim s As Shape, temp As String Dim st1 As New ShapeText, st2 As New ShapeText Const Length As Integer = 254 With ActiveSheet Set s = .Shapes.AddShape(msoShapeRectangle, 20, 20, 150, 300) s.TextFrame.Characters.Text = String(Length, "a") Set st1.Shape = .Shapes.AddShape(msoShapeRectangle, 200, 20, 150, 300) st1.Text = String(Length, "a") Set st2.Shape = .Shapes.AddShape(msoShapeRectangle, 370, 20, 150, 300) st2.Text = st1.Text st2.Characters(3, 4) = st1.Text st2.Text = st2.Text + st1.Text st2.Shape.TextFrame.Characters.Font.Bold = True MsgBox Len(st2.Text) End With End Sub
Property Get/Letについて詳しく説明をして欲しいというご意見をいただきましたので、2回ぐらいに分けて説明したいと思います。
PropertyはVBAのオブジェクトでおなじみの「プロパティ」を自分のクラス(フォームでも作ることが可能なようです。)に作るものです。そのクラスに必要なパラメータの値を設定/取得するための動作を記述するものです。利点は次のようなものが挙げられます。
Option Explicit Private Age As Integer Private Up20 As Boolean Property Let 年齢(i As Integer) If i >= 0 Then Age = i Else Age = -1 Up20 = (Age >= 20) End Property Property Get 年齢() As Integer 年齢 = Age End Property Property Get Is成人() As Boolean Is成人 = Up20 End Property Property Get IsError() As Boolean If Age = -1 Then IsError = True Else IsError = False End Property
Sub test1() Dim a As New Class1 a.年齢 = InputBox("入力してください。") If a.IsError Then MsgBox "入力エラー" Else MsgBox "2倍の年だと" & a.年齢 * 2 & "歳" End If MsgBox a.Is成人, , "成人?" End Sub
Option Explicit Private Age As Integer Public Function Func年齢(Optional i As Integer = -1) As Integer If i >= 0 Then Age = i Func年齢 = Age End Function Property Get 年齢() As Integer 年齢 = Age End Property Property Let 年齢(i As Integer) Age = i End Property
Sub test2() Dim a As New Class2, b As New Class2 a.Func年齢 20 b.Func年齢 a.Func年齢 + 10 MsgBox b.Func年齢 a.年齢 = 20 b.年齢 = a.年齢 + 10 MsgBox b.年齢 End Sub
前々回(12/13)のプログラムでCharactersのプロパティを作成したのですが、次のような質問がきました。
「標準モジュールで関数を作るとき、引数に Optional を指定すると、それ以降の引数はすべて Optional 指定をしないとコンパイラが受けつけてくれません。なぜCharactersではそうではないのですか?」
それでは早速、Property LetとGetの最初の一行(宣言部分)、実際の使用例を並べて書いてみましょう。
Property Let Characters(Optional Pos As Long = 0, Optional Length As Long = 0, Text As String)
Property Get Characters(Optional Pos As Long = 0, Optional Length As Long = 0) As String
s.Characters(3,4) = string 'Property Letが実行される。
string = s.Characters(3,4) 'Property Getが実行される。
(ちなみに、Letの一番最後を除いてLet/Getでまったく同じ引数を宣言していますね。また、Letの一番最後の引数とGetの帰り値の型が一致しています。これは式の右辺でも左辺でも同じように使えるための約束事です。)
たしかにProperty Letにおいて、1,2番目の引数はOptional指定なのにLetの一番最後の引数はそうではありませんね。なぜなんでしょう?
実際の使用例と宣言の引数を比較してみて、Posが3でLengthが4、そしてstringがText(およびGetの帰り値の型)に一致していることが分かるでしょうか?つまり、最後の引数だけ役割が違うのです。見た目で話すと
Property Letの最後の引数は代入される右辺の値で、その他は「関数」の引数である
ということになります。もちろん、Function(関数)ではなくProperty Letなのですが、最後の引数以外についてはご質問のとおり、関数と同じ制約(一度Optionalを指定した以降は全てOptionalにしなければならない)を受けます。しかしながら、最後の引数は代入値という、ほかの引数とは異なった性格を持つ、Property Letには絶対必要なものです。ですから、逆にProperty Letの最後の引数はOptional指定をすることが出来ない引数なのです。
お分かりいただけたでしょうか。
VBAでは私の知る限り、数値・文字列は参照渡しができません。と書くと、「関数間では参照渡し(ByRef)が既定だよ。」といわれるかもしれませんが、関数内では参照渡しができないので、例えば、呼び出されたサブルーチンのほうで、参照を維持することができません。
j = iは値渡しです。一方、Set j = iは参照渡しなのですが、数値、文字列に適用しようとすると「オブジェクトが必要です。」とエラーが出ます。ここで思いつきました。「クラスを使ってやろう。」
次を見てください。
Option Explicit
Sub test()
Dim i As Integer, j As Integer
i = 1
j = i 'Set j = iはエラー
i = 2
MsgBox j '1
End Sub
Sub test2()
Dim i As New IntegerClass, j As IntegerClass
i.Value = 1
Set j = i
i.Value = 2
MsgBox j.Value '2
End Sub
Sub subtest3(Optional i As IntegerClass = Nothing)
Static j As IntegerClass
If Not i Is Nothing Then
Set j = i
ElseIf Not j Is Nothing Then
MsgBox j.Value '2
End If
End Sub
Sub test3()
Dim i As New IntegerClass
i.Value = 1
subtest3 i
i.Value = 2
subtest3
End Sub
IntegerClassというクラスを作り、そこには一行、「Public Value As Integer」と書いてやれば終わりです。もちろん、StringでもVariantでもOKです。
最後の例を見ると親ルーチンの値が変化したことが、サブルーチンで読み取れることがわかると思います。
これを使うメリットは渡す変数を覚えていなくてもいい点、Public変数にしなくても共有化できる点などがあげられます。が、これらはそのまま、構造化プログラミングという観点ではデメリットであり、多用は避けましょう。