ほっとひといき給湯室 |
|
投稿日時: 12/03/15 19:13:21
投稿者: みそじのおじさん
|
---|---|
前スレッドはこちらです。
|
|
投稿日時: 12/03/15 21:49:30
投稿者: どんきち
|
---|---|
別プロジェクトのクラスの利用
'*** クラス clsA1Pub *** '* instancing = PublicNotCreatable Option Explicit Public StrA1Pub As String Public obj2 As clsA2Pub '*** クラス clsA2Pub *** '* instancing = PublicNotCreatable Option Explicit Public StrA2Pub As String '*** クラス clsB1Pub *** '* instancing = PublicNotCreatable Option Explicit Public StrB1Pub As String '* PublicなクラスのPublicなプロパティの型に '* Privateなクラスは指定できない。 'Public Obj2 As clsB2Prv 'NG Private mobjObj2 As clsB2Prv '* PublicなクラスのPublicなプロパティの型に '* Privateなクラスは指定できない。 'Public Property Get Obj2() As clsB2Prv 'NG 'End Property 'Public Property Set Obj2(p1 As clsB2Prv) 'NG 'End Property Public Property Get obj2() As Object Set obj2 = mobjObj2 End Property Public Property Set obj2(p1 As Object) Set mobjObj2 = p1 End Property '*** クラス clsB2Prv *** '* instancing = Private Option Explicit Public StrB2Prv As String '*** クラス clsC1Prv *** '* instancing = Private Option Explicit Public StrC1Prv As String Public obj2 As clsC2Pub '*** クラス clsC2Pub *** '* instancing = PublicNotCreatable Option Explicit Public StrC2Pub As String '*** クラス clsD1Prv *** '* instancing = Private Option Explicit Public StrD1Prv As String Public obj2 As clsD2Prv '*** クラス clsD2Prv *** '* instancing = Private Option Explicit Public StrD2Prv As String '*** 標準モジュール modA1Pub *** Option Explicit Public Function Init1() As clsA1Pub Dim obj1 As clsA1Pub Set obj1 = New clsA1Pub Set obj1.obj2 = modA2Pub.Init1 Set Init1 = obj1 End Function '*** 標準モジュール modA2Pub *** Option Explicit Public Function Init1() As clsA2Pub Dim obj1 As clsA2Pub Set obj1 = New clsA2Pub Set Init1 = obj1 End Function '*** 標準モジュール modB1Pub *** Option Explicit Public Function Init1() As clsB1Pub Dim obj1 As clsB1Pub Set obj1 = New clsB1Pub Set obj1.obj2 = modB2Prv.Init1 Set Init1 = obj1 End Function '*** 標準モジュール modB2Prv *** Option Explicit '* 標準モジュールではPublicプロシージャの戻り値 '* の型にPrivateなクラスを指定できる。 Public Function Init1() As clsB2Prv Dim obj1 As clsB2Prv Set obj1 = New clsB2Prv Set Init1 = obj1 End Function '*** 標準モジュール modC1Prv *** Option Explicit '* 標準モジュールではPublicプロシージャの戻り値 '* の型にPrivateなクラスを指定できる。 Public Function Init1() As clsC1Prv Dim obj1 As clsC1Prv Set obj1 = New clsC1Prv Set obj1.obj2 = modC2Pub.Init1 Set Init1 = obj1 End Function '*** 標準モジュール modC2Pub *** Option Explicit Public Function Init1() As clsC2Pub Dim obj1 As clsC2Pub Set obj1 = New clsC2Pub Set Init1 = obj1 End Function '*** 標準モジュール modD1Prv *** Option Explicit '* 標準モジュールではPublicプロシージャの戻り値 '* の型にPrivateなクラスを指定できる。 Public Function Init1() As clsD1Prv Dim obj1 As clsD1Prv Set obj1 = New clsD1Prv Set obj1.obj2 = modD2Prv.Init1 Set Init1 = obj1 End Function '*** 標準モジュール modD2Prv *** Option Explicit Public Function Init1() As clsD2Prv Dim obj1 As clsD2Prv Set obj1 = New clsD2Prv Set Init1 = obj1 End Function '*** 標準モジュール modTest1 *** '* 親クラスのインスタンス生成時に '* 子クラスのインスタンスも生成1。 '* 変数の型はクラス名。 Option Explicit Sub Test11() Dim obj1 As clsA1Pub Set obj1 = modA1Pub.Init1 obj1.StrA1Pub = "1" obj1.obj2.StrA2Pub = "2" Debug.Print obj1.StrA1Pub Debug.Print obj1.obj2.StrA2Pub End Sub Sub Test12() Dim obj1 As clsB1Pub Set obj1 = modB1Pub.Init1 obj1.StrB1Pub = "3" obj1.obj2.StrB2Prv = "4" Debug.Print obj1.StrB1Pub Debug.Print obj1.obj2.StrB2Prv End Sub Sub Test13() Dim obj1 As clsC1Prv Set obj1 = modC1Prv.Init1 obj1.StrC1Prv = "5" obj1.obj2.StrC2Pub = "6" Debug.Print obj1.StrC1Prv Debug.Print obj1.obj2.StrC2Pub End Sub Sub Test14() Dim obj1 As clsD1Prv Set obj1 = modD1Prv.Init1 obj1.StrD1Prv = "7" obj1.obj2.StrD2Prv = "8" Debug.Print obj1.StrD1Prv Debug.Print obj1.obj2.StrD2Prv End Sub '*** 標準モジュール modTest2 *** '* 親クラスのインスタンス生成時に '* 子クラスのインスタンスも生成1。 '* 変数の型はObject。 Option Explicit Sub Test21() Dim obj1 As Object Set obj1 = modA1Pub.Init1 obj1.StrA1Pub = "1" obj1.obj2.StrA2Pub = "2" Debug.Print obj1.StrA1Pub Debug.Print obj1.obj2.StrA2Pub End Sub Sub Test22() Dim obj1 As Object Set obj1 = modB1Pub.Init1 obj1.StrB1Pub = "3" obj1.obj2.StrB2Prv = "4" Debug.Print obj1.StrB1Pub Debug.Print obj1.obj2.StrB2Prv End Sub Sub Test23() Dim obj1 As Object Set obj1 = modC1Prv.Init1 obj1.StrC1Prv = "5" obj1.obj2.StrC2Pub = "6" Debug.Print obj1.StrC1Prv Debug.Print obj1.obj2.StrC2Pub End Sub Sub Test24() Dim obj1 As Object Set obj1 = modD1Prv.Init1 obj1.StrD1Prv = "7" obj1.obj2.StrD2Prv = "8" Debug.Print obj1.StrD1Prv Debug.Print obj1.obj2.StrD2Prv End Sub '*** 標準モジュール modTest3 *** '* 子クラスのインスタンスを直接生成1。 '* 変数の型はクラス名 Option Explicit Sub Test31() Dim obj2 As clsA2Pub Set obj2 = modA2Pub.Init1 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test32() Dim obj2 As clsB2Prv Set obj2 = modB2Prv.Init1 obj2.StrB2Prv = "2" Debug.Print obj2.StrB2Prv End Sub Sub Test33() Dim obj2 As clsC2Pub Set obj2 = modC2Pub.Init1 obj2.StrC2Pub = "3" Debug.Print obj2.StrC2Pub End Sub Sub Test34() Dim obj2 As clsD2Prv Set obj2 = modD2Prv.Init1 obj2.StrD2Prv = "4" Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest4 *** '* 子クラスのインスタンスを直接生成2。 '* 変数の型はObject。 Option Explicit Sub Test41() Dim obj2 As Object Set obj2 = modA2Pub.Init1 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test42() Dim obj2 As Object Set obj2 = modB2Prv.Init1 obj2.StrB2Prv = "2" Debug.Print obj2.StrB2Prv End Sub Sub Test43() Dim obj2 As Object Set obj2 = modC2Pub.Init1 obj2.StrC2Pub = "3" Debug.Print obj2.StrC2Pub End Sub Sub Test44() Dim obj2 As Object Set obj2 = modD2Prv.Init1 obj2.StrD2Prv = "4" Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest5 *** '* 親クラスのインスタンス生成時に生成した '* 子クラスのインスタンスを変数に代入1。 '* 親クラスの変数の型はクラス名。 '* 子クラスの変数の型はクラス名。 Option Explicit Sub Test51() Dim obj1 As clsA1Pub Dim obj2 As clsA2Pub Set obj1 = modA1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test52() Dim obj1 As clsB1Pub Dim obj2 As clsB2Prv Set obj1 = modB1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrB2Prv = "2" Debug.Print obj2.StrB2Prv End Sub Sub Test53() Dim obj1 As clsC1Prv Dim obj2 As clsC2Pub Set obj1 = modC1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrC2Pub = "3" Debug.Print obj2.StrC2Pub End Sub Sub Test54() Dim obj1 As clsD1Prv Dim obj2 As clsD2Prv Set obj1 = modD1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrD2Prv = "4" Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest6 *** '* 親クラスのインスタンス生成時に生成した '* 子クラスのインスタンスを変数に代入2。 '* 親クラスの変数の型はクラス名。 '* 子クラスの変数の型はObject。 Option Explicit Sub Test61() Dim obj1 As clsA1Pub Dim obj2 As Object Set obj1 = modA1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test62() Dim obj1 As clsB1Pub Dim obj2 As Object Set obj1 = modB1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrB2Prv = "2" Debug.Print obj2.StrB2Prv End Sub Sub Test63() Dim obj1 As clsC1Prv Dim obj2 As Object Set obj1 = modC1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrC2Pub = "3" Debug.Print obj2.StrC2Pub End Sub Sub Test64() Dim obj1 As clsD1Prv Dim obj2 As Object Set obj1 = modD1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrD2Prv = "4" Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest7 *** '* 親クラスのインスタンス生成時に生成した '* 子クラスのインスタンスを変数に代入3。 '* 親クラスの変数の型はObject。 '* 子クラスの変数の型はクラス名。 Option Explicit Sub Test71() Dim obj1 As Object Dim obj2 As clsA2Pub Set obj1 = modA1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test72() Dim obj1 As Object Dim obj2 As clsB2Prv Set obj1 = modB1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrB2Prv = "2" Debug.Print obj2.StrB2Prv End Sub Sub Test73() Dim obj1 As Object Dim obj2 As clsC2Pub Set obj1 = modC1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrC2Pub = "3" Debug.Print obj2.StrC2Pub End Sub Sub Test74() Dim obj1 As Object Dim obj2 As clsD2Prv Set obj1 = modD1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrD2Prv = "4" Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest8 *** '* 親クラスのインスタンス生成時に生成した '* 子クラスのインスタンスを変数に代入4。 '* 親クラスの変数の型はObject。 '* 子クラスの変数の型はObject。 Option Explicit Sub Test81() Dim obj1 As Object Dim obj2 As Object Set obj1 = modA1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test82() Dim obj1 As Object Dim obj2 As Object Set obj1 = modB1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrB2Prv = "2" Debug.Print obj2.StrB2Prv End Sub Sub Test83() Dim obj1 As Object Dim obj2 As Object Set obj1 = modC1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrC2Pub = "3" Debug.Print obj2.StrC2Pub End Sub Sub Test84() Dim obj1 As Object Dim obj2 As Object Set obj1 = modD1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrD2Prv = "4" Debug.Print obj2.StrD2Prv End Sub ●PrjA.xls VBプロジェクト名 PrjA PrjB.xlsを参照設定 '*** 標準モジュール modTest1 *** '* 親クラスのインスタンス生成時に '* 子クラスのインスタンスも生成1。 '* 変数の型はクラス名。 Option Explicit Sub Test11() Dim obj1 As clsA1Pub Set obj1 = modA1Pub.Init1 obj1.StrA1Pub = "1" obj1.obj2.StrA2Pub = "2" Debug.Print obj1.StrA1Pub Debug.Print obj1.obj2.StrA2Pub End Sub Sub Test12() Dim obj1 As clsB1Pub Set obj1 = modB1Pub.Init1 obj1.StrB1Pub = "3" obj1.obj2.StrB2Prv = "4" Debug.Print obj1.StrB1Pub Debug.Print obj1.obj2.StrB2Prv End Sub Sub Test13() ' '別プロジェクトのPrivateなクラスは使用できない ' Dim obj1 As clsC1Prv 'NG ' Set obj1 = modC1Prv.Init1 ' obj1.StrC1Prv = "5" ' obj1.obj2.StrC2Pub = "6" ' Debug.Print obj1.StrC1Prv ' Debug.Print obj1.obj2.StrC2Pub End Sub Sub Test14() ' '別プロジェクトのPrivateなクラスは使用できない ' Dim obj1 As clsD1Prv 'NG ' Set obj1 = modD1Prv.Init1 ' obj1.StrD1Prv = "7" ' obj1.obj2.StrD2Prv = "8" ' Debug.Print obj1.StrD1Prv ' Debug.Print obj1.obj2.StrD2Prv End Sub '*** 標準モジュール modTest2 *** '* 親クラスのインスタンス生成時に '* 子クラスのインスタンスも生成2。 '* 変数の型はObject。 Option Explicit Sub Test21() Dim obj1 As Object Set obj1 = modA1Pub.Init1 obj1.StrA1Pub = "1" obj1.obj2.StrA2Pub = "2" Debug.Print obj1.StrA1Pub Debug.Print obj1.obj2.StrA2Pub End Sub Sub Test22() Dim obj1 As Object Set obj1 = modB1Pub.Init1 obj1.StrB1Pub = "3" obj1.obj2.StrB2Prv = "4" Debug.Print obj1.StrB1Pub Debug.Print obj1.obj2.StrB2Prv End Sub Sub Test23() Dim obj1 As Object Set obj1 = modC1Prv.Init1 obj1.StrC1Prv = "5" obj1.obj2.StrC2Pub = "6" Debug.Print obj1.StrC1Prv Debug.Print obj1.obj2.StrC2Pub End Sub Sub Test24() Dim obj1 As Object Set obj1 = modD1Prv.Init1 obj1.StrD1Prv = "7" obj1.obj2.StrD2Prv = "8" Debug.Print obj1.StrD1Prv Debug.Print obj1.obj2.StrD2Prv End Sub '*** 標準モジュール modTest3 *** '* 子クラスのインスタンスを直接生成1。 '* 変数の型はクラス名 Option Explicit Sub Test31() Dim obj2 As clsA2Pub Set obj2 = modA2Pub.Init1 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test32() ' '別プロジェクトのPrivateなクラスは使用できない ' Dim obj2 As clsB2Prv 'NG ' Set obj2 = modB2Prv.Init1 ' obj2.StrB2Prv = "2" ' Debug.Print obj2.StrB2Prv End Sub Sub Test33() Dim obj2 As clsC2Pub Set obj2 = modC2Pub.Init1 obj2.StrC2Pub = "3" Debug.Print obj2.StrC2Pub End Sub Sub Test34() ' '別プロジェクトのPrivateなクラスは使用できない ' Dim obj2 As clsD2Prv 'NG ' Set obj2 = modD2Prv.Init1 ' obj2.StrD2Prv = "4" ' Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest4 *** '* 子クラスのインスタンスを直接生成2。 '* 変数の型はObject。 Option Explicit Sub Test41() Dim obj2 As Object Set obj2 = modA2Pub.Init1 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test42() Dim obj2 As Object Set obj2 = modB2Prv.Init1 obj2.StrB2Prv = "2" Debug.Print obj2.StrB2Prv End Sub Sub Test43() Dim obj2 As Object Set obj2 = modC2Pub.Init1 obj2.StrC2Pub = "3" Debug.Print obj2.StrC2Pub End Sub Sub Test44() Dim obj2 As Object Set obj2 = modD2Prv.Init1 obj2.StrD2Prv = "4" Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest5 *** '* 親クラスのインスタンス生成時に生成した '* 子クラスのインスタンスを変数に代入1。 '* 親クラスの変数の型はクラス名。 '* 子クラスの変数の型はクラス名。 Option Explicit Sub Test51() Dim obj1 As clsA1Pub Dim obj2 As clsA2Pub Set obj1 = modA1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test52() ' Dim obj1 As clsB1Pub ' '別プロジェクトのPrivateなクラスは使用できない ' Dim obj2 As clsB2Prv 'NG ' Set obj1 = modB1Pub.Init1 ' Set obj2 = obj1.obj2 ' obj2.StrB2Prv = "2" ' Debug.Print obj2.StrB2Prv End Sub Sub Test53() ' '別プロジェクトのPrivateなクラスは使用できない ' Dim obj1 As clsC1Prv 'NG ' Dim obj2 As clsC2Pub ' Set obj1 = modC1Prv.Init1 ' Set obj2 = obj1.obj2 ' obj2.StrC2Pub = "3" ' Debug.Print obj2.StrC2Pub End Sub Sub Test54() ' '別プロジェクトのPrivateなクラスは使用できない ' Dim obj1 As clsD1Prv 'NG ' Dim obj2 As clsD2Prv 'NG ' Set obj1 = modD1Prv.Init1 ' Set obj2 = obj1.obj2 ' obj2.StrD2Prv = "4" ' Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest6 *** '* 親クラスのインスタンス生成時に生成した '* 子クラスのインスタンスを変数に代入2。 '* 親クラスの変数の型はクラス名。 '* 子クラスの変数の型はObject。 Option Explicit Sub Test61() Dim obj1 As clsA1Pub Dim obj2 As Object Set obj1 = modA1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test62() Dim obj1 As clsB1Pub Dim obj2 As Object Set obj1 = modB1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrB2Prv = "2" Debug.Print obj2.StrB2Prv End Sub Sub Test63() ' '別プロジェクトのPrivateなクラスは使用できない ' Dim obj1 As clsC1Prv 'NG ' Dim obj2 As Object ' Set obj1 = modC1Prv.Init1 ' Set obj2 = obj1.obj2 ' obj2.StrC2Pub = "3" ' Debug.Print obj2.StrC2Pub End Sub Sub Test64() ' '別プロジェクトのPrivateなクラスは使用できない ' Dim obj1 As clsD1Prv 'NG ' Dim obj2 As Object ' Set obj1 = modD1Prv.Init1 ' Set obj2 = obj1.obj2 ' obj2.StrD2Prv = "4" ' Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest7 *** '* 親クラスのインスタンス生成時に生成した '* 子クラスのインスタンスを変数に代入3。 '* 親クラスの変数の型はObject。 '* 子クラスの変数の型はクラス名。 Option Explicit Sub Test71() Dim obj1 As Object Dim obj2 As clsA2Pub Set obj1 = modA1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test72() ' Dim obj1 As Object ' '別ブックのPrivateなクラスは使用できない ' Dim obj2 As clsB2Prv 'NG ' Set obj1 = modB1Pub.Init1 ' Set obj2 = obj1.obj2 ' obj2.StrB2Prv = "2" ' Debug.Print obj2.StrB2Prv End Sub Sub Test73() Dim obj1 As Object Dim obj2 As clsC2Pub Set obj1 = modC1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrC2Pub = "3" Debug.Print obj2.StrC2Pub End Sub Sub Test74() ' Dim obj1 As Object ' '別ブックのPrivateなクラスは使用できない ' Dim obj2 As clsD2Prv 'NG ' Set obj1 = modD1Prv.Init1 ' Set obj2 = obj1.obj2 ' obj2.StrD2Prv = "4" ' Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest8 *** '* 親クラスのインスタンス生成時に生成した '* 子クラスのインスタンスを変数に代入4。 '* 親クラスの変数の型はObject。 '* 子クラスの変数の型はObject。 Option Explicit Sub Test81() Dim obj1 As Object Dim obj2 As Object Set obj1 = modA1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrA2Pub = "1" Debug.Print obj2.StrA2Pub End Sub Sub Test82() Dim obj1 As Object Dim obj2 As Object Set obj1 = modB1Pub.Init1 Set obj2 = obj1.obj2 obj2.StrB2Prv = "2" Debug.Print obj2.StrB2Prv End Sub Sub Test83() Dim obj1 As Object Dim obj2 As Object Set obj1 = modC1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrC2Pub = "3" Debug.Print obj2.StrC2Pub End Sub Sub Test84() Dim obj1 As Object Dim obj2 As Object Set obj1 = modD1Prv.Init1 Set obj2 = obj1.obj2 obj2.StrD2Prv = "4" Debug.Print obj2.StrD2Prv End Sub '*** 標準モジュール modTest9 *** '* 親クラスのインスタンス生成時に '* 子クラスのインスタンスも生成2。 '* 変数の型はObject。 Option Explicit Sub Test91() Dim obj1 As Object ' 'PublicかPrivateかに関係なく ' '別プロジェクトのクラスはNewできない。 ' Set obj1 = New clsA1Pub 'NG ' Set obj1 = New clsA2Pub 'NG ' Set obj1 = New clsB1Pub 'NG ' Set obj1 = New clsB2Prv 'NG ' Set obj1 = New clsC1Prv 'NG ' Set obj1 = New clsC2Pub 'NG ' Set obj1 = New clsD1Prv 'NG ' Set obj1 = New clsD2Prv 'NG End Sub |
|
投稿日時: 12/03/15 22:24:23
投稿者: どんきち
|
---|---|
別プロジェクトの参照するときの注意事項
|
|
投稿日時: 12/03/15 23:03:16
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
|
|
投稿日時: 12/03/15 23:35:29
投稿者: 月
|
---|---|
YU-TANG さんの引用: これ、簡単なサンプル書いてもらえないですかね。 |
|
投稿日時: 12/03/16 00:18:50
投稿者: みそじのおじさん
|
---|---|
Abyssさんからアドバイスを頂いていたのを見逃しておりました^^;
Abyss さんの引用: 月 さんの引用: ちょっと話しましたデジタル時計のクラスなのですが、 ・Userformを土台 ・1つのUserform内に何個でもデジタル時計の生成が可能(ラベルを8の字型に生成し 時分秒の6個でワンセット) ・時刻表示はそれぞれオフセットが可能 という作りにしておりまして SetTimerには UserFormのhwnd、TimerIDにObjPtrでクラスのアドレスを指定して (私はもちろん標準モジュールに置いた)WndProc内でTimerIDより呼び出し元を特定して 時刻の表示更新をするとしていました。 「ObjPtrの戻り値をユニークなキーとして利用する」は月さん同様、このスレッドから 得られた重要なテクニックでした。 モーダルフォーム限定ですし、On Error Resume Nextも入れてありますので、 落ちる可能性は低かったのですが、何分「皆様の大事な未保存のデータまで吹き飛ばして はまずい」と思いまして、、 せっかくマコさんにもご参加頂いたのに申し訳なく思っております。 題材を選び直し出直してきますね。 私も考えていますが、どなたか ・1つのクラスのみの構成で ・コードも短め ・実践的 or インパクトが強い ・クラスの魅力を伝えられる?! を提示して頂けませんか? クラスモジュールを扱う方の人口を増やすには、やはり「魅力」を伝えなければと 思っています^^ |
|
投稿日時: 12/03/16 01:14:58
投稿者: YU-TANG
|
---|---|
月 さんの引用: 前スレで、みそじのおじさん さんが提示されていますので、そちらをご覧いただくのがよいと思います。 レス番は…って無いのか! んーと、前スレ開けて、ブラウザ上で「48件」をテキスト検索してみていただけますか。 # 余談。EMOBILE LTE サービスイン初日に調達して実測してみましたが、 # 余裕で WiMAX より遅いです。今後、京名阪の計測値が各所から出てくると # 思いますが、東大付近でこれでは、かなりの無理無理感あり。 |
|
投稿日時: 12/03/16 03:12:54
投稿者: yayadon
|
---|---|
Abyss さんの引用: メソッド(IProc_WndProc)の位置を決め打ちできるように, インターフェースを使うわけかぁ。 YU-TANG さんが言われてたインテリセンスから隠すために使う YU-TANG さんの引用: も,"確かに" って感じです。 ----- コードがややこしすぎて, みそじのおじさん さんのコードのどの部分を 皆さんがいじってるのか?の流れをつかめない方のために補足しておくと, 元のクラスにあった Term というメソッドを無くす方向でコードが書かれています。 月さんのコードは Term だけなく mParent も無くなって,スッキリしました。 ただ, イベントの呼び出しは 実行時バインディング になるので, 使う箇所によっては,気にする人は気にするかもしれません。 |
|
投稿日時: 12/03/16 03:52:11
投稿者: yayadon
|
---|---|
Friend メソッドですが,
Option Explicit Private Sub PrivateMethod1() ' Debug.Print "Private Method1" End Sub Public Sub PublicMethod2() PrivateMethod1 End Sub Friend Sub FriendMethod3() PrivateMethod1 ' ここが遅いもよう End Sub のように,内部で Private メソッドを呼び出すと, Friend メソッド呼び出し時の貯金がなくなって逆に不利になるようです。 Private Sub TestPublic() Const cIterate = 30000000 Dim t As Single, i As Long, c As Class1 Set c = New Class1 t = Timer For i = 1 To cIterate c.PublicMethod2 Next Debug.Print Timer - t, "public" End Sub Private Sub TestFriend() Const cIterate = 30000000 Dim t As Single, i As Long, c As Class1 Set c = New Class1 t = Timer For i = 1 To cIterate c.FriendMethod3 Next Debug.Print Timer - t, "friend" End Sub |
|
投稿日時: 12/03/16 04:04:01
投稿者: Abyss
|
---|---|
引用: 当方の環境では、Friendの方が高速です。環境依存でしょうか?(WinXP SP3, VB6) 4.701172 public 4.591797 friend 4.701172 public 4.591797 friend 4.686523 public 4.606445 friend 4.701172 public 4.591797 friend |
|
投稿日時: 12/03/16 04:33:16
投稿者: yayadon
|
---|---|
VB6 がすぐに用意できないので試せないのですが,
|
|
投稿日時: 12/03/16 11:23:14
投稿者: 月
|
---|---|
YU-TANG さんの引用: 了解しました、見てみます。ありがとうございます。 |
|
投稿日時: 12/03/16 11:23:27
投稿者: 月
|
---|---|
紹介し忘れていましたが、オススメのクラスです。
|
|
投稿日時: 12/03/16 11:42:27
投稿者: 月
|
---|---|
角田 さんの引用: 私に知恵を授けてくださったyayadonさんもこう仰ってますね。 yayadon さんの引用: 親子が1:nのケースではイベントは使えないかも、ということで一旦思考停止します。 |
|
投稿日時: 12/03/16 19:04:30
投稿者: マコ
|
---|---|
こんばんは〜
みそじのおじさん さんの引用: みそじのおじさん、お気遣いありがとうございます。 わからないながらも、みなさんの高度な議論を拝見して、このスレの空気を楽しんでおります。 お時間ができて、適当なサンプルがあるときで結構ですよ。 ROMでも参加してるつもりです ^^ その前に、ご紹介くださったリンク先など、読んでみます! |
|
投稿日時: 12/03/16 20:24:26
投稿者: 角田
|
---|---|
> みそじのおじさん
|
|
投稿日時: 12/03/17 07:25:16
投稿者: yayadon
|
---|---|
# 最初は前置きです。
月 さんの引用:yayadon さんの引用: 特定の [Collection クラス] と [その要素のクラス] の関係の場合, 特定の [Collection クラス] 側は, [その要素のクラス] のイベントをしっているので, 特定の [Collection クラス] 側が,イベントを公開すれば良さそうです。 イベントの引数には,[その要素のクラス] の参照を渡すものを含めます。 そして, [Collection クラス] 側に, インターフェース活用のCallback メソッドを用意して, [その要素クラス] 側は,[Collection クラス]を "弱い" 参照で保持して Callback メソッドを呼び出す形を見てみます。 図で表すと,以下のような関係になると思います。 ※ 太い線は強い参照。 細い線は,弱い参照とメソッド呼び出しorイベント呼び出し Collection を利用する 他のオブジェクト ┃ sink object ref count 1 ┃ ↑ ↑ ┃ ┃ 通知 ┃ ┃ ┃ │ ┃ ↓ ↓ ・Event Alert(source As 要素のクラス) ref count 2 [Collection クラス] ┃ ↑ ・ICallbackXXXX.Alert ┃ │ ↑ ↓ 弱参照 & 呼び出し ref count 1 [Collection の要素クラス] 見た目,なんとなくですがスッキリしません。 # Collection クラスのところが ref count 2 になっていますが, # 先に説明したようにイベント絡みなので,構いません。 # 下でもう一度説明します。 理由は,Collection クラスが, いわゆるコレクションの役割と, オブジェクトの状態変異の通知の役割を兼ねてしまっているからです。 ここから本題 ----- そこで, オブザーバー(Observer) オブジェクトと呼ばれているものを投入します。 そして,通知機能は Observer に移します。 ※ 太い線は参照。 細い線は,メソッド呼び出しorイベント呼び出し [Collection を利用する他のクラス] ref count 1 sink object ┃ ┃ ↑ ↑ ┃ ┃ ┃ 通知 ┃ ┃ ┃ │ ┃ ┃ ┃ Event ┃ ↓ ┃ Alert(src As 要素クラス) ↓ ref count 2 [Observer クラス] [Collection クラス] ref count 1 ↑ HelperAlert ┃ ↑ ↑ ┃ ┃ 呼び出し ┃ ┃ │ ┃ ・ISubject.Attach(o As Observer) │ ↓ [Collection の要素クラス] ref count 1 上の図の オブザーバー(Observer) クラスのリスト(参照群)を管理するクラスのことを サブジェクト(Subject) クラス といいます。 但し, 一般的なパターンを使うと,階層が複雑になって, 初めてだと,かえって理解しずらいので, 上の図で存在していないように,今回はその Subject クラス を別途用意しません。 [Collection の要素クラス] に ISubject インターフェースを実装することで, [Collection の要素クラス] が,Subject クラス も兼ねることにします。 # 下のコードでは CPerson クラス が相当します。 そのため,ISubject インターフェース のメソッドは Attach のみになっています。 本来は,Subject クラスが保持する Observer クラスの参照は, [Collection の要素クラス](下のコードでは CPerson) が直接保持します。 その関係で, ISubject インターフェース に必要な Notify メソッドである notifyLevelChanging や notifyLevelChanged のようなメソッドを用意せずに, Observer の コールバックメソッドであり,Update メソッドと呼ばれる LevelChanging や LevelChanged を直接呼び出します。 まず,これを理解しておくと, Subject クラスを別に用意するパターンを理解しやすくなります。たぶん。 また,Observer クラスですが, 本来は,IObserver インターフェース を作成しておいて, Observer クラスに実装するのですが, 今回は Observer クラスに直接実装します。 で, これらのオブジェクト達の関係は, いわゆるデザイン パターンと呼ばれているお決まりのパターンの中で, Observer パターン(Observer Pattern)と呼ばれています。 ※ 参照の循環の問題 上記の図は,Observer クラスの ref count が 2 になっています。 問題があるように見えますが, 先に説明したように イベントが絡む場合, sink object が絡む関係で,参照の循環が起きないので, sink object と 要素クラス の両方から Release されるので問題ありません。 イベントが絡む場合は,そこの参照は無視して考えればOKです。 ----- Observer パターン(Observer Pattern)を使ったコード例を示します。 上の図で見たように, "弱い" 参照を使わず,かつ,Term メソッドも用意しないでやれます。 せっかくなので, イベントは,2つ用意しました。 オブジェクトの状態が変化する前に呼び出され, キャンセル可能な OnLevelChanging と オブジェクトの状態が変化した後に呼び出される OnLevelChanged です。 CPerson クラスが要素クラスで CPersons クラスがそのコレクションクラスです。 コレクションクラスは,手抜きをしています。 CTestContainer クラスは, CObserver クラス をモジュールレベルで WithEvents で受けるためと, コンテナが破棄されたとき # 以下のコードだと,Test1 の # Set clsTestContainer = Nothing # の箇所 に,各インスタンスがすべて破棄されるか確認するためのものです。 ----- '' ISubject クラス Option Explicit Public Sub Attach(clsObserver As CObserver) ' clsObserver は,イベント通知用 End Sub '' CObserver クラス Option Explicit Event OnLevelChanging(clsPerson As CPerson, lngNewLevel As Long, _ blnCancel As Boolean) Event OnLevelChanged(clsPerson As CPerson) Public Sub LevelChanging(clsPerson As CPerson, lngNewLevel As Long, _ blnCancel As Boolean) RaiseEvent OnLevelChanging(clsPerson, lngNewLevel, blnCancel) End Sub Public Sub LevelChanged(clsPerson As CPerson) RaiseEvent OnLevelChanged(clsPerson) End Sub Private Sub Class_Terminate() Debug.Print "CObserver: Class_Terminate." End Sub '' CPerson クラス Option Explicit Implements ISubject Private m_observer As CObserver ' 動的配列にすれば,Observer群に対応可 Private m_userName As String Private m_level As Long ' Public Property Let UserName(strValue As String) m_userName = strValue End Property Public Property Get UserName() As String UserName = m_userName End Property Public Property Let Level(ByVal lngValue As Long) If m_level <> lngValue Then If m_observer Is Nothing Then '<-- よく忘れるので注意 m_level = lngValue Exit Property End If Dim blnCancel As Boolean Call m_observer.LevelChanging(Me, lngValue, blnCancel) If Not blnCancel Then m_level = lngValue Call m_observer.LevelChanged(Me) End If End If End Property Public Property Get Level() As Long Level = m_level End Property Private Sub Class_Terminate() Debug.Print "CPerson - " & m_userName & ": Class_Terminate." End Sub '' ISubject インターフェース Private Sub ISubject_Attach(clsObserver As CObserver) Set m_observer = clsObserver End Sub '' CPersons クラス ---- Remove 等省いています。 Option Explicit Private m_persons As Collection ' Public Function Add(strUserName As String, _ Optional strDefaultLevel As Long = 3) As CPerson Dim clsPersons As CPersons Dim clsPerson As CPerson '' 重複確認 もしくは Add時にエラー処理で For Each clsPerson In m_persons If clsPerson.UserName = strUserName Then MsgBox "既にその名前は登録されています。" Exit Function End If Next Set clsPerson = New CPerson clsPerson.UserName = strUserName clsPerson.Level = strDefaultLevel Call m_persons.Add(clsPerson, strUserName) Set Add = clsPerson End Function Public Property Get Item(strUserName As String) As CPerson Set Item = m_persons.Item(strUserName) End Property Private Sub Class_Initialize() Set m_persons = New Collection End Sub Private Sub Class_Terminate() Debug.Print "CPersons: Class_Terminate." End Sub '' CTestContainer クラス ---- CObserver からのイベント受け取り用 Option Explicit Private m_clsPersons As CPersons Private WithEvents m_clsObserver As CObserver ' Private Sub Class_Initialize() Dim clsObserver As CObserver Dim itfSubject As ISubject Dim clsPersons As CPersons Dim vntElement As Variant Set clsObserver = New CObserver Set clsPersons = New CPersons For Each vntElement In Array("yayadonさん", _ "ろひさん", _ "simpleさん", _ "月さん", _ "ゴマさん", _ "kumattiさん", _ "YU-TANGさん", _ "Kanabunさん", _ "Abyssさん", _ "みそじのおじさん") Set itfSubject = clsPersons.Add(CStr(vntElement)) Call itfSubject.Attach(clsObserver) Next Set m_clsPersons = clsPersons Set m_clsObserver = clsObserver End Sub Private Sub Class_Terminate() Debug.Print "CTestContainer: Class_Terminate." End Sub '' Test CPersons.Add Public Sub TestAdd(strUserName As String, Optional strLevel As Long = 3) Call m_clsPersons.Add(strUserName) End Sub '' Test CPersons.Item Public Function TestItem(strUserName As String) As CPerson Set TestItem = m_clsPersons.Item(strUserName) End Function '' OnLevelChanging ハンドラ メソッドの一例 Private Sub m_clsObserver_OnLevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, _ blnCancel As Boolean) Dim result As VbMsgBoxResult Dim strMsg As String strMsg = clsPerson.UserName & "のレベルが" & vbCrLf & _ clsPerson.Level & " から" & lngNewLevel & " に" & vbCrLf & _ "変更されようとしています。" & vbCrLf & _ "許可しますか?" result = MsgBox(strMsg, vbYesNo Or vbDefaultButton2 Or vbInformation, _ "変更の確認: ユーザーのレベル") If result = vbNo Then blnCancel = True End If End Sub '' OnLevelChanged ハンドラ メソッドの一例 Private Sub m_clsObserver_OnLevelChanged(clsPerson As CPerson) MsgBox clsPerson.UserName & "のレベルが " & _ clsPerson.Level & " に変化しました。" End Sub '' テスト用 メソッド Private Sub Test1() Dim clsTestContainer As CTestContainer Dim clsPerson As CPerson Debug.Print "Test 開始" Set clsTestContainer = New CTestContainer MsgBox "Test CPersons.Add" Call clsTestContainer.TestAdd("月さん") MsgBox "Test CPersons.Item" Set clsPerson = clsTestContainer.TestItem("月さん") '' イベントのテストも兼ねてます。 MsgBox "Test CPerson.Level" clsPerson.Level = clsPerson.Level + 1 Set clsPerson = Nothing Set clsTestContainer = Nothing ' Class_Terminate がすべて呼ばれるハズ MsgBox "終了" Debug.Print "Test 終了" End Sub 出力 Test 開始 CTestContainer: Class_Terminate. CPersons: Class_Terminate. CPerson - yayadonさん: Class_Terminate. CPerson - ろひさん: Class_Terminate. CPerson - simpleさん: Class_Terminate. CPerson - 月さん: Class_Terminate. CPerson - ゴマさん: Class_Terminate. CPerson - kumattiさん: Class_Terminate. CPerson - YU-TANGさん: Class_Terminate. CPerson - Kanabunさん: Class_Terminate. CPerson - Abyssさん: Class_Terminate. CPerson - みそじのおじさん: Class_Terminate. CObserver: Class_Terminate. Test 終了 |
|
投稿日時: 12/03/17 08:04:03
投稿者: 月
|
---|---|
CTestContainer さんの引用: (*゚∀゚):;*.':;ガハッ おかげさまでレベルアップさせていただいております。 yayadonさん、おはようございます。 ほんとはまだ寝ていたかったのですが、すごいコードが提示されたぞと思いVBEを起動しました。 |
|
投稿日時: 12/03/17 08:11:41
投稿者: yayadon
|
---|---|
上のコードの CPersons クラス の Add メソッドで,
|
|
投稿日時: 12/03/17 11:00:26
投稿者: kumatti
|
---|---|
> デジタル時計
|
|
投稿日時: 12/03/17 12:17:57
投稿者: 月
|
---|---|
角田さん、朗報です。
角田 さんの引用: 親子が1:nのケースでも、イベントを使って実現可能でした。 親オブジェクトへの参照を保持せずに親オブジェクトへの参照を得る方法2 https://gist.github.com/2054643 (yayadonさんのおかげです。yayadonさんへのお礼やもろもろは次の返信にします。) ■ストーリー 子は親の連絡先を知らない。 知っているのは、親の連絡先を知っている親の代理人の連絡先だけ。 子は、親が指定した代理人を通すことでしか親と連絡が取れない。 わけありな子である。 ■内部的な話 インスタンスの破棄について。 CParentAgentへの参照を、CPersonsとCPersonのどちらも持つが、CPersonsとCPersonの各インスタンスが破棄されれば、CParentへの参照を持つインスタンスはなくなるので、CParentAgentのインスタンスも破棄される。 CPersonsのインスタンスが破棄される ↓ CPersonの各インスタンスが破棄される ↓ CParentAgentのインスタンスが破棄される |
|
投稿日時: 12/03/17 12:28:15
投稿者: 月
|
---|---|
親子が1:nのケースで、親への参照を保持せずに親への参照を得ることだけを目的とした、シンプルなオブジェクトモデルを書きました。yayadonさんが書かれたObserverパターンのサブセットになります。
|
|
投稿日時: 12/03/17 13:37:46
投稿者: みそじのおじさん
|
---|---|
みなさん、こんにちは。
|
|
投稿日時: 12/03/17 17:57:00
投稿者: 角田
|
---|---|
こんにちは。
UserForm で子1〜3のイベントを受け取る為に、 UserFormと子の間に親クラスを設ける UserForm ↑ │(1:1) RaiseEventによるイベント授受 親 ↑ │(1:n) 直Call ┌┴─┬──┐ 子1・子2・子3 のと同じで、 親 でも子1〜3のイベントを受け取る為に、 更に、親と子の間に叔父クラスを設ける UserForm ↑ │(1:1) RaiseEventによるイベント授受 親 ↑↑ (1:1) RaiseEventによるイベント授受 │┗━━━━━━━━━┓ │ 叔父 │(1:n) 直Call ↑ ┌┴─┬──┐ ┃ 子1・子2・子3 ┃ ┃ ┃ ┃ ┃ ┗━━┻━━┻━━━━┛ (1:n) 直Call という事。 親子クラスによる、クラス配列(擬似コントロール配列)のイベント集約という 手法そのものには変化はないですね。 ただ、「弱参照・強参照」辺りの内容は斜め読みで流していたので、 「親オブジェクトを持たせない為に、ここまでする」という 利点 が いまひとつ見えて来ません。 結局、子1〜3は、 『親』オブジェクトの参照を持たない 代わりに、 『叔父』オブジェクトの参照を持つ という事ですよね。 もう一度、(1)の頭から読み直さないとな〜 |
|
投稿日時: 12/03/17 19:13:42
投稿者: 月
|
---|---|
みそじのおじさん さんの引用: はい、同世代です。 角田 さんの引用: 親オブジェクトへの参照を保持せずに親オブジェクトへの参照を得る上で、ILinkインターフェイスを使うことは必須ではないので、ILinkインターフェイス不使用版も書きました。 ILinkインターフェイス不使用版 https://gist.github.com/643df93dc39c44644afa 角田 さんの引用: 親オブジェクトのインスタンスが破棄されるようにするためです。 |
|
投稿日時: 12/03/17 19:40:34
投稿者: simple
|
---|---|
月さんからご紹介があった
|
|
投稿日時: 12/03/17 20:24:36
投稿者: 月
|
---|---|
simple さんの引用: 私が書いたわけでもないのに、そう言っていただけると嬉しいです。 simple さんの引用: あ、いえ、Compareメソッドを持つクラスを渡すだけでOKですよ。 使い方 さんの引用: そのコードを書いたのは、渡されたクラスが必ずCompareメソッドを持つことを保障するためにインターフェイスを使ってみたんです。あと、戻り値もインターフェイスで定義して、より厳密にしてみた、というところです。 インターフェイスを使う場合、XArray.Sortメソッドの定義も Sort(Optional Comparer) ↓ Sort(Optional Comparer As Comparer) になりますね。 simple さんの引用: 私もJavaScriptを書いていると、VBAでも無名関数を作ったり渡せたりできたらいいなと思います。 |
|
投稿日時: 12/03/17 21:36:45
投稿者: 月
|
---|---|
角田さんが書かれた、「簡単に言えば、」以降のご説明ですが、大変申し訳ないのですが、理解できませんでした。私の理解力が足りないのかもしれません。
|
|
投稿日時: 12/03/18 00:18:30
投稿者: みそじのおじさん
|
---|---|
休日前の夜ですので、ちょっと息抜きです。
月 さんの引用: 「False 読み方」で、ちょっと検索してみましたがどちらの読み方が多いのでしょうかね? 気になります^^ 私は昔、Abyssさんを「アベシ」さんと本気で読んでいました(笑) 「Abyssさんは、そっか北斗の拳が好きなのかー」と 本当に私は英語音痴で「困った人」です^^ 父は何年も海外勤務経験あり、兄もヨーロッパの各国に在住経験ありで英語を喋れないのは 私だけです。。 私の父の妹がアメリカ人と結婚し(アメリカ在住)で20年くらいぶりに従兄弟たちが日本 に遊びに来て会ったのですが ケン サンフランシスコ市警勤務、腕にごっついタトゥー入り テレサ オール金髪に青い瞳 「これが俺のいとこだって?うそだろー。しかも二人とも日本語がまるっきりダメ!」 「札幌の観光スポットに連れて行って!」と頼まれ、かなり冷や汗をかきました^^; 喋れなくても何とかなるさ!と身振り手振りで会話をしながら案内をしましたが 相手にその気持ちがあれば伝わるものですね。(向こうも必死で聞き取ろうとしてくれ ましたから。)その日は私の家に泊まり楽しく過ごしました。 「英語の授業をもっと真面目に受けていたら、、」と最近はよく思います^^ 今は翻訳ソフトを使って従兄弟たちとメールのやりとりをしています。 |
|
投稿日時: 12/03/18 02:12:20
投稿者: 月
|
---|---|
みそじのおじさん さんの引用: 投票画面を作ってみました。 http://www.pollmo.com/?request=viewpoll&P=PO_eUTC64pRmx7HAXy&theme=ltredglass |
|
投稿日時: 12/03/18 03:04:49
投稿者: 角田
|
---|---|
引用: 昔っから、フェルス だな・・・ a(エー)の影響か・・・ >「False 読み方」で、ちょっと検索してみましたが Google検索で出て来た、読み方の「例」にも全く挙がってない(汗) まぁ、良いや (-。-)y-゚゚゚ なんとか(1)まで読み返した・・・ 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 わたしが作っているクラスでは、基本的に Clear メソッドを用意して、 利用者には「クラスの終了前に Clear メソッドを必ず実行してください」 としています。 クラスが親子ならば、親の Clear 内で、子の Clear を実行するように なっていて、Clear ではモジュールレベルのオブジェクト(親(上位オブ ジェクトへの参照 等)を解放(Nothing or Erase)しています。 なので、「お願」い通りに利用して、利用者が直接操作しているオブジェクト に対して Clear を実行していれば、最下層のオブジェクトから順に、上位オブ ジェクトへの参照が解放されていくので、途中で【ref-count がゼロにならない】 でオブジェクトが残るという事はないです。 (みそじのおじさん のクラスでいう Term メソッドですね) 一言で言えば、 「安全のためにルールに従って利用してください」 「ルールに従って利用していれば安全です」 というスタンスでの造りになっています。 (1)での話題は そうはなっていても、ルールを守らずにいきなり終了する人は居る。 ・そうされた時に、どういう状態になるのか? ⇒ ref-count がゼロにならない ⇒ オブジェクトが解放されずに宙ぶらりんで残ってしまう ⇒ トラブルの素になる危険あり ・そうされたとしても安全な造りにするには、どうするか? ⇒ Clearメソッドなどを用意しなくても良い仕組みは? ⇒ そもそも循環参照にならない仕組みにしておけばOK という流れですよね。 それで、その方法論として @「弱参照」形態 オブジェクト参照(ポインタ)を 数値(Long型)で保持する事で、システム(VBA)側に 「参照が無い」ように見せ掛ける。オブジェクトを使う場面になったら、その時々で、 数値を一時的に(ローカル変数で)ポインタに再変換してオブジェクトを操作する。 ただし、そうすると、未だ使うのに、システムが「もう誰も使っていない」と解放して しまったら困るので、自分よりも寿命の長い上位オブジェクト(自分の呼び出し元)に 限るべき。 A WithEvents & Event/RaiseEvent セットによるカスタムイベントの仕組みの中では、 イベントの通知元オブジェクト(子)vs受領オブジェクト(親)間で、オブジェクト 参照が循環しないようになっている。 [子]で[親]参照を保持し、[子]の中で[親]メンバを操作するような処理は、 ・その処理のタイミングをカスタムイベントとして[親]に上げ、 ・『親メンバの操作』処理そのものを[親]の中へ移す ようにすれば何ら気兼ねする事無く安全。 ただし、WithEvents を使うので[親:子]の関係は1対1に限られる。 1対1の関係であれば、検討する価値は大いにある。 B カスタムイベントを巧く利用すると、イベントプロシジャーを通して、[親]参照を 受け取る仕組みを造れる。必要な時に必要な間だけローカル変数で[親]参照を持てる ので、モジュール変数で[親]参照を保持し続ける必要が無くなり、循環参照の危険を 無くしつつ、[子]の中で[親]メンバを操作できるようになる。 (このスレッドでは、親子は1対1まで) 他の話題では ・わたしの「擬似からの脱却」でいう [Raiseほにゃらら]メソッド([子]から呼ぶ、[親]の 中のイベント通知メソッド)等が Public なので、インテリセンスの候補リストに載り、 利用者に見えている。 ⇒ 間違って使われるとトラブルになる。 ⇒ 隠す方法は無いか? ⇒ ・Public を Friend に変える(Import 形態では効果は無いが、アドイン形態 (別プロジェクト)では見えなくなり有効) ・インターフェース部分を Implements 形態にすることで、インテリセンスの 候補リストに載らないようにできる(Import でも有効)。 ・どんきち さん提示のものは、 「複数のクラス(A,B,C…)が持っている共通イベントを纏める」 「クラス(オブジェクト、コントロール)配列のイベントを纏める」 という利用例の違いはありますが、わたしが書いている『擬似からの脱却』と同等の 考え方(利用側と対象クラスの間に、イベントを纏めるクラスを挟む)ですね。 最近は、大分「クラスを2重に使う」という、この手の方法が浸透しましたが、 昔はどうだったんでしょうね(VBA界では)? 記事でも書いてますが [§9 擬似の足枷] この機能は「Excel2000(1999/6 リリース) からサポート」されています。 まさか、5年もの間、誰も気付かなかった? そんな馬鹿な (oT▽T)ノ彡 『いや、実は昔っから知ってたし、使ってた』という識者が居なかったとは 思えないんだけど…… 解説するのが面倒だから言わなかったって人、居るんだろうなぁ〜 (実際、とっても面倒だったし) 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 (1)は、ここまで(理解しながら読むには長いし疲れますね・・・) |
|
投稿日時: 12/03/18 04:37:28
投稿者: ろひ
|
---|---|
VBAにおいては、MVC(Model-View-Controller)モデルの文脈が、暗黙に扱われることは多いという印象を持っていたのですが、yayadonさんからObserverパターンの具体的なコード例が提示されてしまい、このトピック自体に圧倒されています。
角田 さんの引用: 濃度とスピードについていくことや振り返って確認するのも苦労しますね。。 VBAクラスの制限と工夫や考え方のながれについて、示唆に富んだものが多いです。 ななめ読みや後追いな人向け、な感もあるんですが、ほぼ自分の後学用のメモを提示します。 ⇒読み解ききれてない&消化不足なところ多数なので、ツッコミ歓迎です。 テキストエディタへのベタ打ちなので、見にくくてすみません。 (※yayadonさんからObserverパターン提示前までのメモです。茶文字はトピック内コメントの検索用です。) ---------------------------------------------------------------------- ≪主な出来事≫ ◇トピック開設:挨拶、テーマ掲示 12/02/25 10:30:52 みそじのおじさん ◇コレクションクラスの話題からの派生、作成/連携の例として、 【視覚的にグループ分けが出来るクラス】コード掲示 12/03/02 00:02:37 みそじのおじさん 正しい利用手順の掲示 12/03/03 20:08:40 みそじのおじさん ◇階層構造の見直し、クラスはどういうメンバを持つべきか、を考えて修正 【みそじのおじさんのクラス(修正中)】コード提示 12/03/11 12:40:08 月さん ◇「強い参照」の循環部分の指摘 12/03/13 06:21:22 yayadonさん 説明一部誤りに伴うイベントの再解説 12/03/15 08:22:58 yayadonさん ◇【2クラス構成にした改訂版】コード提示 12/03/13 18:04:18 Abyssさん コード利用の補足 12/03/14 13:41:31 月さん ◇【メソッドをイベントに変更→親オブジェクト取得する共通イベント定義版】コード提示 12/03/15 12:11:17 月さん 図解による参照のながれについて確認 12/03/15 17:41:35 角田さん 回答と補足 12/03/15 18:20:47 月さん ◇各修正・改訂コードの方向性について補足 12/03/16 03:12:54 yayadonさん --------------------------------------------------------------------- ≪テーマ別まとめ≫ ●VBAにおけるクラスとそのメリットとは? - 初学・復習向け ◇Visual Basic 6 クラスモジュール講座 - CavalierLab http://homepage1.nifty.com/CavalierLab/lab/vb/clsmdl/index.html ◇疑似からの脱却 - Addin Box http://www.h3.dion.ne.jp/~sakatsu/Breakthrough_P-Ctrl_Arrays.htm ◇クラスモジュールを使った究極のVBAプログラム - 旧moug(Webアーカイブ) http://web.archive.org/web/20071014002909/http://www.moug.net/skillup/opm/opm08-01.htm ◇コレクションクラスと列挙子(イテレータ) - VBAクラス研究室(1)|12/02/27 01:20:12 yayadonさん ⇒Count/Item /_NewEnumのメンバー関係と必要性を「みかん箱からみかんを取り出すのは誰か? 」で解説 ●こんなクラスを使っています! - おすすめVBAクラス ◇ラベル点滅クラス - Addin Box http://www.h3.dion.ne.jp/~sakatsu/Excel_Tips09.htm ◇nanbu/CSVParser - github https://github.com/nanbu/CSVParser ◇nanbu/XArray - github https://github.com/nanbu/XArray ◇honda0510/Google-Calendar-Library-for-VBA - github https://github.com/honda0510/Google-Calendar-Library-for-VBA ●VBAクラスの制限事項 - 欠点転ずれば可能性に繋がる ほぼ全項の解決方法において、自他コードともに実装やデバッグ/テスト工数に影響します。 そのため、そもそもの基本として、他言語以上に全てにおいて「わかりやすい命名や構造化」が重要になるでしょう。 ◇インスタンス作成時(コンストラクタ)に引数を渡せない ⇒Initメソッドを実装し、Newした後に呼んでもらう 【VBAで引数付きのコンストラクタを定義する方法】コード提示 12/03/05 21:21:35 どんきちさん さらにイベント中継クラスを実装する 【参照関係が3階層のときのPublicなクラスの参照(1)】コード提示 12/03/14 22:47:03 どんきちさん 【参照関係が3階層のときのPublicなクラスの参照(2)】コード提示 12/03/14 22:54:47 どんきちさん ⇒多態性(ポリモーフィズム)の実装、イベントリスナーの設計が悩ましい ○WithEvents 【WithEventsを使って複数のクラスの同じ形式のイベントを1つのイベントプロシージャで受け取る(1)】コード提示 12/03/07 21:39:48 どんきちさん 【WithEventsを使って複数のクラスの同じ形式のイベントを1つのイベントプロシージャで受け取る(2)】コード提示 12/03/07 21:44:55 どんきちさん ○循環参照を回避するためのTearDownメソッド実装 ⇒WindowsAPIの64bit対応をするなら、ポインタ操作なしのこの手法も必要 ○TearDownメソッド不要のための「弱参照 (weak reference) 」 ⇒ObjPtr()で値取得(Long型変数)し、利用時はObject型に代入してから使う (※コレクション側がそれを扱うクラスよりも寿命が長いため、メンバとしてコレクションへの参照を保持しているクラス部分に適用)【CWeakReferenceクラス】コード提示 12/03/04 05:35:00 yayadonさん ◇(組込み以外の)新規作成クラスへ別クラスを継承できない ⇒インタフェース継承(Implements)の呼び出しは可能 …だが間接的ゆえ若干面倒 【Implementsを使用してメンバを隔離】コードの提示 12/03/06 21:31:51 YU-TANGさん ⇒実行時バインディング …VBA的だがコンパイラのエラーチェックが甘くなる (※いずれもコンポジション(クラスのメソッドを呼び出すメソッド)と組み合わせて実装) ◇パッケージ(可視性を担保)できる手段が限定的 ⇒Private,Publicに加え、Friendを活用 【Friendスコープのメソッドと速度について】コード提示 12/03/06 12:39:36 Abyssさん さらなる解説 12/03/07 03:40:39 yayadonさん 補足 12/03/07 04:42:58 yayadonさん さらなるテスト 12/03/07 13:35:31 Abyssさん 不利なケース 12/03/16 03:52:11 yayadon ⇒ブックを分けて「Instancing」値を2(PublicNotCreatable)にする … 被参照ブック/アドインが開いている必要アリ ◇リフレクション機能が限定的 ⇒オブジェクトのクラス名取得(TypeName関数) ⇒オブジェクトのメソッド名からメソッド呼び出し(CallByName関数) 【VBAでクラスのインスタンスを生成するリフレクション機能を実現】コード提示 12/03/07 21:39:48 どんきちさん ◇情報隠蔽・カプセル化に縛りがある [標準モジュール]クラススコープのメソッド・属性を定義 [クラスモジュール・フォームモジュール]インスタンススコープのメソッド・属性を定義 (※Privateであるべきクラススコープの属性をPublicにせざるを得ない) 【別プロジェクトのクラスの利用】コード提示 12/03/15 21:49:30 どんきちさん ◇オーバーロード(同名の関数やメソッドの多重定義)が出来ない ⇒疑似オーバーロード:引数を省略(Optional)、任意数の引数(ParamArray) … 多用すると可読性低下を招く ⇒引数をVariant型/Object型にする …VBA的だがコンパイラのエラーチェックが甘くなる ◇エラーオブジェクト(ErrObjectクラス)の機能が貧弱 ●その他 - 紹介・検証、VBAクラス関連のトピックなど ◇参照設定とNewキーワードとCreateObject関数 http://www.moug.net/faq/viewtopic.php?t=60032 ◇何をカプセル化と呼ぶのか http://www.moug.net/faq/viewtopic.php?t=60181 ◇CELLの値とリアルタイムに表示が連動するUserForm用コントロール http://www.moug.net/faq/viewtopic.php?t=60582 ◇VBAでメソッドチェーンの例 - github:gist https://gist.github.com/1956111 ◇Letステートメント - PC研究室2nd|11/10/22 05:36:15 yayadonさん ◇ByRef渡しとVariant型 - PC研究室2nd|11/12/08 15:42:55 yayadonさん ◇列挙とブック(とシートとクラス)・・ - ExcelQ&Aさろん(VBA)(過去ログ) http://excelfactory.net/excelboard/excelvba/cfs.cgi?word=%97%F1%8B%93%82%C6%83u%83b%83N&andor=and&logs=14.txt ◇参照の引数は実引数のいわゆる別名として扱う - VBAクラス研究室(1)|12/02/28 13:01:48 yayadonさん ◇イベントの場合は循環しているように見えてもOK - VBAクラス研究室(1)|12/03/13 06:21:22 yayadonさん ◇親オブジェクトへの参照を保持せずに親オブジェクトへの参照を得る方法 - github:gist https://gist.github.com/2041685 ◇クラス内でAddressOfに指定するプロシージャ - ExcelQ&Aさろん(VBA)(過去ログ) http://excelfactory.net/excelboard/excelvba/cfs.cgi?word=143081&andor=and&logs=23.txt ◇Calendar Controlのクラス化 - VBAクラス研究室(1)|12/03/15 03:15:42 Abyssさん --------------------------------------------------------------------- 抜けとか漏れとかカテゴリ誤りとかありそうで怖いのですが…寝ます。 |
|
投稿日時: 12/03/18 10:46:34
投稿者: 月
|
---|---|
ひ〜
月 さんの引用: 一箇所修正漏れがあって、肝心の親オブジェクトを取得できていませんでした〜 すみませんでした〜修正しました〜 ' 子から連絡があったら、自分の代理人を通して子と連絡をとる Private Sub m_ParentAgent_OnGetParent(Parent As Object) ↓ Private Sub m_ParentAgent_OnConnect(Parent As Object) |
|
投稿日時: 12/03/18 11:20:27
投稿者: 月
|
---|---|
ここも話がややこしいので、よっぽど興味のある方以外はスルーしてくださってOKです。
月 さんの引用: 修正しました。 これで宿題が片付きました。 みそじのおじさんのクラス 月修正版 https://gist.github.com/1970700 ※呼び出しが入り組んでいて複雑です。 修正前は、1対1の親子でしか使えない、この方法を使っていました。 月 さんの引用: すると、今回の修正によって、親(clsContainer)が子(clsMoveItems)のイベントを受け取り、子も親のイベントを受け取るという状況になり、「モジュール間で循環参照が定義されています。」とコンパイルエラーになってしまいました。 再現コード エラー: モジュール間で循環参照が定義されています。 https://gist.github.com/2067353 そこで、1対nの親子でも使えるこの方法を使いました。 月 さんの引用: そうすることにより、親(clsContainer)が子(clsMoveItems)のイベントを受け取ることはなくなり、コンパイルエラーが解消されました。 |
|
投稿日時: 12/03/18 14:18:23
投稿者: ろひ
|
---|---|
Abyss さんの引用: コメントありがとうございます。「Friendスコープとメソッド」のブランチですね。 この後の、Abyssさんとのやりとりをどうしようか考えて止めてしまってました。 結論が出ない(後続のコメントがある)うちは、(進行中)と記しておきたいと思います。 |
|
投稿日時: 12/03/18 14:47:00
投稿者: 角田
|
---|---|
「7セグメント」デジタル時計 を「タイマーコントロール」クラスの
|
|
投稿日時: 12/03/18 23:19:25
投稿者: simple
|
---|---|
こんばんは。
|
|
投稿日時: 12/03/19 01:20:11
投稿者: マコ
|
---|---|
みそじのおじさん
みそじのおじさん さんの引用: そんなお忙しい中に、初心者向けのクラスを考えてくださり、ありがとうございます。 Rangeオブジェクトの機能拡張クラスですか?!楽しみです。 できましたら、公開してくださいね。 角田 さんの引用: 角田さん やさしいクラスのチョイス、ありがとうございます! さっそくダウンロードして、試してみました。 解説されている通りにやってみたら、ピコン♪ピコン♪と音がなってラベルが点滅、 わぁ、動いた〜! と動かせただけでも嬉しくなりました。 クラスモジュールのインポートってどうやるの? でさっそくと迷ってしまったので 今日は使ってみるところまででしたが、どんなコードが書かれているか、これから拝見します。 ありがとうございました。 |
|
投稿日時: 12/03/19 03:03:36
投稿者: yayadon
|
---|---|
# Friend の件 遅くなりました。
WinXP SP3 & Excel 2002 SP3 .xls ' 10% 程の差が出る 17.35913 public 15.92188 friend 17.40698 public 15.78101 friend 17.375 public 15.76611 friend 17.32788 public 15.78101 friend WinXP SP3 & VB6 SP6 (Ctrl+F5) デバッグ ' 15% 程の差が出る 19.0459 public 16.3291 friend 19.07813 public 16.18701 friend 19.10986 public 16.20313 friend 18.73389 public 16.17188 friend WinXP SP3 & VB6 SP6 P-Code compiled EXE ' 差がハッキリ出ない 16.32799 --- public 16.1411 --- friend 16.32799 --- public 16.12486 --- friend 16.31211 --- public 16.1571 --- friend 16.34386 --- public 16.23411 --- friend WinXP SP3 & VB6 SP6 Native compiled EXE ' かなり明確に差が出る 3.500226 --- public 0.5002266 --- friend 3.485227 --- public 0.5002266 --- friend 3.500226 --- public 0.5002266 --- friend 3.483875 --- public 0.4999883 --- friend 確かに Friend の方が速いようです。 ちなみに, XP SP3 から持ってきた .xls でやると, 5.643066 public 5.533203 friend 5.64502 public 5.545898 friend 5.645996 public 5.53418 friend 5.652832 public 5.525391 friendとなったり 5.641602 public 6.016602 friend 5.647461 public 6.003906 friend 5.646484 public 6.018555 friend 5.649414 public 6.005859 friendとなったりします。 Friend ががんばっている時でも, VB6 SP6 P-Code compiled EXE と同様に,差がハッキリ出ないので, そのあたりにヒントがありそうですが, 私の理解を超えるのでやめておきます。 VB6 で作成した EXE は, WinXP SP3 上での傾向と同じでした。 Win7 SP1 & VB6 SP6 P-Code compiled On WinXP SP3 5.41423 --- public 5.218867 --- friend 5.406902 --- public 5.219188 --- friend 5.439223 --- public 5.214793 --- friend 5.41907 --- public 5.212149 --- friend Win7 SP1 & VB6 SP6 Native compiled On WinXP SP3 ' かなり明確に差が出る 1.132828 --- public 0.1911563 --- friend 1.144242 --- public 0.1902227 --- friend 1.14882 --- public 0.188918 --- friend 1.150176 --- public 0.1898086 --- friend |
|
投稿日時: 12/03/19 06:19:02
投稿者: みそじのおじさん
|
---|---|
みなさん、おはようございます。
yayadon さんの引用: 「デザインパターン」という言葉は目にした事はありますが、VBAで実際のコードを 元にした例は見た事がありませんでした。大変勉強になります。 yayadon さんの引用: これも時期を見てお願いできないでしょうか?完全に理解出来ていないからだと思い ますが、自分の中から出てきません^^; ▼マコさん お気使いありがとうございます^^ 「1つのクラスで」なんて自分でいっているのに「なんでこーなるの!」と 頭を抱えております^^ ▼simpleさん simpleさんが「IT」とは無縁とは大変驚きました。趣味レベルだとはとても思えませ んが・・(VBAにRubyにC? etc? YU-TANGさんがプロフィールに「サンデープログラマ」 と書かれているのですが、私が日曜だけやっていると一生皆様に追いつけません^^) 「他言語を引き合いに」私はとても興味深いです。 「他言語だとこうだ!ではVBAではどうする?」こういった切り口は大変興味があります。 年度末で大変忙しい時期だと思いますが、皆様引き続きよろしくお願い致します。 |
|
投稿日時: 12/03/19 08:06:40
投稿者: kumatti
|
---|---|
# 角田さんからサンプルが出されたので不要ですが。
|
|
投稿日時: 12/03/19 10:36:10
投稿者: yayadon
|
---|---|
# こんなパターンな "感じ" というものなので,
みそじのおじさん さんの引用: まず,CObserver クラスの名前を CPersonObserver に変えることにします。 汎用的ではないので変えておくということです。 また, 本来は,IPersonObserver インターフェースを作成して, それを CPersonObserver クラスで実装という形です。 ですが, VBA では,自動でクラス インターフェースが作成されるのと, コンクリート Observer クラスは,一種類しか作らないので,省略します。 '' CPersonObserver クラス --- 抽象クラス/具象クラス兼用 Option Explicit Event OnLevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, blnCancel As Boolean) Event OnLevelChanged(clsPerson As CPerson) '' 以下,Update(CPerson) メソッド Public Sub LevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, blnCancel As Boolean) RaiseEvent OnLevelChanging(clsPerson, lngNewLevel, blnCancel) End Sub Public Sub LevelChanged(clsPerson As CPerson) RaiseEvent OnLevelChanged(clsPerson) End Sub ''----- Private Sub Class_Terminate() Debug.Print "CPersonObserver: Class_Terminate." End Sub ----- そして, Subject インターフェースは,以下のようにします。 名前は,IPersonSubject とします。 Notify(CPerson) 群は,インターフェースに入れず,コメント アウトします。 VBA では,インターフェースの継承もできない関係もあって, こうすることにしました。 コメント として残しておくのは, 上記の CPersonObserver クラスと対になっているのを確認するためです。 '' IPersonSubject Option Explicit Public Sub Attach(clsObserver As CPersonObserver) ' clsObserver は,イベント通知用 End Sub '' 無くても可。 Public Sub Detach(clsObserver As CPersonObserver) ' clsObserver は,イベント通知用 End Sub '' 以下 Notify(CPerson) ''Public Sub LevelChanging(clsPerson As CPerson, _ '' lngNewLevel As Long, blnCancel As Boolean) ''End Sub '' ''Public Sub LevelChanged(clsPerson As CPerson) ''End Sub ----- Subject (のベースとなる)クラスには, Observer のインスタンス参照用のモジュールレベル変数や Attatch や Detach などの中身のコードを書きます。 実際の Subject クラス である CPerson に, それらのコードがノイズになるのを防ぐのが狙いです。 名前は,IPersonSubjectImpl とします。 C++ で COM オブジェクトを作るためのライブラリに ATL というものがあるのですが, ヘルパー的な実装を予め提供するためのクラスの名前が 〜〜Impl という名前なのでそれに右ならえしました。 .NET 的にやるならば,PersonObservable のような名前でも構いません。 C++ や C# 等ではクラスの継承ができるので, この IPersonSubjectImpl を継承して,CPerson を作りますが, VBA のクラスではやれないので, CPerson の内部に Class_Initialize 時に作成します。 CPerson にプロパティを作成して, 外から設定(オブジェクト インジェクションといいます)しても構いません。 '' IPersonSubjectImpl Option Explicit Implements IPersonSubject '' Detach が必要ない場合は,動的配列でも構いません。 Private m_observers As Collection ' Private Sub Class_Initialize() Set m_observers = New Collection End Sub Private Sub Class_Terminate() Debug.Print "IPersonSubjectImpl: Class_Terminate." End Sub Private Sub IPersonSubject_Attach(clsObserver As CPersonObserver) Dim observer As CPersonObserver '' 重複確認 --- 単にエラー処理でも可。 For Each observer In m_observers If observer Is clsObserver Then Debug.Print "オブザーバーは登録済み。" Exit Sub End If Next Call m_observers.Add(clsObserver) End Sub Private Sub IPersonSubject_Detach(clsObserver As CPersonObserver) Dim observer As CPersonObserver '' 登録済みのもののみ削除 --- 単にエラー処理でも可。 For Each observer In m_observers If observer Is clsObserver Then Call m_observers.Remove(clsObserver) Exit Sub End If Next Debug.Print "未登録のオブザーバーに対して削除。" End Sub '' 以下 Notify(CPerson) Public Sub LevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, blnCancel As Boolean) Dim observer As CPersonObserver For Each observer In m_observers Call observer.LevelChanging(clsPerson, lngNewLevel, blnCancel) Next End Sub Public Sub LevelChanged(clsPerson As CPerson) Dim observer As CPersonObserver For Each observer In m_observers Call observer.LevelChanged(clsPerson) Next End Sub ※ Collection でなく Scripting.Dictionary だと 参照設定が必要ですが,実装は楽になるかも。 ----- CPerson クラスは, Observer クラス(CPersonObserver)をモジュール レベル変数で持つのではなく, ヘルパーとして実装した IPersonSubjectImpl クラスを モジュール レベル変数で持つ形に変えます。 Observer クラスが消える関係で,いわゆるノイズが消えて,スッキリします。 # 上の IPersonSubjectImpl クラスのコードが, # 下の CPerson クラスに混ざっていたらスッキリしないですよね? # 実効速度が遅くなりそうで,かえって,スッキリしない人も, # ここは,そういうことにしておいてください。(笑) '' CPerson Option Explicit Implements IPersonSubject Private m_personSubject As IPersonSubjectImpl Private m_userName As String Private m_level As Long ' Public Property Let UserName(strValue As String) m_userName = strValue End Property Public Property Get UserName() As String UserName = m_userName End Property Public Property Let Level(ByVal lngValue As Long) If m_level <> lngValue Then If m_personSubject Is Nothing Then m_level = lngValue Exit Property End If Dim blnCancel As Boolean Call m_personSubject.LevelChanging(Me, lngValue, blnCancel) If Not blnCancel Then m_level = lngValue Call m_personSubject.LevelChanged(Me) End If End If End Property Public Property Get Level() As Long Level = m_level End Property Private Sub Class_Initialize() Set m_personSubject = New IPersonSubjectImpl End Sub Private Sub Class_Terminate() Debug.Print "CPerson - " & m_userName & ": Class_Terminate." End Sub '' 以下,IPersonSubject インターフェース Private Sub IPersonSubject_Attach(clsObserver As CPersonObserver) Dim personSubject As IPersonSubject Set personSubject = m_personSubject Call personSubject.Attach(clsObserver) End Sub Private Sub IPersonSubject_Detach(clsObserver As CPersonObserver) Dim personSubject As IPersonSubject Set personSubject = m_personSubject Call personSubject.Detach(clsObserver) End Sub ----- CPersons クラス (CPerson クラスのコレクション) は,そのままです。 '' CPersons クラス --- CPerson のコレクション Option Explicit Private m_persons As Collection ' Public Function Add(strUserName As String, _ Optional strDefaultLevel As Long = 3) As CPerson Dim clsPersons As CPersons Dim clsPerson As CPerson '' 重複確認 もしくは Add時にエラー処理で For Each clsPerson In m_persons If clsPerson.UserName = strUserName Then MsgBox "既にその名前は登録されています。" Exit Function End If Next Set clsPerson = New CPerson clsPerson.UserName = strUserName clsPerson.Level = strDefaultLevel Call m_persons.Add(clsPerson, strUserName) Set Add = clsPerson End Function Public Property Get Item(strUserName As String) As CPerson Set Item = m_persons.Item(strUserName) End Property Private Sub Class_Initialize() Set m_persons = New Collection End Sub Private Sub Class_Terminate() Debug.Print "CPersons: Class_Terminate." End Sub ----- テスト用のコンテナ。 CObserver --> CPersonObserver ISubject --> IPersonSubject に名前が変わった関係で,変更になっています。 '' CTestContainer Option Explicit Private m_clsPersons As CPersons Private WithEvents m_clsObserver As CPersonObserver ' Private Sub Class_Initialize() Dim clsObserver As CPersonObserver Dim itfSubject As IPersonSubject Dim clsPersons As CPersons Dim vntElement As Variant Set clsObserver = New CPersonObserver Set clsPersons = New CPersons For Each vntElement In Array("yayadonさん", _ "ろひさん", _ "simpleさん", _ "月さん", _ "ゴマさん", _ "kumattiさん", _ "YU-TANGさん", _ "Kanabunさん", _ "Abyssさん", _ "みそじのおじさん") Set itfSubject = clsPersons.Add(CStr(vntElement)) Call itfSubject.Attach(clsObserver) Next Set m_clsPersons = clsPersons Set m_clsObserver = clsObserver End Sub Private Sub Class_Terminate() Debug.Print "CTestContainer: Class_Terminate." End Sub '' Test CPersons.Add Public Sub TestAdd(strUserName As String, Optional strLevel As Long = 3) Call m_clsPersons.Add(strUserName) End Sub '' Test CPersons.Item Public Function TestItem(strUserName As String) As CPerson Set TestItem = m_clsPersons.Item(strUserName) End Function Private Sub m_clsObserver_OnLevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, _ blnCancel As Boolean) Dim result As VbMsgBoxResult Dim strMsg As String strMsg = clsPerson.UserName & "のレベルが" & vbCrLf & _ clsPerson.Level & " から" & lngNewLevel & " に" & vbCrLf & _ "変更されようとしています。" & vbCrLf & _ "許可しますか?" result = MsgBox(strMsg, vbYesNo Or vbDefaultButton2 Or vbInformation, _ "変更の確認: ユーザーのレベル") If result = vbNo Then blnCancel = True End If End Sub Private Sub m_clsObserver_OnLevelChanged(clsPerson As CPerson) MsgBox clsPerson.UserName & "のレベルが " & _ clsPerson.Level & " に変化しました。" End Sub ----- テスト メソッド Private Sub Test1() Dim clsTestContainer As CTestContainer Dim clsPerson As CPerson Debug.Print "Test 開始" Set clsTestContainer = New CTestContainer MsgBox "Test CPersons.Add" Call clsTestContainer.TestAdd("みそじのおじさん") MsgBox "Test CPersons.Item" Set clsPerson = clsTestContainer.TestItem("みそじのおじさん") '' イベントのテストも兼ねてます。 MsgBox "Test CPerson.Level" clsPerson.Level = clsPerson.Level + 1 '' CPerson クラスのテスト MsgBox clsPerson.UserName & " の現在のレベルは " & _ clsPerson.Level & " です。" Set clsPerson = Nothing Set clsTestContainer = Nothing ' Class_Terminate がすべて呼ばれるハズ MsgBox "終了" Debug.Print "Test 終了" End Sub 出力 Test 開始 CTestContainer: Class_Terminate. CPersons: Class_Terminate. CPerson - yayadonさん: Class_Terminate. IPersonSubjectImpl: Class_Terminate. CPerson - ろひさん: Class_Terminate. IPersonSubjectImpl: Class_Terminate. CPerson - simpleさん: Class_Terminate. IPersonSubjectImpl: Class_Terminate. CPerson - 月さん: Class_Terminate. IPersonSubjectImpl: Class_Terminate. CPerson - ゴマさん: Class_Terminate. IPersonSubjectImpl: Class_Terminate. CPerson - kumattiさん: Class_Terminate. IPersonSubjectImpl: Class_Terminate. CPerson - YU-TANGさん: Class_Terminate. IPersonSubjectImpl: Class_Terminate. CPerson - Kanabunさん: Class_Terminate. IPersonSubjectImpl: Class_Terminate. CPerson - Abyssさん: Class_Terminate. IPersonSubjectImpl: Class_Terminate. CPerson - みそじのおじさん: Class_Terminate. IPersonSubjectImpl: Class_Terminate. CPersonObserver: Class_Terminate. Test 終了 上記のように, VBA の場合,IPersonSubjectImpl クラスのインスタンスが増えてしまうのと, 今回は汎用化していないので, プロパティごとにNotifyメソッドを用意している形になって, スッキリする以外は,不利なところが目立つので, CPerson クラスに直接,CPersonObserver の参照を持つ形の方がいいかもしれません。 上記のコードから, IPersonSubjectImpl クラスを無くすのは,簡単にやれると思います。 IPersonSubjectImpl の中身を CPerson クラスに持ってくるだけです。たぶん。 |
|
投稿日時: 12/03/19 11:45:01
投稿者: yayadon
|
---|---|
yayadon さんの引用: IPersonSubjectImpl クラスで Implements ISubject Implements IPersonSubject のように,2つ実装すればいいだけの話でした。 '' ISubject --- 汎用 Option Explicit Public Sub Attach(vntObserver As Variant) ' vntObserver は,イベント通知用 End Sub Public Sub Detach(vntObserver As Variant) ' vntObserver は,イベント通知用 End Sub '' IPersonSubject Option Explicit ''Implements ISubject '' ''Private Sub ISubject_Attach(vntObserver As Variant) '' ''End Sub '' ''Private Sub ISubject_Detach(vntObserver As Variant) '' ''End Sub '' 以下 Notify(CPerson) Public Sub LevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, blnCancel As Boolean) End Sub Public Sub LevelChanged(clsPerson As CPerson) End Sub '' IPersonSubjectImpl Option Explicit Implements ISubject Implements IPersonSubject Private m_observers As Collection ' Private Sub Class_Initialize() Set m_observers = New Collection End Sub Private Sub Class_Terminate() Debug.Print "IPersonSubjectImpl: Class_Terminate." End Sub Private Sub ISubject_Attach(vntObserver As Variant) '同様 End Sub Private Sub ISubject_Detach(vntObserver As Variant) '同様 End Sub Private Sub IPersonSubject_LevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, blnCancel As Boolean) '同様 End Sub Private Sub IPersonSubject_LevelChanged(clsPerson As CPerson) '同様 End Sub |
|
投稿日時: 12/03/19 12:46:51
投稿者: yayadon
|
---|---|
# VBA のクラスで,こんなにややこしいことをしたのは初めてなので,
Option Explicit Event OnLevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, blnCancel As Boolean) Event OnLevelChanged(clsPerson As CPerson) Public Sub LevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, blnCancel As Boolean) RaiseEvent OnLevelChanging(clsPerson, lngNewLevel, blnCancel) End Sub Public Sub LevelChanged(clsPerson As CPerson) RaiseEvent OnLevelChanged(clsPerson) End Sub Private Sub Class_Terminate() Debug.Print "CPersonObserver: Class_Terminate." End Sub '' ISubject クラス --- 汎用 Subject インターフェース Option Explicit Public Sub Attach(vntObserver As Variant) ' vntObserver は,イベント通知用 End Sub '' 無くても可。 Public Sub Detach(vntObserver As Variant) ' vntObserver は,イベント通知用 End Sub '' IPersonSubject クラス --- CPerson 用 Subject インターフェース Option Explicit ''Implements ISubject '' ''Private Sub ISubject_Attach(vntObserver As Variant) '' ''End Sub '' ''Private Sub ISubject_Detach(vntObserver As Variant) '' ''End Sub '' 以下 Notify(CPerson) Public Sub LevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, blnCancel As Boolean) End Sub Public Sub LevelChanged(clsPerson As CPerson) End Sub '' IPersonSubjectImpl クラス --- 具象 Subject (ヘルパー オブジェクト) Option Explicit Implements ISubject Implements IPersonSubject '' Detach が必要ない場合は,動的配列でも構いません。 Private m_observers As Collection ' Private Sub Class_Initialize() Set m_observers = New Collection End Sub Private Sub Class_Terminate() Debug.Print "IPersonSubjectImpl: Class_Terminate." End Sub '' ISubject インターフェース Private Sub ISubject_Attach(vntObserver As Variant) Dim observer As CPersonObserver '' 重複確認 --- 単にエラー処理でも可。 For Each observer In m_observers If observer Is vntObserver Then Debug.Print "オブザーバーは登録済み。" Exit Sub End If Next Call m_observers.Add(vntObserver) End Sub Private Sub ISubject_Detach(vntObserver As Variant) Dim observer As CPersonObserver '' 登録済みのもののみ削除 --- 単にエラー処理でも可。 For Each observer In m_observers If observer Is vntObserver Then Call m_observers.Remove(vntObserver) Exit Sub End If Next Debug.Print "未登録のオブザーバーに対して削除。" End Sub '' IPersonSubject インターフェース Private Sub IPersonSubject_LevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, blnCancel As Boolean) Dim observer As CPersonObserver For Each observer In m_observers Call observer.LevelChanging(clsPerson, lngNewLevel, blnCancel) Next End Sub Private Sub IPersonSubject_LevelChanged(clsPerson As CPerson) Dim observer As CPersonObserver For Each observer In m_observers Call observer.LevelChanged(clsPerson) Next End Sub '' CPerson クラス --- 具象 Subject Option Explicit Implements ISubject Private m_personSubject As IPersonSubject Private m_userName As String Private m_level As Long ' Public Property Let UserName(strValue As String) m_userName = strValue End Property Public Property Get UserName() As String UserName = m_userName End Property Public Property Let Level(ByVal lngValue As Long) If m_level <> lngValue Then If m_personSubject Is Nothing Then m_level = lngValue Exit Property End If Dim blnCancel As Boolean Call m_personSubject.LevelChanging(Me, lngValue, blnCancel) If Not blnCancel Then m_level = lngValue Call m_personSubject.LevelChanged(Me) End If End If End Property Public Property Get Level() As Long Level = m_level End Property Private Sub Class_Initialize() Set m_personSubject = New IPersonSubjectImpl End Sub Private Sub Class_Terminate() Debug.Print "CPerson - " & m_userName & ": Class_Terminate." End Sub '' ISubject インターフェース Private Sub ISubject_Attach(vntObserver As Variant) Dim subject As ISubject Set subject = m_personSubject Call subject.Attach(vntObserver) End Sub Private Sub ISubject_Detach(vntObserver As Variant) Dim subject As ISubject Set subject = m_personSubject Call subject.Detach(vntObserver) End Sub '' CPersons クラス (CPerson クラスのコレクション) Option Explicit Private m_persons As Collection ' Public Function Add(strUserName As String, _ Optional strDefaultLevel As Long = 3) As CPerson Dim clsPersons As CPersons Dim clsPerson As CPerson '' 重複確認 もしくは Add時にエラー処理で For Each clsPerson In m_persons If clsPerson.UserName = strUserName Then MsgBox "既にその名前は登録されています。" Exit Function End If Next Set clsPerson = New CPerson clsPerson.UserName = strUserName clsPerson.Level = strDefaultLevel Call m_persons.Add(clsPerson, strUserName) Set Add = clsPerson End Function Public Property Get Item(strUserName As String) As CPerson Set Item = m_persons.Item(strUserName) End Property Private Sub Class_Initialize() Set m_persons = New Collection End Sub Private Sub Class_Terminate() Debug.Print "CPersons: Class_Terminate." End Sub '' CTestContainer --- テスト用 コンテナ Option Explicit Private m_clsPersons As CPersons Private WithEvents m_clsObserver As CPersonObserver ' Private Sub Class_Initialize() Dim clsObserver As CPersonObserver Dim itfSubject As ISubject Dim clsPersons As CPersons Dim vntElement As Variant Set clsObserver = New CPersonObserver Set clsPersons = New CPersons For Each vntElement In Array("yayadonさん", _ "ろひさん", _ "simpleさん", _ "月さん", _ "ゴマさん", _ "kumattiさん", _ "YU-TANGさん", _ "Kanabunさん", _ "Abyssさん", _ "みそじのおじさん") Set itfSubject = clsPersons.Add(CStr(vntElement)) Call itfSubject.Attach(clsObserver) Next Set m_clsPersons = clsPersons Set m_clsObserver = clsObserver End Sub Private Sub Class_Terminate() Debug.Print "CTestContainer: Class_Terminate." End Sub '' Test CPersons.Add Public Sub TestAdd(strUserName As String, Optional strLevel As Long = 3) Call m_clsPersons.Add(strUserName) End Sub '' Test CPersons.Item Public Function TestItem(strUserName As String) As CPerson Set TestItem = m_clsPersons.Item(strUserName) End Function Private Sub m_clsObserver_OnLevelChanging(clsPerson As CPerson, _ lngNewLevel As Long, _ blnCancel As Boolean) Dim result As VbMsgBoxResult Dim strMsg As String strMsg = clsPerson.UserName & "のレベルが" & vbCrLf & _ clsPerson.Level & " から " & lngNewLevel & " に" & vbCrLf & _ "変更されようとしています。" & vbCrLf & _ "許可しますか?" result = MsgBox(strMsg, vbYesNo Or vbDefaultButton2 Or vbInformation, _ "変更の確認: ユーザーのレベル") If result = vbNo Then blnCancel = True End If End Sub Private Sub m_clsObserver_OnLevelChanged(clsPerson As CPerson) MsgBox clsPerson.UserName & " のレベルが " & _ clsPerson.Level & " に変化しました。" End Sub '' Test 用メソッド Private Sub Test1() Dim clsTestContainer As CTestContainer Dim clsPerson As CPerson Debug.Print "Test 開始" Set clsTestContainer = New CTestContainer MsgBox "Test CPersons.Add" Call clsTestContainer.TestAdd("yayadonさん") MsgBox "Test CPersons.Item" Set clsPerson = clsTestContainer.TestItem("yayadonさん") MsgBox "Test CPerson.Level" clsPerson.Level = clsPerson.Level + 1 MsgBox clsPerson.UserName & " の現在のレベルは " & _ clsPerson.Level & " です。" Set clsPerson = Nothing Set clsTestContainer = Nothing MsgBox "終了" Debug.Print "Test 終了" End Sub |
|
投稿日時: 12/03/19 23:35:52
投稿者: どんきち
|
---|---|
みそじのおじさん さんの引用: ということなので、実践的でもないし、インパクトが強くもありませんが コードが短めで、標準モジュールとクラスモジュールの違いがわかりそうな例を1つ 提示してみます。 ログファイルのファイル名が決めうちの場合と、可変の場合について、 標準モジュールとクラスモジュールで作ってみました。 ファイル名きめうちだとクラスモジュールにしてもうまく動きません。 クラスを使って、インスタンスごとファイルを表す変数が存在しますが その変数が指し示すファイルが一個なので、OPEN、WRITE、CLOSEの順番が 守られていないとエラーになってしまいます。 ログファイルのファイル名がパラメタで渡す場合は、ファイル名がダブらない という前提つきになりますが正常に動きます。 標準モジュールをクラスモジュールにしてはいけない場合もあるってことです。 また、この例ではログファイルのオープン、テキスト出力、クローズですが、 保護されているExcelシートに対するデータ操作について、 以下のような読み替えても同じことがいえます。 ・ファイル名が固定が可変か → 操作するシートが固定か可変か ・ファイルのオープン → シートの保護解除 ・ファイルのテキスト出力 → シートのデータ編集 ・ファイルのクローズ → シートの保護 '*** 標準モジュール:modLogFile01 *** '前提条件 'OpenLogは最初に1回だけ実行 'CloseLogは最後に1回だけ実行 'ファイル名固定 Option Explicit Public Enum genmLogLevelM1 ltm1Info ltm1Warn ltm1Error End Enum Private mlngFno As Long Private menmLogLevel As genmLogLevelM1 '出力する最低レベルを設定 Public Sub OpenLog( _ penmLogLevel As genmLogLevelM1) menmLogLevel = penmLogLevel mlngFno = FreeFile 'ファイル名固定 Open ThisWorkbook.Path & _ "\logM11.txt" _ For Append As #mlngFno End Sub Public Sub WriteInfo( _ pstrText As String) If menmLogLevel > ltm1Info Then Exit Sub End If Call prvWriteLog("Info ", pstrText) End Sub Public Sub WriteWarn( _ pstrText As String, _ Optional pstrDetail) If menmLogLevel > ltm1Warn Then Exit Sub End If Call prvWriteLog( _ "Warn ", _ pstrText, _ pstrDetail) End Sub Public Sub WriteError( _ pstrText As String, _ Optional pstrDetail) If menmLogLevel > ltm1Error Then Exit Sub End If Call prvWriteLog( _ "Error", _ pstrText, _ pstrDetail) End Sub Public Sub CloseLog() Close #mlngFno End Sub Private Sub prvWriteLog( _ pstrLogLevel As String, _ pstrText As String, _ Optional pstrDetail) Print #mlngFno, _ Format(Now, "yyyy/mm/dd hh:mm:ss") & _ " [" & pstrLogLevel & "]" & pstrText If IsMissing(pstrDetail) = False Then Print #mlngFno, " " & pstrDetail End If End Sub '*** 標準モジュール:modLogFile02 *** '前提条件 'OpenLogは最初に1回だけ実行 'CloseLogは最初に1回だけ実行 'OpenLogで指定するファイル名はダブらない 'ファイル名可変 Option Explicit Public Enum genmLogLevelM2 ltm2Info ltm2Warn ltm2Error End Enum Private mlngFno As Long Private menmLogLevel As genmLogLevelM2 '出力する最低レベルを設定 Public Sub OpenLog( _ pstrFileName As String, _ penmLogLevel As genmLogLevelM2) menmLogLevel = penmLogLevel mlngFno = FreeFile 'ファイル名可変 Open ThisWorkbook.Path & _ "\" & pstrFileName _ For Append As #mlngFno End Sub Public Sub WriteInfo( _ pstrText As String) If menmLogLevel > ltm2Info Then Exit Sub End If Call prvWriteLog("Info ", pstrText) End Sub Public Sub WriteWarn( _ pstrText As String, _ Optional pstrDetail) If menmLogLevel > ltm2Warn Then Exit Sub End If Call prvWriteLog( _ "Warn ", _ pstrText, _ pstrDetail) End Sub Public Sub WriteError( _ pstrText As String, _ Optional pstrDetail) If menmLogLevel > ltm2Error Then Exit Sub End If Call prvWriteLog( _ "Error", _ pstrText, _ pstrDetail) End Sub Public Sub CloseLog() Close #mlngFno End Sub Private Sub prvWriteLog( _ pstrLogLevel As String, _ pstrText As String, _ Optional pstrDetail) Print #mlngFno, _ Format(Now, "yyyy/mm/dd hh:mm:ss") & _ " [" & pstrLogLevel & "]" & pstrText If IsMissing(pstrDetail) = False Then Print #mlngFno, " " & pstrDetail End If End Sub '*** クラスモジュール:clsLogFile01 *** '前提条件 'OpenLogはインスタンスごとに最初に1回だけ実行 'CloseLogはインスタンスごとに最後に1回だけ実行 'ファイル名固定 Option Explicit Public Enum genmLogLevelC1 ltc1Info ltc1Warn ltc1Error End Enum Private mlngFno As Long Private menmLogLevel As genmLogLevelC1 '出力する最低レベルを設定 Public Sub OpenLog( _ penmLogLevel As genmLogLevelC1) menmLogLevel = penmLogLevel mlngFno = FreeFile 'ファイル名固定 Open ThisWorkbook.Path & _ "\logC11.txt" _ For Append As #mlngFno End Sub Public Sub WriteInfo( _ pstrText As String) If menmLogLevel > ltc1Info Then Exit Sub End If Call prvWriteLog("Info ", pstrText) End Sub Public Sub WriteWarn( _ pstrText As String, _ Optional pstrDetail) If menmLogLevel > ltc1Warn Then Exit Sub End If Call prvWriteLog( _ "Warn ", _ pstrText, _ pstrDetail) End Sub Public Sub WriteError( _ pstrText As String, _ Optional pstrDetail) If menmLogLevel > ltc1Error Then Exit Sub End If Call prvWriteLog( _ "Error", _ pstrText, _ pstrDetail) End Sub Public Sub CloseLog() Close #mlngFno End Sub Private Sub prvWriteLog( _ pstrLogLevel As String, _ pstrText As String, _ Optional pstrDetail) Print #mlngFno, _ Format(Now, "yyyy/mm/dd hh:mm:ss") & _ " [" & pstrLogLevel & "]" & pstrText If IsMissing(pstrDetail) = False Then Print #mlngFno, " " & pstrDetail End If End Sub '*** クラスモジュール:clsLogFile02 *** '前提条件 'OpenLogはインスタンスごとに最初に1回だけ実行 'CloseLogはインスタンスごとに最初に1回だけ実行 'OpenLogで指定するファイル名はダブらない 'ファイル名可変 Option Explicit Public Enum genmLogLevelC2 ltc2Info ltc2Warn ltc2Error End Enum Private mlngFno As Long Private menmLogLevel As genmLogLevelC2 '出力する最低レベルを設定 Public Sub OpenLog( _ pstrFileName As String, _ penmLogLevel As genmLogLevelC2) menmLogLevel = penmLogLevel mlngFno = FreeFile 'ファイル名可変 Open ThisWorkbook.Path & _ "\" & pstrFileName _ For Append As #mlngFno End Sub Public Sub WriteInfo( _ pstrText As String) If menmLogLevel > ltc2Info Then Exit Sub End If Call prvWriteLog("Info ", pstrText) End Sub Public Sub WriteWarn( _ pstrText As String, _ Optional pstrDetail) If menmLogLevel > ltc2Warn Then Exit Sub End If Call prvWriteLog( _ "Warn ", _ pstrText, _ pstrDetail) End Sub Public Sub WriteError( _ pstrText As String, _ Optional pstrDetail) If menmLogLevel > ltc2Error Then Exit Sub End If Call prvWriteLog( _ "Error", _ pstrText, _ pstrDetail) End Sub Public Sub CloseLog() Close #mlngFno End Sub Private Sub prvWriteLog( _ pstrLogLevel As String, _ pstrText As String, _ Optional pstrDetail) Print #mlngFno, _ Format(Now, "yyyy/mm/dd hh:mm:ss") & _ " [" & pstrLogLevel & "]" & pstrText If IsMissing(pstrDetail) = False Then Print #mlngFno, " " & pstrDetail End If End Sub '*** 標準モジュール:ModuleM1 *** 'modLogFile01のテスト Option Explicit 'テスト1(正常終了) Sub testm11() Call modLogFile01.OpenLog(ltm1Info) Call modLogFile01.WriteInfo("001-1info") Call modLogFile01.WriteWarn("001-1Warn", "xxx") Call modLogFile01.WriteError("001-1Error", "xxx") Call modLogFile01.CloseLog End Sub 'テスト2(エラー) Sub testm12() Call modLogFile01.OpenLog(ltm1Info) Call modLogFile01.WriteInfo("001-1info") Call modLogFile01.WriteWarn("001-1Warn", "xxx") Call modLogFile01.WriteError("001-1Error", "xxx") Call testm12s1 Call modLogFile01.WriteInfo("001-2info") Call modLogFile01.WriteWarn("001-2Warn", "xxx") Call modLogFile01.WriteError("001-2Error", "xxx") Call modLogFile01.CloseLog End Sub Sub testm12s1() 'ファイルがすでにオープン済みでエラー Call modLogFile01.OpenLog(ltm1Warn) 'NG Call modLogFile01.WriteInfo("002-1info") Call modLogFile01.WriteWarn("002-1Warn", "xxx") Call modLogFile01.WriteError("002-1Error", "xxx") Call modLogFile01.CloseLog End Sub 'テスト3(エラー) Sub testm13() Call modLogFile01.OpenLog(ltm1Info) Call modLogFile01.WriteInfo("001-1info") Call modLogFile01.WriteWarn("001-1Warn", "xxx") Call modLogFile01.WriteError("001-1Error", "xxx") Call testm13s1 Call modLogFile01.WriteInfo("001-2info") Call modLogFile01.WriteWarn("001-2Warn", "xxx") Call modLogFile01.WriteError("001-2Error", "xxx") Call modLogFile01.CloseLog End Sub Sub testm13s1() 'ファイルがすでにオープン済みでエラー Call modLogFile01.OpenLog(ltm1Info) 'NG Call modLogFile01.WriteInfo("002-1info") Call modLogFile01.WriteWarn("002-1Warn", "xxx") Call modLogFile01.WriteError("002-1Error", "xxx") Call testm13s2 Call modLogFile01.WriteInfo("002-2info") Call modLogFile01.WriteWarn("002-2Warn", "xxx") Call modLogFile01.WriteError("002-2Error", "xxx") Call modLogFile01.CloseLog End Sub Sub testm13s2() Call modLogFile01.OpenLog(ltm1Info) Call modLogFile01.WriteInfo("003-1info") Call modLogFile01.WriteWarn("003-1Warn", "xxx") Call modLogFile01.WriteError("003-1Error", "xxx") Call modLogFile01.CloseLog End Sub '*** 標準モジュール:ModuleM2 *** 'modLogFile02のテスト Option Explicit 'テスト1(正常終了) Sub testm21() Call modLogFile02.OpenLog("logM21a.txt", ltm2Info) Call modLogFile02.WriteInfo("001-1info") Call modLogFile02.WriteWarn("001-1Warn", "xxx") Call modLogFile02.WriteError("001-1Error", "xxx") Call modLogFile02.CloseLog End Sub 'テスト2(エラー) Sub testm22() Call modLogFile02.OpenLog("logM21a.txt", ltm2Info) Call modLogFile02.WriteInfo("001-1info") Call modLogFile02.WriteWarn("001-1Warn", "xxx") Call modLogFile02.WriteError("001-1Error", "xxx") Call testm22s1 'クローズ後にテキスト出力しようとして 'ファイル名またはファイル番号が不正でエラー Call modLogFile02.WriteInfo("001-2info") 'NG Call modLogFile02.WriteWarn("001-2Warn", "xxx") Call modLogFile02.WriteError("001-2Error", "xxx") Call modLogFile02.CloseLog End Sub Sub testm22s1() Call modLogFile02.OpenLog("logM21b.txt", ltm2Warn) Call modLogFile02.WriteInfo("002-1info") Call modLogFile02.WriteWarn("002-1Warn", "xxx") Call modLogFile02.WriteError("002-1Error", "xxx") Call modLogFile02.CloseLog End Sub 'テスト3(エラー) Sub testm23() Call modLogFile02.OpenLog("logM21a.txt", ltm2Info) Call modLogFile02.WriteInfo("001-1info") Call modLogFile02.WriteWarn("001-1Warn", "xxx") Call modLogFile02.WriteError("001-1Error", "xxx") Call testm23s1 Call modLogFile02.WriteInfo("001-2info") Call modLogFile02.WriteWarn("001-2Warn", "xxx") Call modLogFile02.WriteError("001-2Error", "xxx") Call modLogFile02.CloseLog End Sub Sub testm23s1() Call modLogFile02.OpenLog("logM21b.txt", ltm2Warn) Call modLogFile02.WriteInfo("002-1info") Call modLogFile02.WriteWarn("002-1Warn", "xxx") Call modLogFile02.WriteError("002-1Error", "xxx") Call testm23s2 'クローズ後にテキスト出力しようとして 'ファイル名またはファイル番号が不正でエラー Call modLogFile02.WriteInfo("002-2info") 'NG Call modLogFile02.WriteWarn("002-2Warn", "xxx") Call modLogFile02.WriteError("002-2Error", "xxx") Call modLogFile02.CloseLog End Sub Sub testm23s2() Call modLogFile02.OpenLog("logM21c.txt", ltm2Error) Call modLogFile02.WriteInfo("003-1info") Call modLogFile02.WriteWarn("003-1Warn", "xxx") Call modLogFile02.WriteError("003-1Error", "xxx") Call modLogFile02.CloseLog End Sub '*** 標準モジュール:ModuleC1 *** 'clsLogFile01のテスト Option Explicit 'テスト1(正常終了) Sub testc11() Dim objLog As clsLogFile01 Set objLog = New clsLogFile01 Call objLog.OpenLog(ltc1Info) Call objLog.WriteInfo("001-1info") Call objLog.WriteWarn("001-1Warn", "xxx") Call objLog.WriteError("001-1Error", "xxx") Call objLog.CloseLog End Sub 'テスト2(エラー) Sub testc12() Dim objLog As clsLogFile01 Set objLog = New clsLogFile01 Call objLog.OpenLog(ltc1Info) Call objLog.WriteInfo("001-1info") Call objLog.WriteWarn("001-1Warn", "xxx") Call objLog.WriteError("001-1Error", "xxx") Call testc12s1 Call objLog.WriteInfo("001-2info") Call objLog.WriteWarn("001-2Warn", "xxx") Call objLog.WriteError("001-2Error", "xxx") Call objLog.CloseLog End Sub Sub testc12s1() Dim objLog As clsLogFile01 Set objLog = New clsLogFile01 'ファイルがすでにオープン済みでエラー Call objLog.OpenLog(ltc1Warn) 'NG Call objLog.WriteInfo("002-1info") Call objLog.WriteWarn("002-1Warn", "xxx") Call objLog.WriteError("002-1Error", "xxx") Call objLog.CloseLog End Sub 'テスト3(エラー) Sub testc13() Dim objLog As clsLogFile01 Set objLog = New clsLogFile01 Call objLog.OpenLog(ltc1Info) Call objLog.WriteInfo("001-1info") Call objLog.WriteWarn("001-1Warn", "xxx") Call objLog.WriteError("001-1Error", "xxx") Call testc13s1 Call objLog.WriteInfo("001-2info") Call objLog.WriteWarn("001-2Warn", "xxx") Call objLog.WriteError("001-2Error", "xxx") Call objLog.CloseLog End Sub Sub testc13s1() Dim objLog As clsLogFile01 Set objLog = New clsLogFile01 'ファイルがすでにオープン済みでエラー Call objLog.OpenLog(ltc1Info) 'NG Call objLog.WriteInfo("002-1info") Call objLog.WriteWarn("002-1Warn", "xxx") Call objLog.WriteError("002-1Error", "xxx") Call testc13s2 Call objLog.WriteInfo("001-2info") Call objLog.WriteWarn("001-2Warn", "xxx") Call objLog.WriteError("001-2Error", "xxx") Call objLog.CloseLog End Sub Sub testc13s2() Dim objLog As clsLogFile01 Set objLog = New clsLogFile01 Call objLog.OpenLog(ltc1Info) Call objLog.WriteInfo("003-1info") Call objLog.WriteWarn("003-1Warn", "xxx") Call objLog.WriteError("003-1Error", "xxx") Call objLog.CloseLog End Sub '*** 標準モジュール:ModuleC2 *** 'clsLogFile02のテスト Option Explicit 'テスト1(正常終了) Sub testc21() Dim objLog As clsLogFile02 Set objLog = New clsLogFile02 Call objLog.OpenLog("logC21a.txt", ltc2Info) Call objLog.WriteInfo("001-1info") Call objLog.WriteWarn("001-1Warn", "xxx") Call objLog.WriteError("001-1Error", "xxx") Call objLog.CloseLog End Sub 'テスト2(正常終了) Sub testc22() Dim objLog As clsLogFile02 Set objLog = New clsLogFile02 Call objLog.OpenLog("logC21a.txt", ltc2Info) Call objLog.WriteInfo("001-1info") Call objLog.WriteWarn("001-1Warn", "xxx") Call objLog.WriteError("001-1Error", "xxx") Call testc22s1 Call objLog.WriteInfo("001-2info") 'NG Call objLog.WriteWarn("001-2Warn", "xxx") Call objLog.WriteError("001-2Error", "xxx") Call objLog.CloseLog End Sub Sub testc22s1() Dim objLog As clsLogFile02 Set objLog = New clsLogFile02 Call objLog.OpenLog("logC21b.txt", ltc2Warn) Call objLog.WriteInfo("002-1info") Call objLog.WriteWarn("002-1Warn", "xxx") Call objLog.WriteError("002-1Error", "xxx") Call objLog.CloseLog End Sub 'テスト3(正常終了) Sub testc23() Dim objLog As clsLogFile02 Set objLog = New clsLogFile02 Call objLog.OpenLog("logC21a.txt", ltc2Info) Call objLog.WriteInfo("001-1info") Call objLog.WriteWarn("001-1Warn", "xxx") Call objLog.WriteError("001-1Error", "xxx") Call testc23s1 Call objLog.WriteInfo("001-2info") Call objLog.WriteWarn("001-2Warn", "xxx") Call objLog.WriteError("001-2Error", "xxx") Call objLog.CloseLog End Sub Sub testc23s1() Dim objLog As clsLogFile02 Set objLog = New clsLogFile02 Call objLog.OpenLog("logC21b.txt", ltc2Warn) Call objLog.WriteInfo("002-1info") Call objLog.WriteWarn("002-1Warn", "xxx") Call objLog.WriteError("002-1Error", "xxx") Call testc23s2 Call objLog.WriteInfo("002-2info") 'NG Call objLog.WriteWarn("002-2Warn", "xxx") Call objLog.WriteError("002-2Error", "xxx") Call objLog.CloseLog End Sub Sub testc23s2() Dim objLog As clsLogFile02 Set objLog = New clsLogFile02 Call objLog.OpenLog("logC21c.txt", ltc2Error) Call objLog.WriteInfo("003-1info") Call objLog.WriteWarn("003-1Warn", "xxx") Call objLog.WriteError("003-1Error", "xxx") Call objLog.CloseLog End Sub |
|
投稿日時: 12/03/20 11:45:55
投稿者: みそじのおじさん
|
---|---|
おはようございます。
|
|
投稿日時: 12/03/20 12:10:16
投稿者: 月
|
---|---|
みそじのおじさん さんの引用: トップバッター、行かせてもらいます。 イベントを使って強参照と弱参照を避けた理由を大きい順に並べます。 ・ 面白かったから ・ VBAの機能でできるから(ヘルプもあるし) ・ Windows APIを知らないから みそじのおじさん さんの引用: 私はどうしても、それなくせないの?と思ってしまうでしょうね。 みそじのおじさんがリーダー、私がメンバーで、その通りやってくれって言われても、それなくせないの?とコーディングの度に思うでしょうね。 みそじのおじさん さんの引用: 実際には、インスタンス残ったらマズそうな時だけ対応して、それ以外は何もしないでしょうね。 |
|
投稿日時: 12/03/20 13:17:32
投稿者: Abyss
|
---|---|
引用: VBの本来の意図通り、これを使うべきだと私は思いますよ。 悪く言えば、弱参照はVB Runtimeを騙す行為ですので。 騙した結果がどうなるのかを十分承知の上で使う分には問題ないでしょう。 |
|
投稿日時: 12/03/20 13:27:07
投稿者: みそじのおじさん
|
---|---|
月さん。ありがとうございます。
月 さんの引用: 本職のプログラマの方からの貴重なご意見、大変重く受け止めます。 私は「強参照」に固執している訳ではないのですが、これからの指針を定めて おきたいと思いまして。(色んなパターンを知った今、次作る物への期待感で一杯です^^) なお、様々なご意見を頂ける?!と思っておりますが、「このスレッド内での結論」という 形はあまり取りたくないと思っております。色々なコーディングスタイルの方がいらっしゃい ますし、その意見の中で自身で判断する形でいいのでは?と思っております^^ # これから母親が手術の為、返信が遅れ気味になる事を先にお詫びしておきます。 # 「かーちゃんガンバ!」 |
|
投稿日時: 12/03/20 23:51:59
投稿者: ろひ
|
---|---|
もっと進んでからにしようと思ったけど…
|
|
投稿日時: 12/03/21 06:57:38
投稿者: yayadon
|
---|---|
Friend メソッド内からの Private メソッド呼び出しの件です。
VERSION 1.0 CLASS BEGIN MultiUse = -1 'True Persistable = 0 'NotPersistable DataBindingBehavior = 0 'vbNone DataSourceBehavior = 0 'vbNone MTSTransactionMode = 0 'NotAnMTSObject END Attribute VB_Name = "Class1" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = True Attribute VB_PredeclaredId = False Attribute VB_Exposed = False Option Explicit Private Function PrivateMethod1(ByVal value As Long) As Long PrivateMethod1 = value + 1 End Function Private Function PrivateMethod2(ByVal value As Long) As Long PrivateMethod2 = value + 2 End Function Private Function PrivateMethod3(ByVal value As Long) As Long PrivateMethod3 = value + 3 End Function Private Function PrivateMethod4(ByVal value As Long) As Long PrivateMethod4 = value + 4 End Function Private Function PrivateMethod5(ByVal value As Long) As Long PrivateMethod5 = value + 5 End Function Public Function PublicMethod(ByVal count As Long) As Long Dim result As Long result = PrivateMethod1(count) If count > 1 Then result = PrivateMethod2(result) If count > 2 Then result = PrivateMethod3(result) If count > 3 Then result = PrivateMethod4(result) If count > 4 Then result = PrivateMethod5(result) End If End If End If End If PublicMethod = result End Function Friend Function FriendMethod(ByVal count As Long) As Long Dim result As Long result = PrivateMethod1(count) If count > 1 Then result = PrivateMethod2(result) If count > 2 Then result = PrivateMethod3(result) If count > 3 Then result = PrivateMethod4(result) If count > 4 Then result = PrivateMethod5(result) End If End If End If End If FriendMethod = result End Function VERSION 5.00 Begin VB.Form Form1 Caption = "Form1" ClientHeight = 3090 ClientLeft = 60 ClientTop = 450 ClientWidth = 7260 LinkTopic = "Form1" ScaleHeight = 3090 ScaleWidth = 7260 StartUpPosition = 3 'Windows の既定値 Begin VB.CommandButton Command2 Caption = "Friend テスト" Height = 585 Left = 4320 TabIndex = 3 Top = 1680 Width = 1680 End Begin VB.TextBox Text2 Alignment = 1 '右揃え Height = 375 Left = 4320 TabIndex = 2 Top = 390 Width = 1680 End Begin VB.CommandButton Command1 Caption = "Public テスト" Height = 615 Left = 4320 TabIndex = 1 Top = 960 Width = 1680 End Begin VB.TextBox Text1 Height = 2355 Left = 285 MultiLine = -1 'True ScrollBars = 2 '垂直 TabIndex = 0 Top = 330 Width = 3885 End End Attribute VB_Name = "Form1" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = True Attribute VB_Exposed = False Option Explicit Private Sub TestPublic(ByVal count As Long) Const cIterate = 30000000 Dim t As Single Dim i As Long Dim c As Class1 Dim result As Long Set c = New Class1 t = Timer For i = 1 To cIterate result = c.PublicMethod(count) Next ' Debug.Print Timer - t, "public" Dim strMsg As String strMsg = Timer - t & " --- " & "public" Text1.Text = Text1.Text & vbCrLf & strMsg End Sub Private Sub TestFriend(ByVal count As Long) Const cIterate = 30000000 Dim t As Single Dim i As Long Dim c As Class1 Dim result As Long Set c = New Class1 t = Timer For i = 1 To cIterate result = c.FriendMethod(count) Next ' Debug.Print Timer - t, "friend" Dim strResult As String strResult = Timer - t & " --- " & "friend" Text1.Text = Text1.Text & vbCrLf & strResult End Sub Private Sub Command1_Click() Dim result As Long Call TestPublic(CLng(Text2.Text)) End Sub Private Sub Command2_Click() Dim result As Long Call TestFriend(CLng(Text2.Text)) End Sub テキスト ボックスに 1 から 5 を順に入れて, 各ボタンを押すと,Public/Friend メソッド内から 用意した 5つ の Private メソッドを入力された数まで呼び出すというものです。 Win7 SP1 上で順に交互に呼び出した結果: PrivateMehtod1 1.377664 --- public 0.3266641 --- friend PrivateMehtod1 & PrivateMehtod2 1.498727 --- public 0.4272656 --- friend PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod3 1.567172 --- public 0.5237188 --- friend PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod3 % PrivateMehtod4 1.794203 --- public 0.6364297 --- friend PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod3 & PrivateMehtod4 & PrivateMehtod5 1.927836 --- public 0.7730156 --- friend Private メソッドを 1 回だけ呼び出した時は, 1.377664 --- public 0.3266641 --- friend と,4倍以上の差があったのに, 最後の Private メソッドを 5 回呼び出した時には, 1.927836 --- public 0.7730156 --- friend のように,3倍未満の差になっています。 ふつうに推測すると,私の環境では, A. Friend メソッド内からの Private メソッドの呼び出しが遅くなった。 B. Public メソッド内からの Private メソッドの呼び出しが速くなった。 のどちらかでしょう。 他の可能性ですが, Friend メソッド内からの 各Private メソッドの最初の呼び出しと比較して Public メソッド内からの Private メソッドの呼び出しが速くなった。 ということではないか?と考えてみました。 上記のコードでは,Private メソッドを 5 種類用意していますが, 例えば,2 種類にして,3つ めからは交互に使う形にして試した結果: ネイティブ コンパイル だと, PrivateMehtod1 1.328734 --- public 0.3419531 --- friend p/f = 1.328 / 0.341 = 3.894 PrivateMehtod1 & PrivateMehtod2 1.492359 --- public 0.4240625 --- friend p/f = 1.492 / 0.424 = 3.518 PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod1 1.561891 --- public 0.5341094 --- friend p/f = 1.561 / 0.424 = 2.923 PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod1 & PrivateMehtod2 1.749344 --- public 0.6203437 --- friend p/f = 1.749 / 0.424 = 2.820 PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod1 1.905297 --- public 0.7766719 --- friend p/f = 1.905 / 0.776 = 2.454 をみると, 3.894 -> 3.518 -> 2.923 -> 2.820 -> 2.454 のように,どんどん差が少なくなってきています。 なので, 最初にテストした,すべて異なる Private メソッド を呼び出す のと同じ傾向がみられ, 変わりはないようです。 但し,P-Code コンパイルだと結果が変わってきました。 P-Code コンパイルだと, 一回しか呼び出さない地点で,すでに Public の方が速くなっています。 ですが 2 回呼び出しの時に一度,PrivateMehtod2 が初呼び出しのため?に 0.943 -> 0.960 と Friend メソッドはさらに遅くなりますが, 3 回呼び出しで,1 回呼び出しと並び, 4 回呼び出しで,さらに差を詰め, 5 回呼び出し時には,Public より速くなっています。 PrivateMehtod1 8.833688 --- public 9.171375 --- friend p/f = 8.833 / 9.171 = 0.963 PrivateMehtod1 & PrivateMehtod2 12.91161 --- public 13.68477 --- friend p/f = 12.911 / 13.684 = 0.943 PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod1 18.11717 --- public 18.85539 --- friend p/f = 18.117 / 18.855 = 0.960 PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod1 & PrivateMehtod2 21.813 --- public 22.21586 --- friend p/f = 21.813 / 22.215 = 0.981 PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod1 & PrivateMehtod2 & PrivateMehtod1 27.13948 --- public 26.7383 --- friend p/f = 27.139 / 26.738 = 1.014 ※ 実行時にロードされている DLL MSVBVM60.DLL 6.00.0098.0015 MSVCP90.dll 9.00.30729.6161 MSVCR90.dll 9.00.30729.6161 上記は,テストの結果が安定している VB6 で行ったものですが, VBA は,VBE6/VBE7.DLL を使っている点では異なりますが, P-Code コンパイルなので,こちらの結果に近いものになると思われます。 # 他の人の反応が無いのは,ひょっとして私の環境だけ?ということでしょうか?? |
|
投稿日時: 12/03/21 11:37:47
投稿者: kumatti
|
---|---|
・VB6は無いのでVBAでテスト
9.664063 --- friend 8.957031 --- public 何度行なっても、friendの方が遅いですね。 前回のコードは、friendの方が速かったです。 # お二方共、高速なPCをお使いですね。 |
|
投稿日時: 12/03/21 11:52:48
投稿者: Abyss
|
---|---|
Officeでテストの場合、私の環境では
|
|
投稿日時: 12/03/21 22:31:39
投稿者: 藤代千尋
|
---|---|
参戦(笑)。
|
|
投稿日時: 12/03/21 23:33:49
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
|
|
投稿日時: 12/03/22 08:57:38
投稿者: kumatti
|
---|---|
> MoveMemory tmp, 0&, 4 ←この行為がはっきり理屈で理解出来ていない
DWORD dwTmp = NULL; LPDWORD pdwTmp = &dwTmp; LPDISPATCH *ppDisp = (LPDISPATCH*)pdwTmp;そんなに難しいかなと。 > moug初参戦の藤代さん 正確には、今のスタイルのmougになってからですね。 |
|
投稿日時: 12/03/22 12:32:56
投稿者: 月
|
---|---|
藤代千尋さん、勉強になりました。
|
|
投稿日時: 12/03/22 14:33:52
投稿者: Abyss
|
---|---|
Private Function ObjFromPtr(ByVal ptr&) As clsContainer Dim tmp As clsContainer MoveMemory tmp, ptr, 4 Set ObjFromPtr = tmp ↑ここまでの使い方はOK MoveMemory tmp, 0&, 4 ←この行為がはっきり理屈で理解出来ていない End Function 参照カウントを考えれば、理解が早いと思います。 現在のclsContainerインスタンスの参照カウントが n だとしましょう。 スタート時点での参照カウント:n > MoveMemory tmp, ptr, 4 この時点での参照カウント:n > Set ObjFromPtr = tmp この時点ではAddRef作業により、参照カウント:n + 1 もし, > MoveMemory tmp, 0&, 4 の処理をしないと、メソッドを抜ける時に tmp変数の開放が行われ(Release) 参照カウントが一つ減します。 n + 1 → n これでは参照カウントのバランスが崩れてしまいますので、 それを防ぐため、tmp変数は最初から何も参照していないよ〜と 「騙す」作業としてこれが必要なります。 > 藤代さん お世話さまです。 |
|
投稿日時: 12/03/22 17:35:22
投稿者: Abyss
|
---|---|
> tmp変数の開放が行われ(Release)
|
|
投稿日時: 12/03/22 18:10:19
投稿者: yayadon
|
---|---|
# 十分な説明が付いてるけど,せっかく書いたので,投稿しておきます。
MoveMemory tmp, 0&, 4 ' ←この行為がはっきり理屈で理解出来ていない End Function ByRef な第2引数に対して即値が渡されるているので, コード自体も理解しづらくなっているんだと思います。 ByRef 引数 の場合,本来は,変数の場所を渡します。 上記にように即値を渡した場合,VBA が Dim lngNullPointer As Long lngNullPointer = 0 MoveMemory tmp, lngNullPointer, 4 End Function であるかのように解釈してくれます。 lngNullPointer 変数の場所の先頭から 4 バイト分の領域にある値を tmp 変数の場所の先頭から 4 バイト分の領域にコピーします。 つまり, lngNullPointer 変数に入っていた 0 という値を tmp 変数に入れて,tmp 変数の値を 0 にしているわけです。 tmp は,オブジェクト変数なので,VBA が構文でチェックしているので tmp = 0とやれないので, MoveMemory で,VBA に黙って入れ込んでいるわけです。 最初の MoveMemory tmp, ptr, 4これも同様です。 ptr 変数の場所の先頭から 4 バイト分の領域にある値を tmp 変数の場所の先頭から 4 バイト分の領域にコピーします。 つまり, Long 型の実引数である ptr 変数に入っていた値を tmp 変数に入れて,tmp 変数の値にしているわけです。 ptr 変数に渡される値は, ObjPtr 関数で前もって保存しておいたアドレス値なので,0 でない値です。 Private Function ObjFromPtr(ByVal ptr As Long) As clsContainer Dim tmp As clsContainer MsgBox ObjPtr(tmp) ' 0 MoveMemory tmp, ptr, 4 ' 最初の騙し MsgBox ObjPtr(tmp) ' 渡されたアドレス値 つまり 0 でない。 MsgBox ObjPtr(ObjFromPtr) ' 0 Set ObjFromPtr = tmp ' 参照カウントは +1 MsgBox ObjPtr(ObjFromPtr) ' 渡されたアドレス値 つまり 0 でない。 ' この地点で,対象のオブジェクト(clsContainer のインスタンス)は,少なくとも, ' tmp と ObjFromPtr の 2つ のオブジェクト変数から参照されている。 Dim lngNullPointer As Long lngNullPointer = 0 MoveMemory tmp, lngNullPointer, 4 ' 2度目の騙し MsgBox ObjPtr(tmp) ' 0 になる。 End Function ' <---- A 最初の騙しの時に,新規にオブジェクト参照ができあがっているのに, VBA を騙したものなので +1 されてません。 そして,VBA は,メソッドの終了時( A の地点)で, tmp の値が 0 でないと, 律儀に Release を呼んで -1 してしまうので, 2度目の騙しで,それを回避しているわけです。 0 のときは,Release を呼ばないという VBA のオブジェクト参照の変数に対する実装を逆手にとってるわけです。 補足: 参照カウント Dim tmp1 As Class1 Dim tmp2 As Class1 Dim tmp3 As stdole.IUnknown Set tmp1 = New Class1 ' A Set tmp2 = tmp1 ' B Set tmp3 = tmp1 ' C With tmp1 ' D End With ' E A では,New 時にあらかじめ COM オブジェクト側が +1 して渡してくるので, ここでの Set tmp1 = は +1 されません。 B では,Set tmp2 = は +1 されます。 C では,インターフェースを IUnknown に変えるために, COM オブジェクトに対して QueryInterface メソッドというものを呼び出すのですが, これも,COM オブジェクト側が,前もって +1 して渡してくるので, Set tmp3 = は +1 されません。 D の With tmp1 では +1 されて E の End With で -1 されます。 |
|
投稿日時: 12/03/22 19:02:28
投稿者: yayadon
|
---|---|
Set ObjFromPtr = tmpが, メソッド名に代入している感じで気持ちが悪い人は, 以下のようになっていること知るとスッキリすると思います。 Function ObjFromPtr(ByVal ptr As Long) As clsContainer は, Function ObjFromPtr(ByVal ptr As Long, ByRef ObjFromPtr As clsContainer) As Long↑↑ のように VBA 側によって解釈されます。 参照渡しで,戻り値を返す形になっています。 なので, Set ObjFromPtr = tmpしても違和感がなくなると思います。 # Long 型の戻り値は HRESULT と呼ばれている成功失敗を示す値を返します。 |
|
投稿日時: 12/03/22 22:27:41
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
|
|
投稿日時: 12/03/22 22:37:51
投稿者: simple
|
---|---|
こんばんは。
module Subject def initialize @observers=[] end def add_observer(&observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end def notify_observers @observers.each do |observer| observer.call(self) end end end class Person include Subject attr_accessor :name attr_reader :level def initialize(name,level) super() @name = name @level = level end def level=(new_level) unless @level == new_level @level = new_level notify_observers end end end class MougPersons attr_reader :persons def initialize members = %w(yayadonさん ろひさん simpleさん 月さん ゴマさん kumattiさん YU-TANGさん kanabunさん Abyssさん みそじのおじさん) # ↑ 文字列の配列です。 @persons = [] @hash = {} members.each do |member| myPerson = Person.new(member,3) @persons << myPerson @hash[member] = myPerson end end def set_observer @persons.each do |myPerson| myPerson.add_observer do |changed_person| puts "#{changed_person.name}は #{changed_person.level} に" + "レベル変更" end end end def [](index) case index when Fixnum @persons[index] when String @hash[index] end end end moug_persons = MougPersons.new moug_persons.set_observer moug_persons[0].level = 4 moug_persons["simpleさん"].level = 1 moug_persons.persons.each do |mem| puts "#{mem.level} : #{mem.name} " end 出力は yayadonさんは 4 にレベル変更 simpleさんは 1 にレベル変更 4 : yayadonさん 3 : ろひさん 1 : simpleさん 3 : 月さん 3 : ゴマさん 3 : kumattiさん 3 : YU-TANGさん 3 : kanabunさん 3 : Abyssさん 3 : みそじのおじさん となります。 Observerパターンは先刻、説明がありましたものです。 add_observerは、ブロック付きメソッドであり、 def add_observer(&observer) @observers << observer end 下記のようなブロックから作成される Proc(無名関数のようなもの)を @observersというインスタンス配列に追加しています。 myPerson.add_observer do |changed_person| puts "#{changed_person.name}は #{changed_person.level} に" + "レベル変更" end levelが更新されると、notify_observersが実行され、 その時、保持されていた無名関数が実行される仕組みです。 -------------- なお、 単調なgetter,setter の定義が簡単にできるのは、Rubyの一つの工夫といえると 思います。 attr_reader :levelというのは、インスタンス変数@levelにgetterを定義するのと同じです。 def level @level endまた、 attr_accessor :nameは、この一文で、下記のsetter と getterを定義するのと同じです。 def neme=(new_name) @name = new_name end def name @name endこんな感じで、コンパクトに書けます。 -------------- Subjectを継承してPersonを作っても良いのでしょうが、 それだとPersonの基底クラスがこれに限定されてしまって不都合なことも あるかもしれません。 そこで登場するのが、Moduleという機構です。 関係のないクラス間で、コードを共有したいときなどは、 メソッドの集合であるModuleをPersonにincludeすることで、 継承関係を使わずに、クラスに機能を混ぜ込むことができます。 Moduleは多重継承を避けつつ、同様の機能を果たすための方法と 見ることもできます。 # なお、上にあげたコードはRuss Olsen氏に依存しています。 |
|
投稿日時: 12/03/23 00:20:42
投稿者: Abyss
|
---|---|
# 話を交錯させるようで、申し訳ないです。
引用: VBで扱われる配列とはSAFEARRAYルールに沿っていますので、 Lbound値だけなら、配列その物を触らなくても可能だと思います。 |
|
投稿日時: 12/03/23 08:03:53
投稿者: yayadon
|
---|---|
kumatti さん,Abyss さん
kumatti さんの引用: Excel VBA Q&A は,結構 ROM ってたのですが,思い出せません。 普段使ってないテクニックは結局忘れてしまいます。(爆) よかったら,何の件なのか,ヒントくださ〜い。 |
|
投稿日時: 12/03/23 08:06:49
投稿者: kumatti
|
---|---|
> 投稿日時: 12/03/22 08:57:38 No.61
|
|
投稿日時: 12/03/23 08:29:35
投稿者: kumatti
|
---|---|
> よかったら,何の件なのか,ヒントくださ〜い。
|
|
投稿日時: 12/03/23 09:41:40
投稿者: yayadon
|
---|---|
ありがとうごさいます。 > kumatti さん,Abyss さん
|
|
投稿日時: 12/03/23 11:42:48
投稿者: 月
|
---|---|
そういえば、Excel 2007からFileSearchオブジェクトが使えなくなったんですよね。
|
|
投稿日時: 12/03/23 19:28:21
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
simple さんの引用: 言語の特色でクラスの作り方はずいぶん変わりますね。 「moduleをinclude出来る」VBAでもこれが出来るとガラッと構成が変わるんでしょうね。 # これが「お仕事」でなく書けるsimpleさんに脱帽です^^ ▼Abyssさん 記憶が曖昧なまま、お返事を書いてしまいました。 Abyssさんのレスを見て気付きました。正確にはLboundをいじろうとしていたのです。 たしか、検索中に出会ったAbyssさんの回答を参考にしていたはずです。 (その回答は1次元配列だったと思います) ▼月さん ネタ振りありがとうございます。 このスレッドから、「VBAユーザーならだれでも知っているクラス」なんて生まれたら すごいですねー。角田さんの「kt」は市民権を得ていますが、私も将来「OJN」 なんて頭文字で世に送り出したいものです^^ (OJN?あーあのおじさんのクラスか!使うのやめとこ! えっ(笑)) |
|
投稿日時: 12/03/24 09:12:07
投稿者: kumatti
|
---|---|
> FileSearch
|
|
投稿日時: 12/03/24 10:30:31
投稿者: 月
|
---|---|
kumatti さんの引用: コードは理解できませんでしたが、考え方が勉強になりました。 |
|
投稿日時: 12/03/24 21:48:49
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
Public RangeExClass Inherits Range ''Rangeクラスを継承する Public Sub 独自のメソッド(Byval ・・・) ・・・ End Sub ''Rangeクラス(BaseClass)のメソッドに変更を加える Public Overrides Sub Rangeのメソッド(ByVal ・・・・) ・・・ End Sub クラスに自作のメソッドやプロパティを作成する他に、Overridesステートメント によって変更をかけたい場所だけプログラマが書けばいいとなります。 しかしVBAではこうは行きません。 RangeExClass Public Sub 独自のメソッド(Byval ・・・) ・・・ End Sub ''Rangeオブジェクトのメンバを以下に全て列挙 Public Property Get Value() As Variant ・・・・ End Property Public Property Let Value(Byval NewValue As Variant) ・・・・ End Property 以下延々とRangeオブジェクトが持つメンバすべてを書かなければいけない Rangeオブジェクトが持っているメンバの数は膨大ですので、よほど根気のある方で ないと作成するのは無理ではないでしょうか? そう考えた時に私は「2つのクラスではどうだ?」と考えました。 RangeExClass 指定範囲の管理と子クラスのインスタンスが主な仕事 RangeExPartsClass 独自のメソッドやプロパティを実装し、ItemプロパティはRange型を 返すクラス この2つのクラスは共にItemプロパティを実装しています。(共にItemが規定のメンバ) RangeExClass のItemの型はRangeExPartsClass RangeExPartsClass のItemの型はRange型 RangeEx.Item("A1") この.Item("A1")はRangeExPartsClassを返します。 RangeEx.Item("A1"). ここまで打つとRangeExPartsClassのメンバが出ます。 (ここに独自のメソッド、プロパティを作成) RangeExクラスの規定のメンバはItemですので以下のように省略して記述出来ます。 RangeEx("A1") 独自に作成したメソッドやプロパティは以下のようにして使うようにしました。 RangeEx("A1:D4").Average 平均を返します。 RangeEx("A1:D4").UniqueValue 重複の無いリストを返します。 このままでは「Rangeオブジェクトを拡張した!」とは言えませんので RangeExPartsクラスにRange型を返すItemプロパティを作成しました。 RangeEx("A1:D4").Item この記述でRange("A1:D4")のRangeオブジェクトが 返ってきます。 RangeEx("A1:D4").Item. と打つとRangeオブジェクトの全てのメンバが出てきます。 RangeEx("A1:D4").Item.Address で"$A$1:$D$4" が返ってきます。 「継承がサポートされていない」中で、それに少しでも近づくには?とやっていました がどうやら私の知識ではこの辺りでギブのようです^^ 皆様だったらどの様に致しますでしょうか?お話を聞かせて頂けると幸いです。 本当は、RangeEx("A1:D4").ItemのItemを省略出来るまでが目標でした。 Itemは規定のメンバにしてありますので RangeEx("A1:D4")().Addressとは書けますが、んー気に入りません^^; Attribute辺りで何とかならないものでしょうか? (Attributeを検索していますが、情報が少なすぎです、、) .Itemを省略したと見せかけて、RangeEx("A1:D4").Addressと書きたかったです。 MsgBox RangeEx("A1") と書きますと .Itemが省略されたと認識されきちんと A1の値が返ってきますが、RangeEx("A1").Addressと打つとメンバが無いと 怒られます。RangeExPartsClassにAddressというメンバがないので当たり前ですが。 仮にObject型にしてコンパイルは通ったとしても今度はインテリセンスが利かなく なりますので八方ふさがりです^^;; 次のスレッドに出来ているコードを載せます。 |
|
投稿日時: 12/03/24 22:21:10
投稿者: みそじのおじさん
|
---|---|
ではコードです。
Option Explicit ''コンストラクタ関数 ''すぐ影響を受ける私です^^藤代さん、ありがとうございます。 Public Function CreateRangeEx(Optional ParentSheet As _ Worksheet = Nothing) As RangeExClass Set CreateRangeEx = New RangeExClass ''Rangeの親の指定がなければActiveSheetを親に If ParentSheet Is Nothing Then Set ParentSheet = ActiveSheet End If CreateRangeEx.Init ParentSheet End Function ''テストデータ作成プロシージャ Sub CreateTestData() Dim wh As Worksheet Dim r As Range Dim i As Integer Dim Menbers As Variant Set wh = Worksheets(1) Menbers = Array("yayadonさん", _ "ろひさん", _ "simpleさん", _ "月さん", _ "ゴマさん", _ "kumattiさん", _ "YU-TANGさん", _ "Kanabunさん", _ "Abyssさん", _ "みそじのおじさん", _ "どんきちさん", _ "マコさん", _ "ふるふるさん", _ "だるまさん", _ "yamaさん") Randomize Now() For Each r In wh.Range("A1:D4") ''Int((upperbound - lowerbound + 1) * Rnd + lowerbound) i = Int((UBound(Menbers) - LBound(Menbers) + 1) * Rnd + LBound(Menbers)) r.Value = Menbers(i) Next For Each r In wh.Range("F1:I4") ''Int((upperbound - lowerbound + 1) * Rnd + lowerbound) i = Int((100 - 0 + 1) * Rnd + 0) r.Value = i Next wh.Range("A10").Value = "Sample1" wh.Range("A11").Value = "Sample2" wh.Columns("A:D").AutoFit End Sub Sub Sample() Dim Persons As Variant Dim RangeEx As RangeExClass: Set RangeEx = CreateRangeEx() ''テストデータ作成 Call CreateTestData ''指定範囲のユニークなデータ取得 ''UniqueValueプロパティ Persons = RangeEx("A1:D4").UniqueValue MsgBox "重複しないリスト:" & vbLf & Join(Persons, vbLf) ''CountIfプロパティ MsgBox Range("A1").Value & "は, " _ & RangeEx("A1:D4").CountIf(Range("A1")) & "回出現しています" ''Averageプロパティ MsgBox "F1:I4の平均は," & RangeEx("F1:I4").Average & "です" ''Sumプロパティ MsgBox "F1:I4の合計は" & RangeEx("F1:I4").Sum & "です" ''StrConvExecuteメソッド MsgBox "vbWideでStrConvを実行します。", vbInformation RangeEx("A1:D4").StrConvExecute vbWide ''RunCmdメソッド MsgBox "セルに記入したマクロを実行します", vbInformation RangeEx("A10:A11").RunCmd True ''.Item.****で従来のRangeオブジェクトのメンバの参照可 MsgBox "RangeEx(""A1:D4"").Item.Addressの構文が正しく働くか確認します" MsgBox RangeEx("A1:D4").Item.Address ''RangeEx("A1:D4").Item この後に「.」を打ってRangeオブジェクト ''のメンバの一覧が出てくる事を確認して下さい^^ End Sub Sub Sample1() MsgBox "Sample1を実行しました。", vbInformation End Sub Sub Sample2() MsgBox "Sample2を実行しました。", vbInformation End Sub RangeExClass Option Explicit ''Partsクラスのイベントを受けとる為、WithEvents宣言 Private WithEvents mParts As RangeExPartsClass Private mRangeParent As Worksheet ''Rangeの親シート Private mRange As Range ''対象Range Private mInitialized As Boolean ''初期化完了フラグ Private Sub Class_Initialize() mInitialized = False End Sub Private Sub Class_Terminate() 'MsgBox "RangeExClass_Term!" End Sub ''コンストラクタ Public Sub Init(ByVal NewRangeParent As Worksheet) ''Partsクラスの生成 Set mRangeParent = NewRangeParent ''初期化完了 mInitialized = True End Sub ''このクラスの規定のプロパティItem Public Property Get Item(Cell1, Optional Cell2) As RangeExPartsClass Attribute Item.VB_UserMemId = 0 If Not mInitialized Then MsgBox "初期化が完了していません", vbExclamation Exit Property End If Set mParts = New RangeExPartsClass Set Item = mParts ''モジュールレベルの変数mRangeにRangeを確保 If IsMissing(Cell2) Then Set mRange = mRangeParent.Range(Cell1) Else Set mRange = mRangeParent.Range(Cell1, Cell2) End If End Property ''子クラスのイベント ''ByRefによってRangeを返します。 Private Sub mParts_GetParentRange(ParentRange As Range) Set ParentRange = mRange End Sub RangeExPartsClass Option Explicit Public Event GetParentRange(ByRef ParentRange As Range) Private WsFunc As WorksheetFunction Private Sub Class_Initialize() Set WsFunc = WorksheetFunction End Sub Private Sub Class_Terminate() 'Debug.Print "RangeExPartsClass_Term!" End Sub ''Itemを指定すると既存のRangeオブジェクトの機能が使用可 Public Property Get Item() As Range Attribute Item.VB_UserMemId = 0 Set Item = GetRange() End Property ''定義したEventを起こしRangeを得る Private Function GetRange() As Range RaiseEvent GetParentRange(GetRange) End Function ''拡張プロパティ ''指定範囲のSum Public Property Get Sum() As Variant Sum = WsFunc.Sum(GetRange()) End Property ''拡張プロパティ ''指定範囲のAverage Public Property Get Average() As Variant Average = WsFunc.Average(GetRange()) End Property ''拡張プロパティ ''指定範囲のCountIf Public Property Get CountIf(ByVal BaseRange As Range) As Double CountIf = WsFunc.CountIf(GetRange(), BaseRange) End Property ''拡張プロパティ ''指定範囲の重複の無いリストを返す Public Property Get UniqueValue(Optional IsTranspose As _ Boolean = False) As Variant Dim Dic As Object Dim r As Range Set Dic = CreateObject("Scripting.Dictionary") For Each r In GetRange() If Not IsEmpty(r.Value) Then If Not Dic.exists(r.Value) Then Dic.Add r.Value, Empty End If End If Next If IsTranspose Then UniqueValue = WsFunc.Transpose(Dic.keys) Else UniqueValue = Dic.keys End If Set Dic = Nothing End Property ''拡張メソッド ''セルに記入した「マクロ名」でマクロを実行する Public Sub RunCmd(Optional IsAsk As Boolean = False) Dim r As Range Dim Ret As VbMsgBoxResult On Error GoTo Err_Handle For Each r In GetRange() If Not IsEmpty(r.Value) Then Ret = 0 If IsAsk Then Ret = MsgBox("マクロ'" & r.Value & _ "'を実行しますか?", vbQuestion Or vbYesNo) End If If (Not IsAsk) Or (Ret = vbYes) Then Application.Run r.Value End If End If Next Exit Sub Err_Handle: MsgBox Err.Description, vbCritical Err.Clear Resume Next End Sub ''拡張メソッド ''StrConvの実行メソッド Public Sub StrConvExecute(ByVal Conversion As VbStrConv) Dim r As Range For Each r In GetRange() r.Value = StrConv(r.Value, Conversion) Next End Sub Sub Sample()を実行して下さい。 それと是非手で打ってみて、どのようにメンバが出るか見て頂きたいです。 よろしくお願い致します。 最初に書いた時はImplementsを使っていましたが分りやすくする為に書き直しました。 (3→2へクラス数を減らしました。これでご勘弁を^^) 作成したいメソッドやプロパティはもっと沢山ありますが、形がこれでベストかも 分りませんのでこの位でやめておきました。 私だと.FAXなんてメソッドを作って指定範囲をFAXするなんて機能を作るかもです^^ |
|
投稿日時: 12/03/26 23:23:03
投稿者: みそじのおじさん
|
---|---|
みなさん、こんばんは。
|
|
投稿日時: 12/03/26 23:46:40
投稿者: 藤代千尋
|
---|---|
今だとクラスの「拡張」は,「継承」とは違う概念になっていますね.(^^;)
|
|
投稿日時: 12/03/27 00:33:11
投稿者: ろひ
|
---|---|
藤代千尋 さんの引用:↓ 藤代千尋 さんの引用: ざーっと一読させていただいたレベルの所感ですが、それでもこの流れに「私自身がVBAクラスに対して持っていたモヤモヤ感」とその琴線に触れるものが多数ありました。 藤代さんの、(1つの専門性だけに依らない)幅広いご経験による観点からの考え方やその流れ、非常に勉強になります。 (※みそじのおじさんのRangeクラスへの考察や判断基準として納得したのではなく、あくまで私自身が「VBAクラスそのもの」に対して抱いている私的なところについてです。) |
|
投稿日時: 12/03/27 22:39:17
投稿者: みそじのおじさん
|
---|---|
遅くなりました。
Public Sub Init(ByVal Target As Variant) '' 文字列は Range に変換.※文字列以外は Range が渡されて来るとする. If VarType(Target) = vbString Then Set Target = Range(Target) →※1へ End If Set mp.Target = Target End Sub '' ※Range の Item と同じ形にする. Public Property Get Item(ByVal RowIndex As Variant, Optional ByVal ColumnIndex As Variant) As Range If IsMissing Then Set Item = mp.Target.Item(RowIndex) Else Set Item = mp.Target.Item(RowIndex, ColumnIndex) End If End Property Public Property Get Range() As Range ※1 Set Range = mp.Target End Property Public Property Get Target() As Range Set Range = mp.Target ← Set Targetですかね? End Property ●雑記について 藤代さんは「雑記」と書かれていますが、こういった話は滅多に聞けませんのでとても貴重です。 ・プロシージャID 確かに、今回「規定のメンバ」を作成したおかげて皆様にご迷惑をかけてしまいました。 普段はどちらかというと省略しない派ですが、自分で作った物に規定のメンバが 作成出来るというのを知ってうれしくなり単純に何も考えず使っていました。 ・クラスにClassと付けない これもグサッときました。見渡してみれば私のつくったクラスはcls、Classのオンパレード です^^; ・mpについて 藤代さんの回答はよく見させて頂いておりますが、「mp」ってなんの略だろうとずっと 思っていました。「mp.でメンバが出る」これは必ず取り入れてみます。ちなみに 定義側の「U」は何の略なのでしょうか?Use?んー分りません。。 # 私のコメントアウトの「''」2点打ちは完全に藤代さんの影響です(笑) # 本題の件ですが、ご負担をお掛けしますがどうぞよろしくお願い致します。 |
|
投稿日時: 12/03/27 23:24:10
投稿者: 藤代千尋
|
---|---|
失礼しました。いろいろミスがあります。(^^;)
引用: ここにもタイプミスが。MyPrope"r"ties ですね。Me や m_ に対抗して mp。 U は、User Defined Type とユーザー定義型の接頭子です。 ○閑話:接頭子 型は大文字で U、E:num、I:nterface、M:odule、(user)F:orm とか。 インスタンス・変数は小文字で、f:lag、b:yte、i:ntegral-number、r:eal-number、s:tring、v:ariant、u:、e:num、o:bject、t:ime、d:ate、c:ounter、p:ointer、h:andle、ix(IndeX→序数)、とか。 ついでに集合は a:rray、n(collectioN) とか。asFiles。 さらにスコープで Pi(Private)、グローバルはあまり認めたくないけど付けるなら独自に 2、3 文字。iRnFixedCols とか。 f As Variant で3値フラグとかあって必ず型一致ではない、システムハンガリアンとアプリケーションハンガリアンの中間みたいな接頭子の付け方をしています。 付けない方が今時かもしれませんけど、ローカルやプライベート領域だと便利なので付けます。パブリック領域やインターフェース部分には付けません。 ○閑話終了 引用: 見ている人に分かるように説明すると。 ' 1 点打ちは、[コメントブロック]コマンドによって一時的に設定されたコメントと同じになってしまう。 '' 2 点打ちにすることにより、プログラマが明確な意志で書いたコメントだと分かるようになります。※もちろん、モジュールやプロシージャのヘッダ書きは見れば分かるので 1 点打ちで良いです。 |
|
投稿日時: 12/03/28 08:41:07
投稿者: kumatti
|
---|---|
前スレの
|
|
投稿日時: 12/03/28 19:41:53
投稿者: どんきち
|
---|---|
角田 さんの引用: 自分が提示した方法は、インタフェースを使ったObserverパターンだと、イベントの通知元が複数あっても通知先では1つのプロシージャで受けとれるのに、RaiseEventとWithEventsだと同じことができない。なんとかできないかと無理やりひねり出した方法です。 通知元のイベント発行処理を変更する必要があるので、ユーザーフォームの複数のコントロールのイベントをまとめることはできません。 「クラスを2重に使う」ことについては、モーグでもVBAのユーザーフォームでコントロール配列を作る方法が紹介さていましたし、気づいている人は気づいていた気がします。 ただ、VBAでは、RaiseEventを使うクラスを作ることがほとんどない。ユーザーフォームではコントロール配列は使えないので複数のイベントプロシージャから共通処理を呼び出してすませる。といった感じで、複数の通知元のイベントを何とかして1つにまとめたい、というところまでたどりついていない人が多いんじゃないかという気がします。 |
|
投稿日時: 12/03/28 19:51:00
投稿者: どんきち
|
---|---|
藤代千尋 さんの引用: For Eachに対応するためだけに内部コレクションをそのまま公開すると、公開したコレクションに要素の追加・削除が行われる可能性があるので、抵抗がありますね。 公開するのであれば、内部コレクションとは別に新たにコレクションのインスタンスを作成して、内部コレクションで管理しているすべての要素を新たに作ったコレクションにAddして、コピーした別インスタンスのコレクションを公開したほうが安全かもしれません。 |
|
投稿日時: 12/03/28 20:05:09
投稿者: どんきち
|
---|---|
Rangeオブジェクトの拡張について
|
|
投稿日時: 12/03/28 21:56:08
投稿者: yayadon
|
---|---|
kumatti さんの引用: Visual C++ 2010 Express 等で,DLL を作成すれば,見れなくもないです。 Express Edition では,(Excel の) プロセスへのアタッチができない関係で, ブレーク ポイントを置いてデバッグするような類ができないので, 内容は,ファイル等に出力するとか,どうにかする必要があります。 # IDispatch と ITypeInfo をどうにかすれば,VBA 内で完結の調査も可能かも。 #include "stdafx.h" #include <comdef.h> //__declspec(dllexport) void __stdcall QueryTypeInfo(IDispatch *pDisp); #pragma comment(linker, "/EXPORT:QueryTypeInfo=?QueryTypeInfo@@YGXPAUIDispatch@@@Z") void __stdcall QueryTypeInfo(IDispatch *pDisp) { UINT ctinfo = 0; HRESULT hr = pDisp->GetTypeInfoCount(&ctinfo); // ctinfo には 1 が返るハズ。 if (FAILED(hr) || ctinfo == 0) return; // http://msdn.microsoft.com/en-us/ // library/windows/desktop/ms221696%28v=vs.85%29.aspx ITypeInfoPtr spTypeInfo; hr = pDisp->GetTypeInfo(0, LOCALE_USER_DEFAULT, &spTypeInfo); if (FAILED(hr)) return; // http://msdn.microsoft.com/en-us/ // library/windows/desktop/ms221003%28v=vs.85%29.aspx TYPEATTR *pTypeAttr = NULL; // CComTypeAttr hr = spTypeInfo->GetTypeAttr(&pTypeAttr); if (FAILED(hr)) { if (pTypeAttr) { spTypeInfo->ReleaseTypeAttr(pTypeAttr); pTypeAttr = NULL; } return; } // 略 --- TYPEATTR のメンバの値の調査/出力 //pTypeAttr->guid; // IID (VBA が適時変更) //pTypeAttr->typekind; // TKIND_DISPATCH // // デュアル インターフェースもこの値になるので, // // TYPEFLAG の TYPEFLAG_FDUAL で判断。 //pTypeAttr->cFuncs; // Public なメソッド(プロパティ含む)の数。 //pTypeAttr->cbSizeVft; // 仮想テーブルのサイズ (Private & Friend 含む) // // TYPEFLAG // http://msdn.microsoft.com/en-us/ // library/windows/desktop/ms221509%28v=vs.85%29.aspx //pTypeAttr->wTypeFlags; // 0x01c2 // // TYPEFLAG_FCANCREATE | TYPEFLAG_FDUAL | // // TYPEFLAG_FNONEXTENSIBLE | TYPEFLAG_FOLEAUTOMATION // http://msdn.microsoft.com/en-us/ // library/windows/desktop/ms221425%28v=vs.85%29.aspx FUNCDESC *pFuncDesc = NULL; // CComFuncDesc // Public なメソッド(プロパティを含む)を列挙。 for (WORD i = 0; i < pTypeAttr->cFuncs; ++i) { hr = spTypeInfo->GetFuncDesc(i, &pFuncDesc); if (FAILED(hr)) break; // 略 --- FUNCDESC のメンバの値の調査/出力 MEMBERID memid = pFuncDesc->memid; const UINT cMaxNames = 10; BSTR bstrNames[cMaxNames] = {0}; UINT cNames; hr = spTypeInfo->GetNames(memid, &bstrNames[0], cMaxNames, &cNames); if (FAILED(hr)) break; // 略 --- bstrNames の値の調査/出力 // 一つのものは,メソッド名のみ。 // 二つ以上あるものは,二つ目からパラメータ名 for (UINT i = 0; i < cNames; ++i) { ::SysFreeString(bstrNames[i]); } if (pFuncDesc) { spTypeInfo->ReleaseFuncDesc(pFuncDesc); pFuncDesc = NULL; } } if (pFuncDesc) spTypeInfo->ReleaseFuncDesc(pFuncDesc); if (pTypeAttr) spTypeInfo->ReleaseTypeAttr(pTypeAttr); return; } VBA 側 '' VBA クラスのインターフェース調査 Private Declare Sub QueryTypeInfo Lib "DLL名.dll" (ByVal pDisp As Object) Private Sub QueryTypeInfo() '' カレント ディレクトリを DLL のある場所に移動 Dim strDllDir As String strDllDir = "DLLがあるディレクトリ" Call ChDrive(strDllDir) Call ChDir(strDllDir) Dim itf As Class1 Set itf = New Class1 Call QueryTypeInfo(itf) End Sub |
|
投稿日時: 12/03/28 21:59:51
投稿者: みそじのおじさん
|
---|---|
藤代 さんの引用: 考えが及ばす、すみません^^; Get Target内でSet Range = となっていましたので、もしかして RangeのSet節の間違いかな?なんて思ったりで頭が混乱しておりました。 やっぱり私は「承継・委譲」といった意味合いをしっかり理解出来ていない ようですね。.netもやっておりますがまだまだ基本的な構文を覚えるのに 四苦八苦しておりまして(スコープの違いや圧倒的なメソッドやプロパティの多さに) 「クラスを使い倒す」域にはまだまだ到達出来て おりません。その域に到達していればこのスレッド自体も立ち上げなかったのかもしれ ませんね^^ 藤代さんには以前書きましたが、私は「プログラムの恥は書き捨てと思っています」 と書いた事があります。 自分の技量をさらけだしてしまいますので、とても恥ずかしいのですが、 その時書けるMAXのコードを出し恥をかいてでも識者の方々に正しい方向へ導いて貰い たいと思っております。 ・色んなジャンルの処理は一通りやってきたつもり ・通常の処理なら困る事はそんなにない。(時にはありますが^^;) ・将来、業務システムを一人(もしくはチーム)で構築出来る男になりたい ・その為にクラスモジュールは絶対押さえておきたい ・がむしゃらにクラスを作り続けているが、しっくりこない どこまで出来て何が足りないのか皆様には見えているのではないでしょうか? こんな私に「足りない物」をざくっと指摘頂けないないでしょうか。 クラスモジュールを扱っていて感じる、この「モヤモヤ感」を打破する ために一つ背中を押してもらえるようご支援頂けると幸いです。 # 皆様がこういった峠をどうやって越えて来たのかとても興味があります^^ |
|
投稿日時: 12/03/28 22:30:26
投稿者: Abyss
|
---|---|
> .netもやっておりますが...
|
|
投稿日時: 12/03/28 22:37:17
投稿者: simple
|
---|---|
# だいぶ発言タイミングが遅れてしまったです。
object.extend ModuleAと言う構文です。 (実際には、objectに対応づけられた特異クラスが作られ、 そこにMix-inされる仕組みです。) -------------------- 以下は、Excelシートの特定範囲の合計値を得る例です。 (現実性はない例示です) # coding: Windows-31J module Worksheet def [] y,x self.Cells.Item(y,x).Value end def []= y,x,value self.Cells.Item(y,x).Value = value end end module RangeEx def sum $xl.WorksheetFunction.Sum(self) end end # -------------- require 'win32ole' # COM を Ruby で扱えるようにする拡張ライブラリ filename = "D:/MyDocuments/201203/test.xlsm" $xl = WIN32OLE.new('Excel.Application') $xl.Visible = true $book = $xl.Workbooks.Open(filename) sheet = $book.Worksheets.Item("Sheet1") sheet.extend Worksheet # (*1) sheet[1,1] = 10 sheet[1,2] = 20 sheet[2,1] = 30 sheet[2,2] = 40 puts sheet[2,2] # => 40.0 が出力される r = $book.Worksheets(1).Range("A1:B2") r.extend RangeEx # (*2) puts r.sum # => 100.0 が出力される -------- ちなみに、 def [] y,x self.Cells.Item(y,x).Value end のところは、[]をメソッドとして定義しています。 []= もメソッドです。 syntactic sugarと呼べるでしょうか。 なお、上記のsheet も r も WIN32OLEのインスタンスであって、 Excelのクラスに属しているわけではありません。 (Abyssさんの発言を受けて記述追加) |
|
投稿日時: 12/03/28 23:17:10
投稿者: 藤代千尋
|
---|---|
どんきち さんの引用: 自分のみのコードは問題ないし、部内コーディング標準にも書かれていることで、今のところ問題ないし、「そこまで手間をかけることかなぁ」と思っていた/いるのですが、自分の投稿に 藤代千尋 さんの引用: とありますね。 何か考えないと。 クローン機能というか変換機能を持った Collection ラッパーを作り、それに保持させておけばいいのか。 Private m_Items As CollectionEx ... Public Property Get Items() As Collection Set Items = m_Items.ToCollection Collection クラスならラッパーとか拡張クラスは作り放題だし、利用するのは自分のコードのみだから、なんの問題も出てきませんね。(^^) |
|
投稿日時: 12/03/29 01:23:43
投稿者: ろひ
|
---|---|
kumatti さんの引用:x64版のエラーメッセージが、will operate without this DLL云々と、無いことが前提の表記だったのと、x86版と同じ「IVIEWERS.DLL」の大文字表記だったせいで気づいてませんでした。 (※x64版はIViewers.dll) kumatti さんの引用:紛らわしいことに、(管理者のアクセス要求が発生する、)名前を変えたり、「IVIEWERS.DLL」を持ってきたりしても、件のエラーメッセージが出るんですね。 (※x64版、x86版ともに、Program Files (x86)配下) kumatti さんの引用:上記を確認したうえで、Oleview.exeを管理者実行したら、全くエラーが出なくなりました。 (※一回きりでなくなったのは嬉しいんですが、なくなるのが逆に納得いかないという…。) エラーメッセージからはジャンクションに起因することがわかりえませんでした。kumattiさんありがとうございます。 |
|
投稿日時: 12/03/29 07:17:26
投稿者: みそじのおじさん
|
---|---|
みなさん、おはようございます。
|