このページに含まれるプログラム、ファイル等については、
クラスとは変数管理などを簡単にするために作っておく窓口のようなものです。あえて作る必要もないけど、手順の統一・簡略化に威力を発揮するといったところでしょうか。私の中では。例えば、一時的に記憶するためのセルを作るために
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変数にしなくても共有化できる点などがあげられます。が、これらはそのまま、構造化プログラミングという観点ではデメリットであり、多用は避けましょう。
無断転載を禁じます。