ほっとひといき給湯室 |
|
|
投稿日時: 12/02/25 10:30:52
投稿者: みそじのおじさん
|
|---|---|
|
みなさん、こんにちは。
|
|
|
|
投稿日時: 12/02/25 18:32:26
投稿者: yayadon
|
|---|---|
|
# すみません。
|
|
|
|
投稿日時: 12/02/25 19:38:00
投稿者: ろひ
|
|---|---|
みそじのおじさん さんの引用: みそじのおじさんが扱われているような、Webや書籍でもなかなか見つからない実務・実例的な観点を含んだクラスについての話題が増えるのを、非常に期待しております。 (※他人事のような書き方ですが、私自身、クラスに関してはたいして実務的な経験やネタを持ち合わせてないものですみません。) 一方で、 ・そもそもVBAにおけるクラスやオブジェクトって、どういう理解や考え方をしたらいいのか …についても、加えていただければ。 (※すでに、yayadonさんがコメントされているようなカテゴリの話題ですね。) 例えば、以下のようなことでしょうか。(どこまで把握しとくべきかは置いておくとして。) ◇実行時のクラス情報の取得(Visual Studio 2005)- MSDN http://msdn.microsoft.com/ja-jp/library/80z6k8st(v=vs.80).aspx ◇クラスについて(Visual Studio 2005)- MSDN http://msdn.microsoft.com/ja-jp/library/ca22fyhc(v=vs.80).aspx 併せて、最近のmougでの関連トピックを掲載しておきます。 ◇参照設定とNewキーワードとCreateObject関数 http://www.moug.net/faq/viewtopic.php?t=60032 ◇何をカプセル化と呼ぶのか http://www.moug.net/faq/viewtopic.php?t=60181 |
|
|
|
投稿日時: 12/02/25 21:19:16
投稿者: simple
|
|---|---|
|
うぉー # って、ボキャ貧にも程がある?
|
|
|
|
投稿日時: 12/02/27 01:20:12
投稿者: yayadon
|
|---|---|
|
◆ コレクション と 列挙子
Dim myItem As Range
For Each myItem In Range("C1:C10")
myItem.Value = ...
Next
を見てみます。 まず, Rangeオブジェクトを操作するというところをわかりやすくするために, 上記コードの Range("C1:C10") プロパティのところは, 返ってくるRangeオブジェクト(参照)を,一度,変数に受けて Dim myRange As Range
Dim myItem As Range
Set myRange = Range("C1:C10")
For Each myItem In myRange
myItem.Value = ...
Next
とすることにします。 そして, これに対応する For ... Next 文は, Dim i As Long
Dim cnt As Long
Dim myRange As Range
Set myRange = Range("C1:C10")
cnt = myRange.Count
For i = 1 To cnt
myItem.Item(i).Value = ...
Next
になります。 列挙子による For Each ... In に対して, インデックスアクセスになるのは,Item による For ... Next になります。 Count / Item / _NewEnum の3つのメンバーの関係と, それらが,どうしてコレクションに必要なメンバーなのかが なんとなく理解できると思います。 |
|
|
|
投稿日時: 12/02/27 15:38:05
投稿者: 月
|
|---|---|
みそじのおじさん さんの引用: データと、データと関連する関数をクラスにします。 Public/Private変数がプロパティになり、Sub/Functionプロシージャがメソッドになります。 みそじのおじさん さんの引用: 実例で話したいですね。 みそじのおじさん さんの引用: honda0510/Google-Calendar-Library-for-VBA - GitHub https://github.com/honda0510/Google-Calendar-Library-for-VBA みそじのおじさん さんの引用: とりあえずインスタンス作成時に引数を渡したいですね。 どなたでも結構ですが、これ、クラスを使って書き換えるならどうしますか?みたいなコードをアップしてもらえないですかね?それでどうクラスにするのかしないのかの塩梅がわかると思います。 みそじのおじさん さんの引用: これですかね?参考になったなら幸いです。 PC研究室2nd 投稿日時: 11/10/25 14:05:02 投稿者: 月 さんの引用: |
|
|
|
投稿日時: 12/02/27 21:42:27
投稿者: ゴマ
|
|---|---|
|
一昔前、モーグ・スキルアップ講座
|
|
|
|
投稿日時: 12/02/28 00:01:59
投稿者: みそじのおじさん
|
|---|---|
|
yayadonさん、ろひさん、simpleさん、月さん、ゴマさん。
引用: 私自身ほとんど「駄作」クラスしかもっておりません。(100以上ありますが、どれもこれも、、) 「実務・実例的な観点を含んだクラス」をもっともっと見たいのですが やはりweb上には情報が少なくこのスレッドを立ち上げさせてもらいました。 引き続きご参加頂けると幸いです。 ▽月さん 月さんご紹介のクラスはアップされた当時、すぐダウンロードさせてもらっていました^^ 中身を見させて頂いきましたが、自分との力の差に愕然としたのを今でも覚えています。 「こういった実用的なクラスをいつか私も書きたいんだ!」 「今の私に足りない知識は何なのか?何を勉強したらいいのか?」とかなりへこみました。 発奮材料にもかなりなりましたが。 月さんご提案の、「一つのお題をみなさんがそれぞれクラス化してみる」のはとてもおも しろそうですね。クラスの設計からコーディング、実装までを見させて頂きますと 自分の考えの至らなさがはっきり見えてくると思います。 私には適切なお題はとても出せそうにもありませんから、どなたかお願い致します^^ # 月さんの発言だったのですね。思い出せなく申し訳ありません^^ ▽ゴマさん ご紹介ありがとうございます。 VBAをやり始めて、「クラスの存在を知る」のはほとんどこのパターンですよね。 私もそのくちでした^^ このスレッドを見て下さっているクラスを使った事がない方には 是非試してもらいたいですね。クラスを扱うほんの入り口ですが 「その魅力」を是非体感してもらいたいと思います。 ▽simpleさん 質問者としての質問の仕方は慣れていますが、こういった議論の進め方には全く 不慣れで手際が悪い私ですので、どうかフォローの程よろしくお願い致します。 それではみなさま、引き続きよろしくお願い致します。 |
|
|
|
投稿日時: 12/02/28 09:00:01
投稿者: kumatti
|
|---|---|
|
# 既にご存知でしょうけど、ROMの方向けに。
|
|
|
|
投稿日時: 12/02/28 13:01:48
投稿者: yayadon
|
|---|---|
|
# 話が脱線するんですが...
Private Sub Ref3(ByRef wsh As Worksheet)
Debug.Print Hex(VarPtr(wsh))
End Sub
Private Sub Ref2(ByRef wsh As Worksheet)
Debug.Print Hex(VarPtr(wsh))
Call Ref3(wsh)
End Sub
Private Sub Ref1(ByRef wsh As Worksheet)
Debug.Print Hex(VarPtr(wsh))
Call Ref2(wsh)
End Sub
Private Sub CommandButton1_Click()
Dim wsh As Worksheet
Set wsh = ActiveSheet
Debug.Print Hex(VarPtr(wsh))
Call Ref1(wsh)
End Sub
' 12E2B4
' 12E2B4
' 12E2B4
' 12E2B4
例外もありますが,その例外も, オブジェクト参照がもう一段階間接的になるのは, ByRef Variant型 の引数に渡した時だけのような気がします。 |
|
|
|
投稿日時: 12/03/01 22:04:06
投稿者: simple
|
|---|---|
|
私は、IT企業とは無縁の会社に勤める、単なる物好き?です。
|
|
|
|
投稿日時: 12/03/02 00:02:37
投稿者: みそじのおじさん
|
|---|---|
|
kumattiさん。ご参加ありがとうございます。(ご無沙汰しております^^)
引用: まずは、言いだしっぺの私から勉強の為に作成した上記のクラスを提示してみます。 ご感想や、改善点など頂けると幸いです。 作成に当たって主眼を置いていたのは ・コレクションクラスの作成 ・コレクションクラス同士の連携 です。 作成した4つのクラスは ・clsMoveItem アイテム ・clsMoveItems アイテムのコレクションクラス ・clsContainer アイテムを収容するコンテナ ・clsContainers コンテナのコレクションクラス 4つのクラスの関係のイメージ
clsMoveItems ←→ 協調関係(互いに存在しないと動作しない) ←→ clsContainers
↑ ↑
親子関係 親子関係
↓ ↓
clsMoveItem clsContainer
UsefFormをお一つご用意下さい。利用するコントロールは全て動的作成しますので 何も配置しなくて結構です。 使用する物 ・UserFormを1つ ・標準モジュール ・クラスモジュール4つです。 まずはUserFormモジュールです。
Option Explicit
Private mMoveItems As clsMoveItems
Private WithEvents mContainers As clsContainers
Private WithEvents mCommandButton As MSForms.CommandButton
Private mTextBox As MSForms.TextBox
Private Const cnsTop As Single = 30
Private Sub UserForm_Initialize()
Dim ctl As Control
Dim i As Long
Dim Container As MSForms.Label
Dim Item As MSForms.Label
With Me
.Width = 640
.Height = 320
End With
''コンテナのコレクションクラス作成
Set mContainers = New clsContainers
''コンテナの作成
With mContainers
.CreateContainer Me, "Moug", 20, cnsTop, 100, 140
.CreateContainer Me, "Excel", 170, cnsTop, 100, 140
.CreateContainer Me, "Access", 320, cnsTop, 100, 140
.CreateContainer Me, "Word", 470, cnsTop, 100, 140
End With
With mContainers("Moug")
.Value = "モーグ"
.CreateTitle "Moug"
End With
With mContainers("Excel")
.Value = "Excel"
.CreateTitle "ExcelVBA講座"
.MaxItemCount = 3
End With
With mContainers("Access")
.Value = "Access"
.CreateTitle "AccessVBA講座"
.MaxItemCount = 6
End With
With mContainers("Word")
.Value = "Word"
.CreateTitle "WordVBA講座"
.MaxItemCount = 3
End With
''アイテムのコレクションクラス作成
Set mMoveItems = New clsMoveItems
Set mMoveItems.Containers = mContainers
''アイテムの作成
Dim vntName
Dim Member
Member = Array("yayadonさん", "ろひさん", "simpleさん", _
"月さん", "ゴマさん", "kumattiさん", "YU-TANGさん", _
"Kanabunさん", "Abyssさん", "みそじのおじさん")
For Each vntName In Member
Set Item = Me.Controls.Add("Forms.Label.1", "Label" & vntName, True)
With Item
.Height = 20
.Width = 70
.Caption = vntName
End With
mMoveItems.Add Item, Me
mMoveItems(Item.Name).Value = vntName
mContainers("Moug").AddItem mMoveItems(Item.Name)
Next
''コマンドボタンの作成
Set mCommandButton = Me.Controls.Add("Forms.CommandButton.1", _
"myCommandButton", True)
With mCommandButton
.Left = 20
.Top = 140
.Height = 20
.Width = 50
.Caption = "決定"
End With
''グループ分け結果の表示用テキストボックス作成
Set mTextBox = Me.Controls.Add("Forms.TextBox.1", "myTextBox", True)
With mTextBox
.Left = 80
.Top = 160
.Height = 120
.SpecialEffect = fmSpecialEffectEtched
.Width = 140
.MultiLine = True
End With
''見出しのラベル作成
Set Item = Me.Controls.Add("Forms.Label.1", "lbl1", True)
With Item
.Left = 80
.Top = 140
.Height = 20
.SpecialEffect = fmSpecialEffectEtched
.Width = 140
.Caption = "グループ分け結果"
.TextAlign = fmTextAlignCenter
End With
End Sub
Private Sub mCommandButton_Click()
Dim ContainerItems As Collection
Dim ContainerItem As clsContainer
Dim MoveItems As Collection
Dim MoveItem As clsMoveItem
Dim v() As Variant
Dim i As Long
Set ContainerItems = mContainers.Items
For Each ContainerItem In ContainerItems
For Each MoveItem In ContainerItem.Items
ReDim Preserve v(i)
v(i) = ContainerItem.Value & vbTab & MoveItem.Value
i = i + 1
Next
Next
mTextBox.Text = Join(v, vbCrLf)
End Sub
Private Sub mContainers_MaxItemCountOver(ByVal AlertContainer As clsContainer, _
ByVal MaxItemCount As Long)
MsgBox AlertContainer.Title & "の最大定員は、" & MaxItemCount & "名です。", vbExclamation
End Sub
Private Sub UserForm_Terminate()
mMoveItems.Tearm
mContainers.Term
Set mMoveItems = Nothing
Set mContainers = Nothing
End Sub
クラスモジュール4つです。
Option Explicit
'***************************************************
'クラス名 clsContainers
'***************************************************
Public Event MaxItemCountOver(ByVal AlertContainer As clsContainer, _
ByVal MaxItemCount As Long)
Private mContainerItems As Collection
Private Sub Class_Initialize()
Set mContainerItems = New Collection
End Sub
Public Sub Term()
Dim Container As clsContainer
For Each Container In mContainerItems
Container.Team
Next
Set mContainerItems = Nothing
End Sub
Private Sub Class_Terminate()
'Debug.Print "clsContainers_Term!"
End Sub
Public Function Add(ByVal NewContainerItem As MSForms.Label) As Boolean
Dim ContainerItem As clsContainer
Set ContainerItem = New clsContainer
mContainerItems.Add ContainerItem.Init(NewContainerItem, Me), _
CStr(NewContainerItem.Name)
End Function
Public Function CreateContainer(ByVal Parent As Object, _
ByVal ContainerName As String, _
ByVal Left As Single, _
ByVal Top As Single, _
ByVal Height As Single, _
ByVal Width As Single) As Boolean
Dim ContainerItem As clsContainer
Dim CreateContainerLabel As MSForms.Label
Set ContainerItem = New clsContainer
Set CreateContainerLabel = Parent.Controls.Add _
("Forms.Label.1", ContainerName, True)
With CreateContainerLabel
.Left = Left
.Top = Top
.Height = Height
.Width = Width
.SpecialEffect = fmSpecialEffectEtched
End With
mContainerItems.Add ContainerItem.Init(CreateContainerLabel, Me), _
CStr(CreateContainerLabel.Name)
End Function
Public Property Get Items() As Collection
Set Items = mContainerItems
End Property
Public Property Get Item(ByVal Index As Variant) As clsContainer
On Error GoTo Err_Trap:
Select Case TypeName(Index)
Case "Long", "Integer"
'CollectionのLBoundは1の為+1
Set Item = mContainerItems(CLng(Index) + 1&)
Case "String"
'フィールド名でアクセス
Set Item = mContainerItems(Index)
End Select
On Error GoTo 0
Err_Trap:
End Property
Public Sub Alert(ByVal c As clsContainer)
RaiseEvent MaxItemCountOver(c, c.MaxItemCount)
End Sub
Public Sub OverLapEffect(ByVal TargetContainer As clsContainer)
Dim Container As clsContainer
For Each Container In mContainerItems
If TargetContainer Is Container Then
Container.OverLapEffect
Else
Container.UndoOverLapEffect
End If
Next
End Sub
Public Sub UndoOverLapEffect()
Dim Container As clsContainer
For Each Container In mContainerItems
Container.UndoOverLapEffect
Next
End Sub
Option Explicit
'***************************************************
'クラス名 clsContainer
'***************************************************
Private mContainer As MSForms.Label
Private mTitleLabel As MSForms.Label
Private mItems As Collection
Private mItemPositions As Collection
Private mParent As clsContainers
Private mValue As Variant
Private mMaxItemCount As Long
Private mMaxCountCheck As Boolean
Private mTitle As String
Private mTitleForeColor As Long
Private mTitleBackColor As Long
Private mContainerForeColor As Long
Private mContainerBackColor As Long
Private Const cnsTitleHeight As Single = 18
Private Const cnsTitleForeColor As Long = vbWhite
Private Const cnsTitleBackColor As Long = &H800000
Private Const cnsContainerForeColor As Long = vbBlue
Private Const cnsContainerBackColor As Long = vbWhite
Public Function Init(ByVal NewContainer As MSForms.Label, _
ByVal NewParent As clsContainers)
Set mContainer = NewContainer
With mContainer
.BackColor = cnsContainerBackColor
.SpecialEffect = fmSpecialEffectEtched
End With
Set mParent = NewParent
Set Init = Me
End Function
Private Sub Class_Initialize()
Set mItems = New Collection
Set mItemPositions = New Collection
mMaxCountCheck = False
mTitleForeColor = cnsTitleForeColor
mTitleBackColor = cnsTitleBackColor
End Sub
Private Sub Class_Terminate()
Set mContainer = Nothing
Set mTitleLabel = Nothing
End Sub
Public Sub Team()
Dim MoveItem As clsMoveItem
For Each MoveItem In mItems
MoveItem.Term
Next
Set mItems = Nothing
Set mParent = Nothing
End Sub
Public Function CreateTitle(ByVal NewTitle As String) As Boolean
If Not mTitleLabel Is Nothing Then Exit Function
Set mTitleLabel = mContainer.Parent.Controls.Add _
("Forms.Label.1", "Label" & Title, True)
With mTitleLabel
.Left = mContainer.Left
.Top = mContainer.Top - cnsTitleHeight
.Height = cnsTitleHeight
.Width = mContainer.Width
.SpecialEffect = fmSpecialEffectEtched
.ForeColor = mTitleForeColor
.BackColor = mTitleBackColor
.TextAlign = fmTextAlignCenter
.Font.Bold = True
Title = NewTitle
End With
End Function
Public Property Get Title() As String
Title = mTitle
End Property
Public Property Let Title(ByVal NewTitle As String)
mTitle = NewTitle
If Not mTitleLabel Is Nothing Then
mTitleLabel.Caption = mTitle
End If
End Property
Public Property Get ItemPositions() As Collection
Set ItemPositions = mItemPositions
End Property
Property Get ContainerRect() As Rect
With mContainer
ContainerRect.Left = .Left
ContainerRect.Top = .Top
ContainerRect.Width = .Width
ContainerRect.Height = .Height
End With
End Property
Public Function AddItem(ByVal tItem As clsMoveItem) As MoveItemAddResultEnum
Dim Item As clsMoveItem
If Not IsInCollection(tItem) Then
If (MaxItemCount >= ItemCount + 1) Or (Not mMaxCountCheck) Then
Call ItemMove(tItem)
mItems.Add tItem, CStr(tItem.Name)
With tItem.ItemRect
mItemPositions.Add Array(.Left, .Top, .Width, .Height, _
CStr(tItem.Name)), CStr(tItem.Name)
End With
AddItem = MoveItemAddOk
Else
mParent.Alert Me
AddItem = MoveItemMaxCountOver
End If
Else
Set mItemPositions = Nothing
Set mItemPositions = New Collection
For Each Item In Me.Items
With Item.OldPositionRect
mItemPositions.Add Array(.Left, .Top, .Width, .Height, _
CStr(Item.Name)), CStr(Item.Name)
End With
Next
Call ItemMoveAnchor(tItem, mItemPositions(CStr(tItem.Name)))
AddItem = MoveItemAlreadyAdd
End If
End Function
Public Sub RemoveItem(ByVal rItem As clsMoveItem)
If IsInCollection(rItem) Then
mItems.Remove CStr(rItem.Name)
mItemPositions.Remove CStr(rItem.Name)
AllItemMove
End If
End Sub
Private Sub AllItemMove()
Dim Item As clsMoveItem
Dim t As Single, lw As Single
Dim Ih As Single, Iw As Single
Dim OldRect As Rect
Dim NewRect As Rect
Dim MaxWidth As Single
Dim SameFg As Boolean
For Each Item In mItems
OldRect = Item.ItemRect
Iw = Item.ItemRect.Width
If MaxWidth < Iw Then
MaxWidth = Iw
End If
If t + OldRect.Height > mContainer.Height Then
t = 0
lw = lw + MaxWidth
MaxWidth = 0
End If
With NewRect
.Left = mContainer.Left + lw
.Top = mContainer.Top + t
.Width = OldRect.Width
.Height = OldRect.Height
End With
Item.ItemRect = NewRect
t = t + OldRect.Height
Next
End Sub
Private Sub ItemMove(ByVal MoveItem As clsMoveItem)
Dim Item As clsMoveItem
Dim t As Single, lw As Single
Dim Ih As Single, Iw As Single
Dim OldRect As Rect
Dim NewRect As Rect
Dim MaxWidth As Single
OldRect = MoveItem.ItemRect
For Each Item In mItems
Ih = Item.ItemRect.Height
Iw = Item.ItemRect.Width
t = t + Ih
If MaxWidth < Iw Then
MaxWidth = Iw
End If
If t + Ih > mContainer.Height Then
t = 0
lw = lw + MaxWidth
MaxWidth = 0
End If
Next
With NewRect
.Left = mContainer.Left + lw
.Top = mContainer.Top + t
.Width = OldRect.Width
.Height = OldRect.Height
End With
MoveItem.ItemRect = NewRect
End Sub
Private Sub ItemMoveAnchor(ByVal MoveItem As clsMoveItem, _
RectArray As Variant)
Dim DefaultRect As Rect
With DefaultRect
.Left = RectArray(0)
.Top = RectArray(1)
.Width = RectArray(2)
.Height = RectArray(3)
End With
MoveItem.ItemRect = DefaultRect
End Sub
Private Function IsInCollection(ByVal Item As clsMoveItem) As Boolean
Dim obj As clsMoveItem
IsInCollection = False
For Each obj In mItems
If obj Is Item Then
IsInCollection = True
Exit For
End If
Next
End Function
Property Get Value() As Variant
Value = mValue
End Property
Property Let Value(ByVal NewValue As Variant)
mValue = NewValue
End Property
Property Get Name() As Variant
Name = mContainer.Name
End Property
Property Get Items() As Collection
Set Items = mItems
End Property
Property Get ItemCount() As Long
ItemCount = 0
If mItems Is Nothing Then Exit Property
ItemCount = mItems.Count
End Property
Property Get MaxItemCount() As Long
MaxItemCount = mMaxItemCount
End Property
Property Let MaxItemCount(ByVal NewMaxCount As Long)
If NewMaxCount < 0 Then
MsgBox "MaxItemCountniに設定できるのは0以上です。", vbCritical
Exit Property
End If
mMaxItemCount = NewMaxCount
MaxCountCheck = True
End Property
Public Property Get MaxCountCheck() As Boolean
MaxCountCheck = mMaxCountCheck
End Property
Public Property Let MaxCountCheck(ByVal blnMaxCountCheck As Boolean)
mMaxCountCheck = blnMaxCountCheck
End Property
Public Property Get TitleForeColor() As Long
TitleForeColor = mTitleForeColor
End Property
Public Property Let TitleForeColor(ByVal lngNewColor As Long)
mTitleForeColor = lngNewColor
If Not mTitleLabel Is Nothing Then
mTitleLabel.ForeColor = mTitleForeColor
End If
End Property
Public Property Get TitleBackColor() As Long
TitleBackColor = mTitleBackColor
End Property
Public Property Let TitleBackColor(ByVal lngNewColor As Long)
mTitleBackColor = lngNewColor
If Not mTitleLabel Is Nothing Then
mTitleLabel.BackColor = mTitleBackColor
End If
End Property
Public Sub OverLapEffect()
With mContainer
.BorderStyle = fmBorderStyleSingle
.BorderColor = vbBlue
End With
End Sub
Public Sub UndoOverLapEffect()
With mContainer
.SpecialEffect = fmSpecialEffectEtched
End With
End Sub
Option Explicit
'***************************************************
'クラス名 clsMoveItems
'***************************************************
Private mMoveItems As Collection
Private mFormObject As Object
Private mContainers As clsContainers
Public Function Add(ByVal NewMoveItem As MSForms.Label, _
ByVal mFormObject As Object) As Boolean
Dim mMoveItem As clsMoveItem
Set mMoveItem = New clsMoveItem
Set mFormObject = mFormObject
mMoveItems.Add mMoveItem.Init(NewMoveItem, mFormObject, Me), _
CStr(NewMoveItem.Name)
End Function
Private Sub Class_Initialize()
Set mMoveItems = New Collection
End Sub
Private Sub Class_Terminate()
'Debug.Print "clsMoveItems_Term!"
End Sub
Public Sub Tearm()
Dim MoveItem As clsMoveItem
For Each MoveItem In mMoveItems
MoveItem.Term
Next
Set mMoveItems = Nothing
Set mFormObject = Nothing
Set mContainers = Nothing
End Sub
Property Get Containers() As clsContainers
Set Containers = mContainers
End Property
Property Set Containers(ByVal NewContainers As clsContainers)
Set mContainers = NewContainers
End Property
Property Get Item(ByVal Index As Variant) As clsMoveItem
On Error GoTo Err_Trap:
Select Case TypeName(Index)
Case "Long", "Integer"
'CollectionのLBoundは1の為+1
Set Item = mMoveItems(CLng(Index) + 1&)
Case "String"
'フィールド名でアクセス
Set Item = mMoveItems(Index)
End Select
On Error GoTo 0
Err_Trap:
End Property
Option Explicit
'***************************************************
'クラス名 clsMoveItem
'***************************************************
Private WithEvents mMoveLabel As MSForms.Label
Private mFormObject As Object
Private mParent As clsMoveItems
Private mCapture As Boolean
Private mValue As Variant
Private mOldPositionRect As Rect
Private Type StructOffsetValue
x As Single
y As Single
End Type
Private o As StructOffsetValue
Private Const cnsItemBackColor As Long = vbWhite
Private Const cnsItemForeColor As Long = vbBlue
Public Function Init(ByVal NewMoveLabel As MSForms.Label, _
ByVal NewmFormObject As Object, _
ByVal NewParent As clsMoveItems) As clsMoveItem
Set mMoveLabel = NewMoveLabel
With mMoveLabel
.TextAlign = fmTextAlignCenter
.ForeColor = cnsItemForeColor
.BackColor = cnsItemBackColor
.SpecialEffect = fmSpecialEffectEtched
End With
Set mFormObject = NewmFormObject
Set mParent = NewParent
Set Init = Me
End Function
Public Sub Term()
Set mFormObject = Nothing
Set mParent = Nothing
End Sub
Private Sub Class_Terminate()
Set mMoveLabel = Nothing
End Sub
Private Sub mMoveLabel_MouseDown(ByVal Button As Integer, _
ByVal Shift As Integer, _
ByVal x As Single, _
ByVal y As Single)
mCapture = True
With mMoveLabel
.SpecialEffect = fmSpecialEffectBump
.BackColor = vbYellow
End With
o.x = x
o.y = y
Call OldPositionSet
End Sub
Private Sub mMoveLabel_MouseMove(ByVal Button As Integer, _
ByVal Shift As Integer, _
ByVal x As Single, _
ByVal y As Single)
If mCapture Then
Static p As StructMovePoint
With mMoveLabel
If p.x + .Width <= mFormObject.InsideWidth Then
If p.x >= 0 Then
If p.y >= 0 Then
If p.y + .Height - o.y <= mFormObject.InsideHeight Then
.Left = .Left + x - o.x
.Top = .Top + y - o.y
Else
.Top = mFormObject.InsideHeight - .Height
End If
Else
.Top = 0
End If
Else
.Left = 0
End If
Else
.Left = mFormObject.InsideWidth - .Width
End If
p.x = .Left
p.y = .Top
.ZOrder 0
Call mParent.Containers.OverLapEffect(OverlapContainer())
End With
End If
End Sub
Private Sub mMoveLabel_MouseUp(ByVal Button As Integer, _
ByVal Shift As Integer, _
ByVal x As Single, _
ByVal y As Single)
o.x = 0
o.y = 0
If mCapture Then
Call PositionCheck
End If
mCapture = False
With mMoveLabel
.SpecialEffect = fmSpecialEffectEtched
.BackColor = cnsItemBackColor
End With
Call mParent.Containers.UndoOverLapEffect
End Sub
Public Sub PositionCheck()
Dim Containers As clsContainers
Dim Container As clsContainer
Dim ret As MoveItemAddResultEnum
Dim tContainer As clsContainer
Dim Area As Long, MaxArea As Long
Dim ConItems As Collection
Set Containers = mParent.Containers
Set tContainer = OverlapContainer()
Set ConItems = New Collection
If Not tContainer Is Nothing Then
ConItems.Add tContainer
End If
For Each Container In Containers.Items
If Not Container Is tContainer Then
ConItems.Add Container
End If
Next
For Each Container In ConItems
If OverLapArea(Container.ContainerRect) > 0 Then
ret = Container.AddItem(Me)
Select Case ret
Case MoveItemMaxCountOver
ItemRect = OldPositionRect()
Case Else
End Select
Else
Container.RemoveItem Me
End If
Next
Set ConItems = Nothing
Call OldPositionSet
End Sub
Private Function OverlapContainer() As clsContainer
Dim Containers As clsContainers
Dim Container As clsContainer
Dim Area As Long, MaxArea As Long
Set Containers = mParent.Containers
''重なっている領域が最大のコンテナを取得
For Each Container In Containers.Items
Area = OverLapArea(Container.ContainerRect)
If Area > MaxArea Then
Set OverlapContainer = Container
MaxArea = Area
End If
Next
End Function
Private Function OverLapArea(ContainerRect As Rect) As Long
Dim lngMoveItem As Long
Dim MoveItemRect As Rect
Dim ConRect As Rect
Dim lngRet As Long
Dim InRect As Rect
Dim mRect As Rect
MoveItemRect = ItemRect()
With MoveItemRect
mRect.Left = .Left
mRect.Top = .Top
mRect.Width = .Left + .Width
mRect.Height = .Top + .Height
End With
ConRect = ContainerRect
With ConRect
.Left = .Left
.Top = .Top
.Width = .Left + .Width
.Height = .Top + .Height
End With
lngRet = IntersectRect(InRect, mRect, ConRect)
With InRect
OverLapArea = (.Width - .Left) * (.Height - .Top)
End With
End Function
Property Get OldPositionRect() As Rect
OldPositionRect = mOldPositionRect
End Property
Private Sub OldPositionSet()
With mMoveLabel
mOldPositionRect.Left = .Left
mOldPositionRect.Top = .Top
mOldPositionRect.Width = .Width
mOldPositionRect.Height = .Height
End With
End Sub
Property Get ItemRect() As Rect
With mMoveLabel
ItemRect.Left = .Left
ItemRect.Top = .Top
ItemRect.Width = .Width
ItemRect.Height = .Height
End With
End Property
Property Let ItemRect(NewRect As Rect)
With mMoveLabel
.Left = NewRect.Left
.Top = NewRect.Top
.Width = NewRect.Width
.Height = NewRect.Height
End With
Call OldPositionSet
End Property
Property Get Name() As String
Name = mMoveLabel.Name
End Property
Property Get Value() As Variant
Value = mValue
End Property
Property Let Value(ByVal NewValue As Variant)
mValue = NewValue
End Property
最後に標準モジュールです。
Option Explicit
Public Type StructMovePoint
x As Single
y As Single
End Type
Public Type Rect
Left As Long
Top As Long
Width As Long
Height As Long
End Type
Public Declare Function CreateRectRgn Lib "gdi32.dll" _
(ByVal nLeft As Long, _
ByVal nTop As Long, _
ByVal nRight As Long, _
ByVal nBotomm As Long) As Long
Public Declare Function RectInRegion Lib "gdi32.dll" _
(ByVal hrgn As Long, _
lprc As Rect) As Long
Public Declare Function IntersectRect Lib "user32" _
(lpDestRc As Rect, _
lprc1 As Rect, _
lprc2 As Rect) As Long
Public Enum MoveItemAddResultEnum
MoveItemNotAdd = 0
MoveItemAddOk = 1
MoveItemAlreadyAdd = 2
MoveItemMaxCountOver = 3
End Enum
UserFormを立ち上げればグループ分けが出来るようになっております。 CommandButtonで結果が表示されます。 Constでの決め打ちではなく、プロパティにした方がいい所やまわり くどい処理をしている部分が沢山ありますが、 是非お試しになってもらい感想やご意見を頂けると幸いです。 # RECTの定義が間違っております。終盤になって気づき手遅れになってしまいました^^; # お名前を勝手に上げさせてもらい申し訳ありません。このスレッドへのお誘いの意味も # 含めております^^ |
|
|
|
投稿日時: 12/03/02 03:29:55
投稿者: YU-TANG
|
|---|---|
|
こんばんは、YU-TANG です。
みそじのおじさん さんの引用: いや、すごいですね。 私は Excel って家計簿くらいにしか使ってないので、ユーザーフォームでこんなことができるとは想像もしませんでした。 ラベルをドラッグして、箱にピタッと収まるのがシンプルに気持ちいいですね。つい繰り返してしまいます。 小学生並みの感想ですいません。 改善点は、特に見当たらないです。 clsMoveItems.Tearm() のメソッド名が typo くさい気がしたくらいかな。 自分は明らかにクラスを使いまくっている方なんですが、特にこだわりとか主義・主張がないので発信したいことがなくて、ご指名いただくまで ROM ってました。 月 さんの引用: これは同感。 まあ、いまさら言っても VBA がバージョンアップすることはないでしょうけれど。 みそじのおじさん さんの引用: 自サイトで公開しているデモは見れば分かるので、公開していないのからピックアップすると、JScript で Office マクロを書けるようにしたクラスが一番使用頻度高いですかね。VBA モジュールのコメントブロックに JScript を書いておくと、ScriptControl 経由で実行してくれます。おかげで最近は VBA あんまり使ってなくて、JScript 使ってます。VBA に無い機能を使えるので、けっこう便利です。 あと Google カレンダーの操作クラスもあったんですが、HDD と共にお亡くなりになりました。生きていれば月さんのクラスと対戦できたんですが…。 そういえば、なんかクラスの使い方がよく分からない的な投稿を定期的に見かけるんで、ずっと不思議に思ってたんですよ。だって msdn に VB6 のオンラインヘルプがあるんだから、それ読めば分かる話じゃないですか。 …と思ってさっき確認したら、いつのまにか日本語版が消えてるよw これはたしかに、最近のユーザーは英語読めないとアウト。(-人-) |
|
|
|
投稿日時: 12/03/02 10:59:11
投稿者: 月
|
|---|---|
|
simpleさん、ご質問ありがとうございます。
simple さんの引用: 必要ないと思っています。 特にpasswordについてはやっちゃマズイだろうと思っています。 予期せず漏れたりしたら大変なので。 Excelファイルもパスワードを設定できますが、取得できませんよね。 simple さんの引用: いえ、マズくないです。 このクラスはまだ未完成でして、今後他で使うことがあるかもしれないと思ってPrivate変数にしました。 simple さんの引用: とりあえず後者です。 クラスというからには、というと違和感を感じるんですよね。 クラスから見て考えるのではなく、散らばったものをまとめたものがクラスだと思っています。 |
|
|
|
投稿日時: 12/03/02 11:10:45
投稿者: ちび坊主
|
|---|---|
|
こんにちは。
|
|
|
|
投稿日時: 12/03/02 11:19:50
投稿者: 月
|
|---|---|
simple さんの引用: Option Explicit
' GoogleCalendarモジュール
Public Sub Initialize()
End Sub
Public Sub login()
End Sub
Public Sub add()
End Sub
Public Sub Terminate()
End Sub
こういうことですよね? 大違いだと思います。 グローバル領域に関数が登録されています。 GoogleCalendar.login とメソッドっぽく書けるというだけで、loginだけでも呼び出せます。 また、もし同名の関数がプロジェクト内にあったら競合する可能性があります。 例えば、 ' Module1モジュール Public Sub login() End Sub
' Module2モジュール
Sub test()
login
End Sub
とあり、そこへ上記のGoogleCalendarモジュールをインポートしたら、test()のloginがコンパイルエラーになります。 クラスのメソッドとすることで名前の競合が起きません。 クラス化するメリットのひとつですね。 |
|
|
|
投稿日時: 12/03/02 11:25:12
投稿者: 月
|
|---|---|
YU-TANG さんの引用: そうですね、ネットワーク対戦したかったですね。できません。 みそじのおじさんのクラスはあとで試してみよ〜。 |
|
|
|
投稿日時: 12/03/02 11:41:03
投稿者: ちび坊主
|
|---|---|
|
先ほどのみそじのおじさん さんのコードのエラーの対策ですが、
|
|
|
|
投稿日時: 12/03/02 14:02:49
投稿者: 月
|
|---|---|
|
みそじのおじさんへ。
For Each vntName In MemberFor Each Member In Members の方が自然に見える。 UserForm1クラス mMoveItems.Add Item, Me
mMoveItems.Item(Item.Name).Value = vntName
mContainers.Items("Moug").AddItem mMoveItems.Item(Item.Name)
1. 追加して 2. 追加したものを取得して 3. また追加 に違和感を感じる。 ※コードをちゃんと追っていないことをご了承ください。 |
|
|
|
投稿日時: 12/03/02 15:08:02
投稿者: 月
|
|---|---|
|
メソッドチェーンをご存知ですか?
|
|
|
|
投稿日時: 12/03/02 16:34:31
投稿者: yayadon
|
|---|---|
|
みそじのおじさん さんへ
|
|
|
|
投稿日時: 12/03/02 20:25:14
投稿者: 月
|
|---|---|
|
今iPhoneでみそじのおじさんのコード読み中。幅がなくてつらい。
|
|
|
|
投稿日時: 12/03/02 20:30:33
投稿者: 月
|
|---|---|
|
もっと言うと、clsContainers コンテナのコレクションクラス は、ユーザーフォームのプロパティにしたいですね。そうすればMeを渡さなくて済むのでは。 |
|
|
|
投稿日時: 12/03/02 20:34:41
投稿者: 月
|
|---|---|
|
余談ですが、私がAccess VBAで画面がある物を作っていた時も、フォームのメンバとして実装していましたね。親フォームと子フォームという状況があって、フォーム同士でデータか何かをやりとりすることがよくあったんですが、そういう時に相手のメンバを呼び出すということです。 |
|
|
|
投稿日時: 12/03/02 20:48:54
投稿者: 月
|
|---|---|
|
clsMoveItems アイテムのコレクションクラス は、clsContainer アイテムを収容するコンテナ のインスタンス作成時にNewして、プロパティとして公開
|
|
|
|
投稿日時: 12/03/02 21:11:38
投稿者: 月
|
|---|---|
|
CreateContainerメソッドとは別にAddメソッドもあるんですね。どっちかいらなくないですか?
|
|
|
|
投稿日時: 12/03/02 23:05:44
投稿者: みそじのおじさん
|
|---|---|
|
遅くなりました。申し訳ありません。
|
|
|
|
投稿日時: 12/03/02 23:46:47
投稿者: simple
|
|---|---|
|
すごいソースが提示されまして、インパクトがありすぎますね。
|
|
|
|
投稿日時: 12/03/03 08:28:44
投稿者: kumatti
|
|---|---|
|
# 上級者の方々がコメントされていまして、大して言える事もないのですが。
|
|
|
|
投稿日時: 12/03/03 15:26:29
投稿者: Abyss
|
|---|---|
|
> 井川さんが、VBAでも使える事を見付けた(広めた)印象を受けます。
|
|
|
|
投稿日時: 12/03/03 15:57:25
投稿者: Abyss
|
|---|---|
引用: 釣られました。(笑) yayadonさん同様、斬新なUserInterfaceにはMSFormsの可能性が感じられるほどでした。 こんな立派なClassにコメントを付けるのは無理かな!? 一個人の好みとして「これは...」と思ったのは、クラス初期化プロシージャでの引数。 .., ByVal NewmFormObject As Object,...他の部分で、Label, CommandButtonなどMSFormsのコントロールを使っていますから、 VBA前提での使用だと考えると、Object型よりは、MSForms.Userform型が好みかな...ぐらいです。 |
|
|
|
投稿日時: 12/03/03 20:08:40
投稿者: みそじのおじさん
|
|---|---|
|
またまたお詫びです^^;
clsContainersとclsMoveItemsを一度エクスポートします。エクスポート後に2つのクラスを 解放して下さい。 エクスポートしたファイルをメモ帳などで開きます。 clsContainersは Public Property Get Item(ByVal Index As Variant) As clsContainer Attribute Item.VB_UserMemId = 0 'この行を追加し上書き保存します。 clsMoveItemsも Property Get Item(ByVal Index As Variant) As clsMoveItem Attribute Item.VB_UserMemId = 0 'この行を追加し上書き保存します。 メモ帳での作業が終わったら2つのクラスをVBEにインポートします。 これで2つのクラスの規定のプロパティがItemになります。 規定のプロパティはオブジェクトブラウザで水色の丸印が付き確認できます。 ▽Abyssさん お待ちしておりました(笑) 斬新なんて言われますと大変恐縮です^^ As Objectの件ですが、クラスに限らずUserForm型で受けるとたまに引っかかる 事があるので「えーい、とりあえずObject型で受けておけ!」なんて軽い考えでやって おりました^^(コンパイルを通す為にあえてObjectで受ける事が多いのですが、今回は UserForm型でよさそうですね!) 引き続きご参加をよろしくお願い致します。 # UIだけの動きを見るとそれなりですが、見る人が見れば裏側は改善の余地がいくらでも # 出てきそうですね^^; 月さん。まだ内容の精査中です。申し訳ありません。 |
|
|
|
投稿日時: 12/03/03 20:20:27
投稿者: 月
|
|---|---|
みそじのおじさん さんの引用: いえいえ、時間に余裕がある時で結構ですよ。 私も質問をして回答をもらうと早くやらねばと焦るので、お気持ちはわかります。 私もみそじのおじさんのコードをいじりたいと思っているのですが、昨日の夜から風邪でダウンしています。 みそじのおじさんも体調がよくない上にお忙しいんですよね。 なので、本当に焦らなくて結構ですよ。 |
|
|
|
投稿日時: 12/03/04 05:35:00
投稿者: yayadon
|
|---|---|
|
コードをざっと見てみました。
Private Declare Sub MoveMemory Lib "Kernel32" Alias "RtlMoveMemory" _
(Destination As Any, Source As Any, ByVal Length As Long)
''-----
'' 引数 vptr は,ObjPtr関数で取得して保持しておいたLong値
Private Function GetTarget(ByVal vptr As Long) As Object ' or 固有オブジェクト型
Dim tmp As Object ' or 固有オブジェクト型
MoveMemory tmp, vptr, 4& ' 値を入れるだけなので AddRef されない
Set GetTarget = tmp ' この時に AddRef されます。意味的には強参照化。
MoveMemory tmp, 0&, 4& ' 自動的に Release されないように 0 にしておく
End Function
のような感じの関数です。 成功不成功を返す感じにするのならば
'' target は,出力パラメータ
Private Function TryGetTarget(ByVal vptr As Long, target As Object) As Boolean
On Error Goto ErrHandler
'TryGetTarget = False
Dim tmp As Object
MoveMemory tmp, vptr, 4&
Set target = tmp
MoveMemory tmp, 0&, 4&
TryGetTarget = True
ExitHandler:
Exit Function
ErrHandler:
'MsgBox ...
'Resume ExitHandler
End Function
のような感じでしょうか。 でも,オートメーション エラーをトラップできないハズなので, そうなると,このメソッドは意味がないと思うので, 書いてみましたが,無しの方向ということで。 この弱参照部分だけをクラスにしてみると... .NET だと,弱参照 のクラスがあって,ジェネリック クラスになってて, VBA はジェネリックのたぐいの仕組みが使えないのでやるのならば, 固有オブジェクト型ではなく,Object型 でやる感じでしょうか。 クラス CWeakReference Private Declare Sub MoveMemory Lib "Kernel32" Alias "RtlMoveMemory" _
(Destination As Any, Source As Any, ByVal Length As Long)
Private m_vptr As Long
Public Sub SetTarget(target As Object)
m_vptr = ObjPtr(target)
End Sub
Public Function GetTarget() As Object
Dim tmp As Object
MoveMemory tmp, m_vptr, 4&
Set GetTarget = tmp
MoveMemory tmp, 0&, 4&
End Function
直書きしているので,typo 等おかしなところがあるかもしれません。 また, SetTarget/GetTarget のところは, (Value 等の) プロパティ にするのが良いのかもしれませんが, そのままメソッドでということにしておきます。 書いておいてなんなんですが, 他のサイトでも見かけたことがあるので,それほど特殊ではないと思いますが, 循環参照をなくすために,VBA でここまでやるべきかは,よくわかりません。 最終的に Term メソッドを無くせないのならば, わざわざリスクをおかす必要はなく,やらないのが無難な気がします。 # このネタを引っ張ったのは, # クラス CWeakReference を書いてみたかっただけかもしれません(爆)。 ----- コードを追うと mFormObject は, 作成時に渡されてやってきた UserForm の参照のようです。 VBA では,たぶん,習わない項目なんですが, COM には,クラス自体の継承がない代わりに, 2つのクラスの機能をひとつにする仕組みがあって, この場合, MSForms.Label が,UserForm の Controls コレクションに追加されたときに Control オブジェクトにアグリゲートされる(Contained and Aggregated)※関係で, Control の Parent プロパティを呼び出すことができ, コードの流れを見た感じでは,UserForm の参照を取得することが可能です。 ※ IUnknown は共通,つまり,利用する側からはひとつのオブジェクトに見える。 IDispatch は Control に無い名前のメンバは Label に委譲 (Containment/Delegation) Control と同名のメンバは,Control の Objectプロパティ経由でアクセス Label のデフォルトのインターフェースは,そのまま公開 (Aggregation) なので, コードで,最初に Me を UserForm から渡していますが, 必ずしも渡す必要はないです。 これも無くせば, 循環参照はなくなると思います。 (でも,なくならないかもしれません。) |
|
|
|
投稿日時: 12/03/04 07:55:47
投稿者: kumatti
|
|---|---|
Abyss さんの引用: ありがとうございます。そんな経緯があったのですね。 > タッチ関連のAPI 一応、登録用の子ウィンドウを別途、用意して(CreateWindow等で)そちらをサブクラス化する事になります。 (高速化の為に、特殊なテクニックは別に要りません) |
|
|
|
投稿日時: 12/03/04 09:08:44
投稿者: みそじのおじさん
|
|---|---|
|
みなさん。おはようございます。
|
|
|
|
投稿日時: 12/03/04 20:41:58
投稿者: YU-TANG
|
|---|---|
|
こんばんは、YU-TANG です。
yayadon さんの引用: 自分は循環参照の回避として、弱い参照 (weak reference) の方をよく使いますね。自分の作るカスタム コレクション クラスはほとんどそれです。 ただ TearDown 派とは一長一短なので、あとは好みの問題ですから、どっちでもいいと思っています。 というか、むしろ 64bit Office への移行が現実味を帯びてきた最近は、ポインタ操作しない TearDown の方が正解なのかなという気が強くしてきました。ポインタ周りをいちいち改修して回りたくないですし。 なお TearDown て何? という方がいらっしゃいましたら、一般的にオブジェクト自身に後始末を要請するためのメソッド名とご理解ください。VB6 では (ヘルプでそう提唱していた関係で) この名前を付けるのが標準だったはず。現在でも、xUnit 系フレームワークでテストの後始末を行うのは TearDown というのが相場です。別に名前をどう付けても役割が一緒なら動作に支障は無いですが、ソースコードを読んだときにメソッド名を一瞥しただけで役割が分かるので、メンテナンスの都合上はメソッド名を揃えたほうが望ましいです。 みそじのおじさん さんの引用: 自分は、動いているならそれでいい派なので、特にどっちがいいとか意見は無いのですが。 仮に UserForm に直接伝える方向で改修するとしたら、上記の問題は結局 clsContainer のイベントを親オブジェクトにどう伝えるのか、ということと一緒ですよね。で、clsContainers に対しては Alert メソッドを呼んで伝えるという方法をすでに取っているようですから、対象が UserForm に変わるだけで、同じ手法で良いと思います。というか、配列は WithEvents できませんから、他にやりようが無いです。 問題になるとしたら、Alert を外部公開していいのか、という点でしょう。これは現在の clsContainers も同じ問題を抱えています。 このメソッドは本来、子要素 clsContainer からしか呼んではいけないわけですが、実際は Public ですからどこからでも呼ぶことが可能です。間違ってか故意にか、とにかく呼ばれたら VBA の仕組み的にそれを防ぐ仕掛けが無いのでオシマイですし、開発者が呼ばないまでも、親オブジェクト (UserForm だろうが clsContainers だろうが) のメンバに出てきますから、インテリセンスの候補リスト上では確実にノイズになります。 これは対策が、楽でいい加減なのと、面倒で厳密なのと二種類あります。 楽でいい加減な方は、Friend スコープにするという。自分はこっちの方をよく使います。同一プロジェクト内ではほとんど意味が無いのですが(何も防いでくれない)、Friend 宣言は珍しいので、使う側が「アレッ?」と思います (思いますよね? いや、思うはずです! 話が進まないので、思うということにしてください…)。これでコメントでも書いておけば、注意喚起としては十分なケースも多いと思います。 面倒で厳密な方は、コールバック インターフェイスを Implements して、専用メンバを隔離してしまう方法です。クラス名は ICallback○○ にすることが多いかな。自分は、ここぞというときは (仕方なく) これを使います。これで、間違って呼び出すということはほぼ不可能になりますし、ノイズにもなりません。Implements はよく多態性の側面ばかり言及されがちですが、用法としてメンバの隔離を挙げないと片手落ちな気が(自分は)しています。 これは VB6 ではごく一般的な用法です(自分には、そのように見受けられました)。たとえば Karl E. Peterson のサンプルとか漁っていると、よく見かけます。 |
|
|
|
投稿日時: 12/03/05 17:56:19
投稿者: めんたん
|
|---|---|
|
クラスって使ったことなかったので、どんなものかと試してみたいと思い、
|
|
|
|
投稿日時: 12/03/05 18:19:11
投稿者: ろひ
|
|---|---|
めんたん さんの引用: As以降の定義に基づいて、クラスモジュール名を付けてないから、だと思います。 (※クラスモジュールを呼び出せてない状態。) プロパティウィンドウを表示させて、「(オブジェクト名)」の項目に、正確にクラスモジュール名を入力してください。 |
|
|
|
投稿日時: 12/03/05 21:21:35
投稿者: どんきち
|
|---|---|
|
話の流れをぶったぎって申し訳ありません。
'==================== こ こ か ら =============================>>>>>>>>>>
Option Explicit
'コンストラクタ実行済みフラグ
Private mblnInit As Boolean
'モジュール変数
Private mstrName As String
Private mlngTotal As Long
Private mlngNums As Long
'***** コンストラクタの実体 *****
'外部から読み書きできないモジュール変数も変更できるようにするために
'コンストラクタの実体はクラスに定義する。
Public Sub Init2(pstrName As String)
'コンストラクタ実行済みならメッセージ表示
If mblnInit = True Then
MsgBox "コンストラクタは2回以上実行できません"
Exit Sub
End If
'コンストラクタ実行済みフラグを設定
mblnInit = True
'モジュール変数の設定
mstrName = pstrName
mlngNums = 0
mlngTotal = 0
End Sub
Public Sub Clear()
'Publicメソッドの先頭でコンストラクタ実行済みかチェック
'コンストラクタ実行済みならメッセージ表示
If mblnInit = False Then
MsgBox "コンストラクタが実行されていません。"
Exit Sub
End If
mlngTotal = 0
mlngNums = 0
End Sub
Public Sub Add(plngVal As Long)
If mblnInit = False Then
MsgBox "コンストラクタが実行されていません。"
Exit Sub
End If
mlngTotal = mlngTotal + plngVal
mlngNums = mlngNums + 1
End Sub
Public Sub MsgAvg()
Dim strR As String
Dim lngAvg As String
If mblnInit = False Then
MsgBox "コンストラクタが実行されていません。"
Exit Sub
End If
lngAvg = mlngTotal / mlngNums
MsgBox mstrName & ":平均=" & CStr(lngAvg)
End Sub
Public Sub MsgTotal()
If mblnInit = False Then
MsgBox "コンストラクタが実行されていません。"
Exit Sub
End If
MsgBox mstrName & ":合計=" & CStr(mlngTotal)
End Sub
'<<<<<<<<<<========== こ こ ま で =======================================
●クラスモジュールとペアになる標準モジュール:modHoge '==================== こ こ か ら =============================>>>>>>>>>>
Option Explicit
'***** コンストラクタ *****
'1文で実行できるようにするためにコンストラクタの呼び出し口は
'標準モジュールに定義する。
Public Function Init2(pstrName As String) As clsHoge
Dim objRtn As clsHoge
Set objRtn = New clsHoge
Call objRtn.Init2(pstrName)
Set Init2 = objRtn
End Function
'<<<<<<<<<<========== こ こ ま で =======================================
'コンストラクタの確認用の標準モジュール:modMain1 '==================== こ こ か ら =============================>>>>>>>>>>
Option Explicit
'テスト11
Public Sub Test11()
Dim objA As clsHoge
'コンストラクタの実行
Set objA = modHoge.Init2("ほげ太郎")
Call objA.Add(1)
Call objA.Add(2)
Call objA.Add(3)
Call objA.MsgTotal
End Sub
'テスト12
Public Sub Test12()
Dim objA As clsHoge
'コンストラクタを2回実行
Set objA = New clsHoge
Set objA = modHoge.Init2("ほげ太郎")
Call objA.Init2("ほげ")
End Sub
'テスト13
Public Sub Test13()
Dim objA As clsHoge
'コンストラクタを実行せずにNew
Set objA = New clsHoge
Call objA.Add(1)
End Sub
'<<<<<<<<<<========== こ こ ま で =======================================
|
|
|
|
投稿日時: 12/03/05 21:49:25
投稿者: どんきち
|
|---|---|
|
サンプルコードの2つ目として、VBAでクラスのインスタンスを生成するリフレクション機能を実現するコードを掲載します。
'==================== こ こ か ら =============================>>>>>>>>>>
Option Explicit
'モジュール変数
Private mstrName As String
'引数なしのコンストラクタ
Public Sub Init1()
'モジュール変数の設定
mstrName = "名無しの権兵衛"
End Sub
'引数ありのコンストラクタ
Public Sub Init2(pstrName As String)
mstrName = pstrName
End Sub
'メソッド
Public Sub SubA()
MsgBox "clsPiyoA(" & mstrName & ").SubA"
End Sub
'<<<<<<<<<<========== こ こ ま で =======================================
●clsPiyoAとペアになる標準モジュール:modPiyoA '==================== こ こ か ら =============================>>>>>>>>>>
Option Explicit
'コンストラクタ
'1文で実行できるようにするためにコンストラクタの呼び出し口は
'標準モジュールに定義する。
Public Function Init1() As clsPiyoA
Dim objRtn As clsPiyoA
Set objRtn = New clsPiyoA
Call objRtn.Init1
Set Init1 = objRtn
End Function
'コンストラクタ
'1文で実行できるようにするためにコンストラクタの呼び出し口は
'標準モジュールに定義する。
Public Function Init2(pstrName As String) As clsPiyoA
Dim objRtn As clsPiyoA
Set objRtn = New clsPiyoA
Call objRtn.Init2(pstrName)
Set Init2 = objRtn
End Function
'<<<<<<<<<<========== こ こ ま で =======================================
●クラスモジュール:clsPiyoB '==================== こ こ か ら =============================>>>>>>>>>>
Option Explicit
'モジュール変数
Private mstrName As String
'引数なしのコンストラクタ
Public Sub Init1()
'モジュール変数の設定
mstrName = "名無しの権兵衛"
End Sub
'引数ありのコンストラクタ
Public Sub Init2(pstrName As String)
mstrName = pstrName
End Sub
'メソッド
Public Sub SubB()
MsgBox "clsPiyoB(" & mstrName & ").SubB"
End Sub
'<<<<<<<<<<========== こ こ ま で =======================================
●clsPiyoBとペアになる標準モジュール:modPiyoB '==================== こ こ か ら =============================>>>>>>>>>>
Option Explicit
'コンストラクタ
'1文で実行できるようにするためにコンストラクタの呼び出し口は
'標準モジュールに定義する。
Public Function Init1() As clsPiyoB
Dim objRtn As clsPiyoB
Set objRtn = New clsPiyoB
Call objRtn.Init1
Set Init1 = objRtn
End Function
'コンストラクタ
'1文で実行できるようにするためにコンストラクタの呼び出し口は
'標準モジュールに定義する。
Public Function Init2(pstrName As String) As clsPiyoB
Dim objRtn As clsPiyoB
Set objRtn = New clsPiyoB
Call objRtn.Init2(pstrName)
Set Init2 = objRtn
End Function
'<<<<<<<<<<========== こ こ ま で =======================================
●リフレクション用標準モジュール:monNewInstance '==================== こ こ か ら =============================>>>>>>>>>>
Option Explicit
'***** 引数なしのコンストラクタ *****
'インスタンスの生成対象のクラスモジュールと
'そのクラス用標準モジュールとが同じ命名規約で存在する。
'コンストラクタのメソッド名がInit1
Public Function Init1(pstrClsallName As String) As Object
Dim objRtn As Object
Dim strProcName As String
strProcName = "mod" & Mid(pstrClsallName, 4) & ".Init1"
Set objRtn = Application.Run(strProcName)
Set Init1 = objRtn
End Function
'***** 引数ありのコンストラクタ *****
'インスタンスの生成対象のクラスモジュールと
'そのクラス用標準モジュールとが同じ命名規約で存在する。
'コンストラクタのメソッド名はInit2で、
'String型の引数が1つ
Public Function Init2(pstrClsallName As String, pstrName As String) As Object
Dim objRtn As Object
Dim strProcName As String
strProcName = "mod" & Mid(pstrClsallName, 4) & ".Init2"
Set objRtn = Application.Run(strProcName, pstrName)
Set Init2 = objRtn
End Function
'<<<<<<<<<<========== こ こ ま で =======================================
●リフレクションの確認用標準モジュール:modMain2 '==================== こ こ か ら =============================>>>>>>>>>>
Option Explicit
'テスト21
Public Sub Test21()
Dim objA As clsPiyoA
Dim objB As clsPiyoB
Set objA = monNewInstance.Init1("clsPiyoA")
Call objA.SubA
Set objB = monNewInstance.Init1("clsPiyoB")
Call objB.SubB
End Sub
'テスト22
Public Sub Test22()
Dim objA As clsPiyoA
Dim objB As clsPiyoB
Set objA = monNewInstance.Init2("clsPiyoA", "あああ")
Call objA.SubA
Set objB = monNewInstance.Init2("clsPiyoB", "いいい")
Call objB.SubB
End Sub
'<<<<<<<<<<========== こ こ ま で =======================================
|
|
|
|
投稿日時: 12/03/05 21:55:57
投稿者: みそじのおじさん
|
|---|---|
|
みなさん、こんばんは。
引用: ごもっともです^^;英語が目茶目茶苦手です。変数名、メソッド名など命名するのも一苦労です。 引用: そうですね。こう見ますと、ずいぶん冗長的なコードですね^^ 「メソッドチェーン」という呼ばれ方をしているのは知りませんでしたが、clsMoveItemのInitが これにあたるんですね。 mMoveItems.Add Item, Me のAddがclsMoveItemを戻り値にすればよかったのですね。 「なんでBooleanにしたのかな?」と考えましたら、たぶん追加出来た、出来ないを戻り値に しなきゃとまっさきに思ってしまったのだと思います。(しかし戻り値を受ける様にはなって いません、、私いい加減ですね。反省です。) 引用: このスレッドを立てました理由の一つに、クラス内部のコードのお話がたまにあったとしても こういったクラスの組み立て方のお話がほぼ皆無だという気持ちがありました。 とても参考になります。組み立て直してみようと思っていますが、TermDown Or 弱参照の どっちにするかを見極めてから取り掛かりたいと思います。 引用: 「UserFromにプロパティを作成する」といった手法はよく利用しますが、 ごめんなさい。これに関しては何回考えてもわかりませんでした^^; 引き続きお付き合い下さると幸いです。 ▼YU-TANGさん 本当にためになるお話ありがとうございます。 「弱参照」という言葉を知っても、今度はAPI回りの改修の問題ですか、、んー。 64bitの環境はまだ一度も使った事がないのですが、(LongがLongLongでしたっけ?) その辺りまで考えていないとダメなんですね^^(クラスとは関係ありませんが、工作機械を RS232CでPCと繋ぎ、APIでポートオープン、通信設定、データ送受信なんてほぼAPIオンリー のソフトをVBAで作って使用していますが、まずまともに動かないんでしょうね^^;) 後半の「ノイズ」のお話は、何かコード上から私の「モヤッ」とする部分を汲み取って頂いた かのようなお話でした。 「子クラスには公開したいが、他からは呼ばれたくない!」 「このクラスをみなさんに公開したら、間違いなく呼んではいけないメソッドが呼ばれてしまうだろう」 「はてまた、数ヵ月後の自分ですらも同じ事をしてしまうであろう。」 自分で行き着いたのがクラスを外部プロジェクトにして「Friend」スコープを使用するでした。 YU-TANGさんが言われている「対策がらくでいい加減な方」で、スレッドの一番上に書きました、 ワークシートをRecordset風に扱うクラスはこの手法でそれを実現いたしました。以前Abyssさんが Friendを使って回答していたのですが「これは何を警戒してのFriendなんだ?」と当時はコード から意図を汲み取れませんでしたが、Friendを研究しやっとたどり着きました。 (Friendを使った回答を見たのはAbyssさんが最初で最後でした。) Implementsは自分から欲して使用したことはなく、練習の為に無理やり使ってきた感がそうとう あったのですが、YU-TANGさんのお話から少し光が見えた気がしました。 「多態性ばかりではない」何かようやっと自分から欲して初めてImplementsが使える様な気が してきました。ありがとうございます。(ただいまサンプルファイルで確認中です。) それでは、皆様引き続き「ワイワイガヤガヤ」でお願いしたします。(笑) |
|
|
|
投稿日時: 12/03/06 09:06:00
投稿者: めんたん
|
|---|---|
|
>プロパティウィンドウを表示させて、「(オブジェクト名)」の項目に、正確にクラスモジュール名を入力してください。
|
|
|
|
投稿日時: 12/03/06 11:01:50
投稿者: 月
|
|---|---|
みそじのおじさん さんの引用: そうですね。ただ、メソッドチェーンをしてはいないですね。メソッドチェーンができるようになっているだけです。 みそじのおじさん さんの引用: 今、みそじのおじさんのコードをいじりまくっています。元々、自分で言ったことは自分でやるつもりだったので、みそじのおじさんに直していただかなくても結構ですよ。 私の作業はまだ時間が掛かりそうです。できたらお見せします。好きでやっていることですのでお気になさらず。 みそじのおじさん さんの引用: そうすればMeを渡さなくて済むのでは、のことですかね? すみません、これは私の勘違いでした。 |
|
|
|
投稿日時: 12/03/06 12:39:36
投稿者: Abyss
|
|---|---|
|
Friendスコープのメソッドは、Privateメソッド、Publicメソッド一部の
Public Sub PublicMethod()
Debug.Print "Public Method"
End Sub
Friend Sub FrendMethod()
Debug.Print "Friend Method"
End Sub----- 呼び出しテスト(別モジュールから)
Sub Test()
Dim o As Object
Set o = New Class1
o.PublicMethod
o.FriendMethod '←実行時Error(DISP_E_MEMBERNOTFOUND)
End Sub
このように、Friend FunctionはVTableのメンバーとして乗らないので IDispatch呼出時(実行時バインディング)には失敗します。 Friend メソッドはVTable経由でなく、ダイレクトに呼び出されるので VTable経由より高速のはずです。私が主に Friend Functionを使う場面は、 Windowにサブクラスを掛ける時、標準モジュールからClassモジュールに 置いたWndProcをCallする際、Friend FunctionがVTable経由より速度面で 有利だからです。 # あと、偶然ですが、みそじのおじさん さんのサンプルにバグを見つけたような。。。 と言うのは、ラベルをドラッグし移動する際、UserformのClient領域の外に 持っていくと、あるタイミングでラベルが消えますね。そのとき、マウスボタンを 離すとラベルが画面から消える。おそらく、Client領域の外にそのラベルコントロールが 置かれている。 # もう一つ、RECT構造体の各メンバーは「Long型」に宣言されているけど、 実際使用時には、「Single型」が対象になっている。 |
|
|
|
投稿日時: 12/03/06 13:16:13
投稿者: Abyss
|
|---|---|
|
失礼!!
引用:既にチェック済みでしたね。。 |
|
|
|
投稿日時: 12/03/06 20:24:19
投稿者: みそじのおじさん
|
|---|---|
|
こんばんは。あっという間にレスが48件です。皆様ありがとうございます。
Option Explicit
Sub Sample()
Dim p As Class1
Set p = New Class1
p.Value = -2
'このpから見えるのはValueプロパティとTestMethodメソッドのみ
'Alertメソッドは見えない
p.TermDown
Set p = Nothing
End Sub
クラス名 ICallBackClass1 Option Explicit Public Sub Alert(ByVal AlertValue As Long) End Sub クラス名 Class1
Option Explicit
Implements ICallBackClass1
Public Event Alert(ByVal AlertValue As Long)
Private mChild As Class2
Private Sub Class_Initialize()
Dim Child As Class2
Set Child = New Class2
Set mChild = Child.Init(Me)
End Sub
Private Sub Class_Terminate()
Set mChild = Nothing
Debug.Print "Class1 Term!"
End Sub
Private Sub ICallBackClass1_Alert(ByVal Value As Long)
'RaiseEvent Alert(Value)
'今回は標準モジュールでテストしたのでRaiseEventは使用しません
MsgBox "Valueが0以下になりました"
End Sub
Public Property Get Value() As Long
Value = mChild.Value
End Property
Public Property Let Value(ByVal NewValue As Long)
mChild.Value = NewValue
End Property
Public Sub TestMethod()
MsgBox "TestMethod"
End Sub
Public Sub TermDown()
mChild.TermDown
Set mChild = Nothing
End Sub
クラス名 Class2
Option Explicit
Private mParent As ICallBackClass1 '※1
Private mValue As Long
Public Function Init(ByVal NewParent As Class1) As Class2
Set mParent = NewParent
Set Init = Me
End Function
Public Property Get Value() As Long
Value = mValue
End Property
Public Property Let Value(ByVal NewValue As Long)
mValue = NewValue
If mValue < 0 Then
mParent.Alert mValue
End If
End Property
Public Sub TermDown()
Set mParent = Nothing
End Sub
Private Sub Class_Terminate()
Debug.Print "Class2 Term!"
End Sub
Class2の※1の宣言部ですが、mParentをIcallBackClass1で宣言しましたがこの 部分に自信がもてません。As Class1で宣言するとClass1のICallBackClass1_Alertメソッド が見えませんのでこうするしかなかったのですが、これで正解でしょうか? |
|
|
|
投稿日時: 12/03/06 21:31:51
投稿者: YU-TANG
|
|---|---|
|
こんばんは、YU-TANG です。
みそじのおじさん さんの引用: どちらでも良いです。 絶対的な正解/不正解があるものではなく、用途に応じて使い分けるものですので。 主に、以下のような使い分けをします。 1. 子クラスにとって、親クラスは ICallBack クラスのメンバだけ見えていれば良い。 ― かつ ― 親クラスにとって、ICallBack クラスを Implement することが必須。 → Private mParent As ICallBackClass1 で良い。 2. 子クラスは、親クラスの ICallBack クラス以外のメンバも呼ぶかもしれない。 ― または ― 親クラスは、ICallBack クラスを Implement しないかもしれない(コールバック不要なら Implement しないことがあり得る)。 → Private mParent As Class1 で宣言して、コールバック箇所は以下のように実装する。 Public Property Let Value(ByVal NewValue As Long)
mValue = NewValue
If mValue < 0 Then
If TypeOf mParent Is ICallBackClass1 Then ' コールバック インターフェイスを実装していたら
CallBackClass1From(mParent).Alert mValue ' コールバックしてあげる
End If
End If
End Property
Private Function CallBackClass1From(o As Class1) As ICallBackClass1
Set CallBackClass1From = o
End Function
# ベタ打ちなので、ミスっていたら適宜修正お願いします。 たぶん、クラスをあまり使わない人の目から見ると、「何やってんの? ゴチャゴチャやらなくても、呼んでダメなら呼ばなきゃいいだけじゃん」と思われるかもしれないのですが。 人間、いつか必ずミスします (特に自分は、人並み以上にミスするみたいです orz)。だから、長く付き合うシステムであればあるほど、呼んでダメなものは VBA 自体にガードしてもらって、そもそも呼べなくしてもらう、仮に呼んだらコンパイル時にエラーにしてくれるという絶対的な安心感が、すごく重要になってきます。 みそじのおじさん さんの引用: Public Sub TermDown() 誤読を誘う書き方をしていたら申し訳ありません。TearDown が正しいです。 直訳すると「涙落」みたいな感じでしょうか。 なんでこう呼ぶかは知らないです。自分が知ったときはすでに、「そう呼ぶものと決まってんだよ、言わせんな恥ずかしい」みたいな雰囲気でした。 ご存知の方がいらっしゃいましたら、教えてください。 |
|
|
|
投稿日時: 12/03/06 21:57:40
投稿者: Abyss
|
|---|---|
|
> 直訳すると「涙落」みたいな感じでしょうか。
|
|
|
|
投稿日時: 12/03/06 22:23:30
投稿者: YU-TANG
|
|---|---|
|
いやいや、大真面目ですw
Abyss さんの引用: あー、そうなんですか。 それなら何となく、処理のイメージと一致する気がしますね。 ありがとうございます。 |
|
|
|
投稿日時: 12/03/07 00:04:57
投稿者: みそじのおじさん
|
|---|---|
|
▼YU-TANGさん
|
|
|
|
投稿日時: 12/03/07 03:40:39
投稿者: yayadon
|
|---|---|
Abyss さんの引用: 調べてみると, # テストの仕方があっているという前提ですが Private メソッドは,仮想関数テーブル経由で呼び出されているようです。 また, Friendメソッドのスロット自体は仮想関数テーブル上にあるようです。 例えば,以下のような CMethodTest クラス Option Explicit
Private Sub PrivateMethod()
Debug.Print , "PrivateMethod was called."
End Sub
Friend Sub FriendMethod()
Debug.Print , "FriendMethod was called."
End Sub
Public Sub PublicMethod()
Debug.Print , "PublicMethod was called."
Call PrivateMethod
End Sub
クラスがあったとします。 そして,クラスが VBA でやってくためには, VBA は以下の3つのインターフェースを作成する必要があるでしょう。 ・IUnknown インターフェース ・IDispatch インターフェース ・カスタム インターフェース それぞれ実装する場合は, IUnknown インターフェース ・QueryInterface メソッド ・AddRef メソッド ・Release メソッド IDispatch インターフェース (IUnknown を継承) ・QueryInterface メソッド ・AddRef メソッド ・Release メソッド ・GetTypeInfoCount メソッド ・GetTypeInfo メソッド ・GetIDsOfNames メソッド ・Invoke メソッド カスタム インターフェース (IUnknown を継承) ・QueryInterface メソッド ・AddRef メソッド ・Release メソッド ・PublicMethod1 メソッド ・PublicMethod2 メソッド : のような形になります。 で,調べてみると, IDispatch のポインタの位置がカスタム インターフェースと同じ位置なので, IDispatch インターフェースは,カスタム インターフェースと統合された デュアル インターフェース として実装されているようです。 ということで,以下の2つになります。 ・IUnknown インターフェース ・デュアル インターフェース で,以下の実験で確認してみたところ... VBA のデュアル インターフェースの実装において 仮想テーブルのスロット順は, Public のメソッドが Private や Friend より先のスロット位置に移動して, ・QueryInterface メソッド ・AddRef メソッド ・Release メソッド ・GetTypeInfoCount メソッド ・GetTypeInfo メソッド ・GetIDsOfNames メソッド ・Invoke メソッド ・PublicMethod メソッド <--- ここに移動 ・PrivateMethod メソッド <--- 以下,順に後ろに移動のもよう ・FriendMethod メソッド <--- 存在しているもよう のような感じになっています。 以前,Invoke の時に, kumatti さんが書かれてた例のコードを修正して, 仮想テーブルのスロットを通るか?を確認してみました。 # ROM の方もコードを読むだけでわかるように # メソッド名等や用意する変数は # PrivateMethod と FriendMethod に一対一になるように冗長なものに変えてあります。 # また,関連コードはふだん使っている形に変えてあります。 # また,Private/FriendMethod に引数が無いので # DispCallFunc で要らないものは省略してあります。 上記の CMethodTest クラス は,クラスモジュールに作成します。 下記は,標準モジュールに作成します。 名前は ModuleHook としておきます。 Option Explicit
Private Declare Function IsBadWritePtr Lib "kernel32" _
(ByVal lp As Long, ByVal ucb As Long) As Long
Private Declare Function VirtualProtect Lib "kernel32" _
(ByVal lpAddress As Long, ByVal dwSize As Long, _
ByVal flNewProtect As Long, lpflOldProtect As Long) As Long
Private Const PAGE_EXECUTE_READWRITE = &H40
Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" _
(Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Function DispCallFunc Lib "oleaut32" _
(ByVal pvInstance As Long, _
ByVal oVft As Long, _
ByVal cc As Long, _
ByVal vtReturn As Integer, _
ByVal cActuals As Long, _
prgvt As Integer, _
prgpvarg As Long, _
pvargResult As Variant) As Long
Private Const CC_STDCALL = 4
Private Const VT_EMPTY = 0 ' 引数無し用
'' PrivateMethod 用
Private m_pPrivateMethodHookProc As Long ' フックプロシージャのアドレス値
Private m_PrevPrivateMethod As Long ' 以前の関数ポインタ値
Private m_pSlotForPrivateMethod As Long ' スロットのアドレス値
'' FriendMethod 用
Private m_pFriendMethodHookProc As Long ' フックプロシージャのアドレス値
Private m_PrevFriendMethod As Long ' 以前の関数ポインタ値
Private m_pSlotForFriendMethod As Long ' スロットのアドレス値
'' StartHookOnXXXXMethod 内で使用
Public CustomInterface As CMethodTest
'
'' ここから PrivateMethod 用
Private Function PrivateMethodHookProc(ByVal This As Object) As Long
On Error Resume Next
Debug.Print , "PrivateMethodHookProc was called."
Dim oVft As Long ' スロット位置
oVft = 32 ' 7 + 2 個目 4 * (9 - 1)
' 再帰呼び出し回避のため
Call EndHookOnPrivateMethod
Dim hr As Long
Dim vntResult As Variant
hr = DispCallFunc(ObjPtr(This), oVft, CC_STDCALL, _
VT_EMPTY, 0&, ByVal 0&, ByVal 0&, vntResult)
Call StartHookOnPrivateMethod
If hr < 0 Then
PrivateMethodHookProc = hr
Else
PrivateMethodHookProc = vntResult
End If
End Function
Sub StartHookOnPrivateMethod()
Debug.Print "---StartHookOnPrivateMethod---"
Dim pvptr As Long
Dim pVtbl As Long
If m_pSlotForPrivateMethod Then Exit Sub ' すでに開始している
pvptr = ObjPtr(CustomInterface)
Call MoveMemory(pVtbl, ByVal pvptr, 4&)
' スロット位置
pVtbl = pVtbl + 32 ' 4 * (9 - 1) : PrivateMethod メソッド
' 以前のポインタを退避
Call MoveMemory(m_PrevPrivateMethod, ByVal pVtbl, 4&)
m_pPrivateMethodHookProc = VBA.CLng(AddressOf PrivateMethodHookProc)
' 差し替え
If Not ForceCopyLong(pVtbl, m_pPrivateMethodHookProc) Then Exit Sub
m_pSlotForPrivateMethod = pVtbl
End Sub
Sub EndHookOnPrivateMethod()
Debug.Print "---EndHookOnPrivateMethod---"
Dim p As Long
If m_pSlotForPrivateMethod = 0 Then Exit Sub
Call MoveMemory(p, ByVal m_pSlotForPrivateMethod, 4&)
If p = m_pPrivateMethodHookProc Then
Call ForceCopyLong(m_pSlotForPrivateMethod, m_PrevPrivateMethod)
End If
m_pSlotForPrivateMethod = 0
End Sub
'' ここから FriendMethod 用
Private Function FriendMethodHookProc(ByVal This As Object) As Long
On Error Resume Next
Debug.Print , "FriendMethodHookProc was called."
Dim oVft As Long ' スロット位置
oVft = 36 ' 7 + 3 個目 4 * (10 - 1)
' 再帰呼び出し回避のため
Call EndHookOnFriendMethod
Dim hr As Long
Dim vntResult As Variant
hr = DispCallFunc(ObjPtr(This), oVft, CC_STDCALL, _
VT_EMPTY, 0&, ByVal 0&, ByVal 0&, vntResult)
Call StartHookOnFriendMethod
If hr < 0 Then
FriendMethodHookProc = hr
Else
FriendMethodHookProc = vntResult
End If
End Function
Sub StartHookOnFriendMethod()
Debug.Print "---StartHookOnFriendMethod---"
Dim pvptr As Long
Dim pVtbl As Long
If m_pSlotForFriendMethod Then Exit Sub ' すでに開始している
pvptr = ObjPtr(CustomInterface)
Call MoveMemory(pVtbl, ByVal pvptr, 4&)
' スロット位置
pVtbl = pVtbl + 36 ' 4 * (10 - 1) : FriendMethod メソッド
' 以前のポインタを退避
Call MoveMemory(m_PrevFriendMethod, ByVal pVtbl, 4&)
m_pFriendMethodHookProc = VBA.CLng(AddressOf FriendMethodHookProc)
' 差し替え
If Not ForceCopyLong(pVtbl, m_pFriendMethodHookProc) Then Exit Sub
m_pSlotForFriendMethod = pVtbl
End Sub
Sub EndHookOnFriendMethod()
Debug.Print "---EndHookOnFriendMethod---"
Dim p As Long
If m_pSlotForFriendMethod = 0 Then Exit Sub
Call MoveMemory(p, ByVal m_pSlotForFriendMethod, 4&)
If p = m_pFriendMethodHookProc Then
Call ForceCopyLong(m_pSlotForFriendMethod, m_PrevFriendMethod)
End If
m_pSlotForFriendMethod = 0
End Sub
'' メモリ上の指定されたアドレスに32ビット値を書き込み
Private Function ForceCopyLong(ByVal Address As Long, _
ByVal Value As Long) As Boolean
Dim lngOld As Long
If IsBadWritePtr(Address, 4&) Then
Debug.Print "IsBadWritePtr is True"
If VirtualProtect(Address, 4&, _
PAGE_EXECUTE_READWRITE, lngOld) = 0 Then
Exit Function
End If
Call MoveMemory(ByVal Address, Value, 4&)
VirtualProtect Address, 4&, lngOld, lngOld
Else
Call MoveMemory(ByVal Address, Value, 4&)
End If
ForceCopyLong = True
End Function
以下,テスト用メソッド Private Sub Test()
Dim pUnk As stdole.IUnknown
Dim pDisp As Object
Dim pCustom As CMethodTest
Debug.Print "開始"
' 各インターフェース ポインタの位置の確認。
Set pUnk = New CMethodTest
Debug.Print "pUnk : ", Hex(ObjPtr(pUnk))
Set pDisp = pUnk
Debug.Print "pDisp : ", Hex(ObjPtr(pDisp))
Set pCustom = pUnk
Debug.Print "pCustom: ", Hex(ObjPtr(pCustom))
Debug.Print
' 仮想関数テーブルのスロット位置の関数ポインタの確認。
Dim hr As Long
Dim rgvt As Integer
Dim rgpvarg As Long
Dim vntResult As Variant
Debug.Print "Call 8番目 --- 4 * 7 オフセット目"
hr = DispCallFunc(ObjPtr(pCustom), 28, CC_STDCALL, _
VT_EMPTY, 0&, ByVal 0&, ByVal 0&, vntResult)
Debug.Print "Call 9番目 --- 4 * 8 オフセット目"
hr = DispCallFunc(ObjPtr(pCustom), 32, CC_STDCALL, _
VT_EMPTY, 0&, ByVal 0&, ByVal 0&, vntResult)
Debug.Print "Call 10番目 --- 4 * 9 オフセット目"
hr = DispCallFunc(ObjPtr(pCustom), 36, CC_STDCALL, _
VT_EMPTY, 0&, ByVal 0&, ByVal 0&, vntResult)
Debug.Print
' スロットをフックして,そこを通過するか?を確認。
Set ModuleHook.CustomInterface = pCustom
' Public -> Private
Call ModuleHook.StartHookOnPrivateMethod
Debug.Print "Call pCustom.PublicMethod"
Call pCustom.PublicMethod
Call ModuleHook.EndHookOnPrivateMethod
Debug.Print
' Friend
Call ModuleHook.StartHookOnFriendMethod
Debug.Print "Call pCustom.FriendMethod"
Call pCustom.FriendMethod
Call ModuleHook.EndHookOnFriendMethod
Debug.Print
Call ModuleHook.StartHookOnFriendMethod
Debug.Print "DispCallFunc オフセット 36"
hr = DispCallFunc(ObjPtr(pCustom), 36, CC_STDCALL, _
VT_EMPTY, 0&, ByVal 0&, ByVal 0&, vntResult)
Call ModuleHook.EndHookOnFriendMethod
Set ModuleHook.CustomInterface = Nothing
Debug.Print "終了"
End Sub
結果 開始
pUnk : 4DD4424
pDisp : 4DD4408
pCustom: 4DD4408
Call 8番目 --- 4 * 7 オフセット目
PublicMethod was called.
PrivateMethod was called.
Call 9番目 --- 4 * 8 オフセット目
PrivateMethod was called.
Call 10番目 --- 4 * 9 オフセット目
FriendMethod was called.
---StartHookOnPrivateMethod---
Call pCustom.PublicMethod
PublicMethod was called.
PrivateMethodHookProc was called.
---EndHookOnPrivateMethod---
PrivateMethod was called.
---StartHookOnPrivateMethod---
---EndHookOnPrivateMethod---
---StartHookOnFriendMethod---
Call pCustom.FriendMethod
FriendMethod was called.
---EndHookOnFriendMethod---
---StartHookOnFriendMethod---
DispCallFunc オフセット 36
FriendMethodHookProc was called.
---EndHookOnFriendMethod---
FriendMethod was called.
---StartHookOnFriendMethod---
---EndHookOnFriendMethod---
終了
pDisp と pCustom のアドレス値が同じことから, pCustom は,デュアル インターフェースと想定。 ということは, PublicMethod は,8番目の位置(32bit OS でのオフセットだと 4 * (8 - 1) ) に存在すると想定。 PrivateMethod や FriendMethod は,その後ろにずれていると想定。 それらを前提にコードを書いています。 PrivateMethod は, ---StartHookOnPrivateMethod---
Call pCustom.PublicMethod
PublicMethod was called.
PrivateMethodHookProc was called. (A)
---EndHookOnPrivateMethod---
PrivateMethod was called. (B)
---StartHookOnPrivateMethod---
---EndHookOnPrivateMethod---
をみる限り, PrivateMethodHookProc を通過して(A)から PrivateMethod が呼ばれている(B)。 FriendMethod は, ---StartHookOnFriendMethod---
Call pCustom.FriendMethod
FriendMethod was called. (C)
---EndHookOnFriendMethod---
を見る限り, FriendMethod was called. は存在する(C)が,その前に, FriendMethodHookProc was Called. の行が無いので, FriendMethodHookProc を通過しないで呼ばれている。 スロット位置が違っているのか?と念のため確認した DispCallFunc オフセット 36 ---StartHookOnFriendMethod---
DispCallFunc オフセット 36
FriendMethodHookProc was called. (D)
---EndHookOnFriendMethod---
FriendMethod was called. (E)
---StartHookOnFriendMethod---
---EndHookOnFriendMethod---
は,FriendMethodHookProc を通過して(D)から FriendMethod が呼ばれている(E)。 スロット位置はあっているもよう。 # 使うときは自己責任でお願いします。 |
|
|
|
投稿日時: 12/03/07 04:42:58
投稿者: yayadon
|
|---|---|
|
補足:
|
|
|
|
投稿日時: 12/03/07 13:35:31
投稿者: Abyss
|
|---|---|
|
yayadonさん、ここまで分析するとは、流石ですね!
引用: メモリー構造はそうかも知れません。(yayadonさんの実験を信じて) でも、上記の例でしたら VTableとは、QueryInterfaceから PublicMethodメソッドまでの 範囲だと思います。その後に続くPrivateMethodメソッド以降の関数ポインターの並びは それぞれの言葉の仕様による物だと私は思います。 yayadonさんの実験に比べると相当手抜きですが、以下が私のテスト。 ----Class1 Public mPublicMember As Long Private mPrivateMember As Long Private Sub PrivateMethod1() ' Debug.Print "Private Method1" End Sub Public Sub PublicMethod1() ' Debug.Print "Public Method2" End Sub Friend Sub FriendMethod3() ' Debug.Print "Friend Method3" End Sub Public Function PublicFunction4() As String ' Debug.Print "Public Method4" End Function 参照設定で「TypeLib Information」を参照してもいいですが、 以下のテストでメンバーを表示。別モジュールから実行。 Sub Test1()
Dim m 'MemberInfo
With CreateObject("TLI.TLIApplication")
For Each m In .InterfaceInfoFromObject(New Class1).VTableInterface.Members
Debug.Print m.VTableOffset, m.Name
Next
End With
End Sub
ついでに、 引用: もテスト。 Sub Test2()
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.PublicMethod1
Next
Debug.Print Timer - t, "public"
t = Timer
For i = 1 To cIterate
c.FriendMethod3
Next
Debug.Print Timer - t, "friend"
End Sub
わずかですが、Friend Methodが高速なのが確認できます。
|
|
|
|
投稿日時: 12/03/07 21:39:48
投稿者: どんきち
|
|---|---|
|
WithEventsを使って複数のクラスの同じ形式のイベントを1つのイベントプロシージャで受け取る(1)
'============================================================
'クラスモジュール:clsFrom1
'============================================================
'イベント通知元クラス1
Option Explicit
'イベント
Public Event EvtA()
Public Event EvtB(p1 As Long)
Public Event EvtC(p1 As Long, p2 As Long)
Public Sub OnEvtA()
Debug.Print "clsFrom1.OnEvtA"
RaiseEvent EvtA
End Sub
Public Sub OnEvtB(p1 As Long)
Debug.Print "clsFrom1.OnEvtB"
RaiseEvent EvtB(p1)
End Sub
Public Sub OnEvtC(p1 As Long, p2 As Long)
Debug.Print "clsFrom1.OnEvtC"
RaiseEvent EvtC(p1, p2)
End Sub
'============================================================
'クラスモジュール:clsFrom2
'============================================================
'イベント通知元クラス2
Option Explicit
'イベント
Public Event EvtA()
Public Event EvtB(p1 As Long)
Public Event EvtC(p1 As Long, p2 As Long)
Public Sub OnEvtA()
Debug.Print "clsFrom2.OnEvtA"
RaiseEvent EvtA
End Sub
Public Sub OnEvtB(p1 As Long)
Debug.Print "clsFrom2.OnEvtB"
RaiseEvent EvtB(p1)
End Sub
Public Sub OnEvtC(p1 As Long, p2 As Long)
Debug.Print "clsFrom2.OnEvtC"
RaiseEvent EvtC(p1, p2)
End Sub
'============================================================
'クラスモジュール:clsFrom3
'============================================================
'イベント通知元クラス3
Option Explicit
'イベント
Public Event EvtA()
Public Event EvtB(p1 As Long)
Public Event EvtC(p1 As Long, p2 As Long)
Public Sub OnEvtA()
Debug.Print "clsFrom3.OnEvtA"
RaiseEvent EvtA
End Sub
Public Sub OnEvtB(p1 As Long)
Debug.Print "clsFrom3.OnEvtB"
RaiseEvent EvtB(p1)
End Sub
Public Sub OnEvtC(p1 As Long, p2 As Long)
Debug.Print "clsFrom3.OnEvtC"
RaiseEvent EvtC(p1, p2)
End Sub
'============================================================
'クラスモジュール:clsTo1
'============================================================
'イベント通知先クラス1
Option Explicit
Private WithEvents mobjFrom1 As clsFrom1
Private WithEvents mobjFrom2 As clsFrom2
Private WithEvents mobjFrom3 As clsFrom3
'初期処理
Public Sub Init1()
Set mobjFrom1 = New clsFrom1
Set mobjFrom2 = New clsFrom2
Set mobjFrom3 = New clsFrom3
End Sub
'イベントEvtAの発生処理の実行
Public Sub OnEvtA()
Debug.Print "clsTo1.onEvtA:>>>"
Debug.Print "clsTo1.onEvtA:001"
Call mobjFrom1.OnEvtA
Debug.Print
Debug.Print "clsTo1.onEvtA:002"
Call mobjFrom2.OnEvtA
Debug.Print
Debug.Print "clsTo1.onEvtA:003"
Call mobjFrom3.OnEvtA
Debug.Print "clsTo1.onEvtA:<<<"
End Sub
'イベントEvtBの発生処理の実行
Public Sub OnEvtB()
Debug.Print "clsTo1.OnEvtB:>>>"
Debug.Print "clsTo1.OnEvtB:001"
Call mobjFrom1.OnEvtB(1)
Debug.Print
Debug.Print "clsTo1.OnEvtB:002"
Call mobjFrom2.OnEvtB(2)
Debug.Print
Debug.Print "clsTo1.OnEvtB:003"
Call mobjFrom3.OnEvtB(3)
Debug.Print "clsTo1.OnEvtB:<<<"
End Sub
'イベントEvtCの発生処理の実行
Public Sub OnEvtC()
Debug.Print "clsTo1.OnEvtC:>>>"
Debug.Print "clsTo1.OnEvtC:001"
Call mobjFrom1.OnEvtC(1, 2)
Debug.Print
Debug.Print "clsTo1.OnEvtC:002"
Call mobjFrom2.OnEvtC(2, 3)
Debug.Print
Debug.Print "clsTo1.OnEvtC:003"
Call mobjFrom3.OnEvtC(3, 4)
Debug.Print "clsTo1.OnEvtC:<<<"
End Sub
'mobjFrom1のイベントプロシージャ
Private Sub mobjFrom1_EvtA()
Debug.Print "clsTo1.mobjFrom1_EvtA"
'イベントEvtAの共通処理を実行
Call doEvtA(mobjFrom1)
End Sub
Private Sub mobjFrom1_EvtB( _
p1 As Long)
Debug.Print "clsTo1.mobjFrom1_EvtB(" _
& Trim(p1) & ")"
'イベントEvtBの共通処理を実行
Call doEvtB(mobjFrom1, p1)
End Sub
Private Sub mobjFrom1_EvtC( _
p1 As Long, p2 As Long)
Debug.Print "clsTo1.mobjFrom2_EvtC(" _
& Trim(p1) & "," & Trim(p2) & ")"
'イベントEvtCの共通処理を実行
Call doEvtC(mobjFrom1, p1, p2)
End Sub
'mobjFrom2のイベントプロシージャ
Private Sub mobjFrom2_EvtA()
Debug.Print "clsTo1.mobjFrom2_EvtA"
'イベントEvtAの共通処理を実行
Call doEvtA(mobjFrom2)
End Sub
Private Sub mobjFrom2_EvtB( _
p1 As Long)
Debug.Print "clsTo1.mobjFrom2_EvtB(" _
& Trim(p1) & ")"
'イベントEvtBの共通処理を実行
Call doEvtB(mobjFrom2, p1)
End Sub
Private Sub mobjFrom2_EvtC( _
p1 As Long, p2 As Long)
Debug.Print "clsTo1.mobjFrom2_EvtC(" _
& Trim(p1) & "," & Trim(p2) & ")"
'イベントEvtCの共通処理を実行
Call doEvtC(mobjFrom2, p1, p2)
End Sub
'mobjFrom3のイベントプロシージャ
Private Sub mobjFrom3_EvtA()
Debug.Print "clsTo1.mobjFrom3_EvtA"
'イベントEvtAの共通処理を実行
Call doEvtA(mobjFrom3)
End Sub
Private Sub mobjFrom3_EvtB( _
p1 As Long)
Debug.Print "clsTo1.mobjFrom3_EvtB(" _
& Trim(p1) & ")"
'イベントEvtBの共通処理を実行
Call doEvtB(mobjFrom3, p1)
End Sub
Private Sub mobjFrom3_EvtC( _
p1 As Long, p2 As Long)
Debug.Print "clsTo1.mobjFrom3_EvtC(" _
& Trim(p1) & "," & Trim(p2) & ")"
'イベントEvtCの共通処理を実行
Call doEvtC(mobjFrom3, p1, p2)
End Sub
'イベントEvtAの共通処理
Private Sub doEvtA( _
pobjFrom As Object)
Debug.Print "clsTo1.doEvtA(" _
& TypeName(pobjFrom) & ")"
End Sub
'イベントEvtBの共通処理
Private Sub doEvtB( _
pobjFrom As Object, p1 As Long)
Debug.Print "clsTo1.doEvtB(" _
& TypeName(pobjFrom) _
& "," & Trim(p1) & ")"
End Sub
'イベントEvtCの共通処理
Private Sub doEvtC( _
pobjFrom As Object, p1 As Long, p2 As Long)
Debug.Print "clsTo1.doEvtC(" _
& TypeName(pobjFrom) _
& "," & Trim(p1) & "," & Trim(p2) & ")"
End Sub
'============================================================
'標準モジュール:modMain
'============================================================
Option Explicit
'テスト実行処理
Sub Test1()
Dim objTo1 As clsTo1
Set objTo1 = New clsTo1
Call objTo1.Init1
Debug.Print "MOD1.001"
Call objTo1.OnEvtA
Debug.Print
Debug.Print "MOD1.002"
Call objTo1.OnEvtB
Debug.Print
Debug.Print "MOD1.003"
Call objTo1.OnEvtC
End Sub
|
|
|
|
投稿日時: 12/03/07 21:44:55
投稿者: どんきち
|
|---|---|
|
WithEventsを使って複数のクラスの同じ形式のイベントを1つのイベントプロシージャで受け取る(2)
'============================================================
'クラスモジュール:clsEvt1
'============================================================
'イベント中継クラス1
Option Explicit
'イベント(通知元オブジェクトを識別する引数を追加)
Public Event EvtA( _
pobjFrom As Object)
Public Event EvtB( _
pobjFrom As Object, p1 As Long)
Public Event EvtC( _
pobjFrom As Object, p1 As Long, p2 As Long)
'イベント中継メソッド
Public Sub OnEvtA( _
pobjFrom As Object)
Debug.Print "clsEvt1.OnEvtA"
RaiseEvent EvtA(pobjFrom)
End Sub
Public Sub OnEvtB( _
pobjFrom As Object, p1 As Long)
Debug.Print "clsEvt1.OnEvtB"
RaiseEvent EvtB(pobjFrom, p1)
End Sub
Public Sub OnEvtC( _
pobjFrom As Object, p1 As Long, p2 As Long)
Debug.Print "clsEvt1.OnEvtC"
RaiseEvent EvtC(pobjFrom, p1, p2)
End Sub
'============================================================
'クラスモジュール:clsFrom1
'============================================================
'イベント通知元クラス1
Option Explicit
Private mobjEvt1 As clsEvt1
Public Property Set ObjEvt1(p1 As clsEvt1)
Set mobjEvt1 = p1
End Property
Public Sub OnEvtA()
Debug.Print "clsFrom1.OnEvtA"
Call mobjEvt1.OnEvtA(Me)
End Sub
Public Sub OnEvtB(p1 As Long)
Debug.Print "clsFrom1.OnEvtB"
Call mobjEvt1.OnEvtB(Me, p1)
End Sub
Public Sub OnEvtC(p1 As Long, p2 As Long)
Debug.Print "clsFrom1.OnEvtC"
Call mobjEvt1.OnEvtC(Me, p1, p2)
End Sub
'============================================================
'クラスモジュール:clsFrom2
'============================================================
'イベント通知元クラス2
Option Explicit
Private mobjEvt1 As clsEvt1
Public Property Set ObjEvt1(p1 As clsEvt1)
Set mobjEvt1 = p1
End Property
Public Sub OnEvtA()
Debug.Print "clsFrom2.OnEvtA"
Call mobjEvt1.OnEvtA(Me)
End Sub
Public Sub OnEvtB(p1 As Long)
Debug.Print "clsFrom2.OnEvtB"
Call mobjEvt1.OnEvtB(Me, p1)
End Sub
Public Sub OnEvtC(p1 As Long, p2 As Long)
Debug.Print "clsFrom2.OnEvtC"
Call mobjEvt1.OnEvtC(Me, p1, p2)
End Sub
'============================================================
'クラスモジュール:clsFrom3
'============================================================
'イベント通知元クラス3
Option Explicit
Private mobjEvt1 As clsEvt1
Public Property Set ObjEvt1(p1 As clsEvt1)
Set mobjEvt1 = p1
End Property
Public Sub OnEvtA()
Debug.Print "clsFrom3.OnEvtA"
Call mobjEvt1.OnEvtA(Me)
End Sub
Public Sub OnEvtB(p1 As Long)
Debug.Print "clsFrom3.OnEvtB"
Call mobjEvt1.OnEvtB(Me, p1)
End Sub
Public Sub OnEvtC(p1 As Long, p2 As Long)
Debug.Print "clsFrom3.OnEvtC"
Call mobjEvt1.OnEvtC(Me, p1, p2)
End Sub
'============================================================
'クラスモジュール:clsTo1
'============================================================
'イベント通知先クラス1
Option Explicit
Private WithEvents mobjEvt1 As clsEvt1
Private mobjFrom1 As clsFrom1
Private mobjFrom2 As clsFrom2
Private mobjFrom3 As clsFrom3
Public Sub Init1()
'イベント中継クラスの同じインスタンス
'を使って通知元と通知先をひもづける。
Set mobjEvt1 = New clsEvt1
Set mobjFrom1 = New clsFrom1
Set mobjFrom1.ObjEvt1 = mobjEvt1
Set mobjFrom2 = New clsFrom2
Set mobjFrom2.ObjEvt1 = mobjEvt1
Set mobjFrom3 = New clsFrom3
Set mobjFrom3.ObjEvt1 = mobjEvt1
End Sub
'イベントEvtAの発生処理の実行
Public Sub OnEvtA()
Debug.Print "clsTo1.onEvtA:>>>"
Debug.Print "clsTo1.onEvtA:001"
Call mobjFrom1.OnEvtA
Debug.Print
Debug.Print "clsTo1.onEvtA:002"
Call mobjFrom2.OnEvtA
Debug.Print
Debug.Print "clsTo1.onEvtA:003"
Call mobjFrom3.OnEvtA
Debug.Print "clsTo1.onEvtA:<<<"
End Sub
'イベントEvtBの発生処理の実行
Public Sub OnEvtB()
Debug.Print "clsTo1.OnEvtB:>>>"
Debug.Print "clsTo1.OnEvtB:001"
Call mobjFrom1.OnEvtB(1)
Debug.Print
Debug.Print "clsTo1.OnEvtB:002"
Call mobjFrom2.OnEvtB(2)
Debug.Print
Debug.Print "clsTo1.OnEvtB:003"
Call mobjFrom3.OnEvtB(3)
Debug.Print "clsTo1.OnEvtB:<<<"
End Sub
'イベントEvtCの発生処理の実行
Public Sub OnEvtC()
Debug.Print "clsTo1.OnEvtC:>>>"
Debug.Print "clsTo1.OnEvtC:001"
Call mobjFrom1.OnEvtC(1, 2)
Debug.Print
Debug.Print "clsTo1.OnEvtC:002"
Call mobjFrom2.OnEvtC(2, 3)
Debug.Print
Debug.Print "clsTo1.OnEvtC:003"
Call mobjFrom3.OnEvtC(3, 4)
Debug.Print "clsTo1.OnEvtC:<<<"
End Sub
'mobjEvt1のイベントプロシージャ
Private Sub mobjEvt1_EvtA( _
pobjFrom As Object)
Debug.Print "clsTo1.mobjEvt1_EvtA(" _
& TypeName(pobjFrom) & ")"
End Sub
Private Sub mobjEvt1_EvtB( _
pobjFrom As Object, p1 As Long)
Debug.Print "clsTo1.mobjEvt1_EvtB(" _
& TypeName(pobjFrom) & "," _
& Trim(p1) & ")"
End Sub
Private Sub mobjEvt1_EvtC( _
pobjFrom As Object, p1 As Long, p2 As Long)
Debug.Print "clsTo1.mobjFrom3_EvtC(" _
& TypeName(pobjFrom) & "," _
& Trim(p1) & "," & Trim(p2) & ")"
End Sub
'============================================================
'標準モジュール:modMain
'============================================================
Option Explicit
'テスト実行処理
Sub Test1()
Dim objTo1 As clsTo1
Set objTo1 = New clsTo1
Call objTo1.Init1
Debug.Print "MOD1.001"
Call objTo1.OnEvtA
Debug.Print
Debug.Print "MOD1.002"
Call objTo1.OnEvtB
Debug.Print
Debug.Print "MOD1.003"
Call objTo1.OnEvtC
End Sub
|
|
|
|
投稿日時: 12/03/08 00:28:08
投稿者: みそじのおじさん
|
|---|---|
|
みなさん、こんばんは。
|
|
|
|
投稿日時: 12/03/11 05:57:32
投稿者: YU-TANG
|
|---|---|
月 さんの引用: ちょっと思ったんですけれど、このアイテムって、コンテナの外にも移動できますよね。 そうすると、どのコンテナにも属していないアイテムっていうのが発生することになるんですが、それはどのように管理するイメージになりますか? #「どのコンテナにも属していないアイテム」用のダミーコンテナみたいなのに突っ込む? # 引用元を指すレス番が欲しいな…。 |
|
|
|
投稿日時: 12/03/11 07:21:57
投稿者: みそじのおじさん
|
|---|---|
|
おはようございます。YU-TANGさん早起きですね^^
YU-TANG さんの引用: 実は、このクラスは皆様に見てもらおうと思ったのが投稿の一日くらい前で、 「皆様に見てもらうなら」と取って付けたかのようなメソッドやプロパティを作成して しまいました。 改修前は、スタート時はどのコンテナにも属していないのが初期状態だったのです。 月さんに指摘されました「似たようなメソッドがある」は、この突貫工事の為でした^^; マウスアップ時に「コンテナ上でドロップされたか、どのコンテナにも属していないか」は 判定出来ていますので戻り値を適切に設定すれば元の位置に戻すというロジックで対応 はすぐ出来ると思います。(初期状態は必ずどれかのコンテナに属すという前提で) 月さんご提案のクラスの階層に改修すれば、どこにも属していないアイテムは、はなから 作成出来ませんので「やっぱりこっちの階層の方が自然だな」と感じております。 「皆様に見て頂く」のを前提としていなかった為、仕様上の不備や回りくどい処理が 沢山入っております。皆様に大変ご迷惑をかけております。 特にRECTの定義ですが Public Type Rect Left As Long Top As Long Width As Long 本当はRight Height As Long 本当はButtom End Type APIで必要なのは高さ、幅ではなくRightとButtomですので、Rightを求めるのにLeftと Widthを足すなんて目茶苦茶なコードになっています。領域関係のロジックでは単位変換 も行っていません。 (APIで得られた領域に移動するなら単位変換が必要ですが判定だけですので、、手抜きです^^) # 昨日、twitterの方でここの話題が書かれているのを見ました。 # 敷居が高いなんて言わず、どーぞお気軽にご参加ください! # コードは画面1つに収まる範囲で(笑)実用性があり「クラスって面白そうだな!」 # と思って頂ける様な簡単なクラスも投稿もしようと思っています。 # 昨日一日でデジタル時計のクラスを書き上げましたが、コードが画面1つに収まり # ません!(笑) 出直してきます^^ |
|
|
|
投稿日時: 12/03/11 11:43:29
投稿者: 月
|
|---|---|
YU-TANG さんの引用: まったく考えていませんでしたが、 clsContainers コンテナのコレクションクラス ↓ clsContainer アイテムを収容するコンテナ ↓ clsMoveItems アイテムのコレクションクラス ↓ clsMoveItem アイテム この場合、clsContainerクラスにはMoveItemsプロパティがあるのですが、コンテナの外にも配置できる仕様なら、コンテナの外 = UserFormにもMoveItemsプロパティを設けますかね。 見た目から考えているだけです。 |
|
|
|
投稿日時: 12/03/11 12:55:57
投稿者: 月
|
|---|---|
月 さんの引用: これもできていません。 |
|
|
|
投稿日時: 12/03/12 22:09:15
投稿者: マコ
|
|---|---|
みそじのおじさん さんの引用: その「1画面に・・」ツイートの犯人です ^^ あっという間に60件以上のレスがついて、盛り上がっている様子を すごいなぁ、かっこいいな〜 と拝見しておりました。 みなさんがすごい、すごい、とおっしゃってる、みそじのおじさんのサンプル。 中で何が行われているかは理解できてませんが、私も実行してみて、 ほんとにこれ、Excelのユーザーフォームなの???と驚くばかりでした。 水を差すようなことを言ってしまってごめんなさい。 1画面より、もう少し長くても大丈夫です!(たぶん 笑) デジタル時計のクラス、拝見したいです。ぜひアップしてくださーい! |
|
|
|
投稿日時: 12/03/13 06:21:22
投稿者: yayadon
|
|---|---|
|
# なんとなく,考えたことを並べてみました。
Private mMoveItems As clsMoveItems のように持っているので,WithEvents を付けて, イベントを受け取れます。 clsMoveItems 内で,mParent を触っているところ (clsMoveItem の Rect を変更するところ等) は,イベント実装にするのも,ありかもしれません。 また, コンテナのクライアント領域上の コンテナ内の要素の表示サイズ(COM的には extent/領域 といいます)の変更は, クライアント側からは,希望のサイズだけを要求する形にして, コンテナ側が行う方が自然な気がします。 clsMoveItems 内のいくつかのメソッドをイベントに変更して, イベントの引数 (ByVal TargetMoveItem As clsMoveItem) で,clsMoveItem の参照を渡せば, clsContainer 側での処理が可能な気がします。 clsMoveItem が必要とするミニマムな領域も渡してもいいかもしれません。 (ByVal TargetMoveItem As clsMoveItem, _ minimumRect As Rect) そんな感じにすれば, clsMoveItems 内の mParent は必要でなくなり, 弱参照のようなややこしい仕組みは必要なくなります。たぶん。 |
|
|
|
投稿日時: 12/03/13 07:15:20
投稿者: みそじのおじさん
|
|---|---|
|
みなさん、おはようございます。
|
|
|
|
投稿日時: 12/03/13 12:00:24
投稿者: 月
|
|---|---|
|
yayadonさん、ご感想ありがとうございます。
みそじのおじさん さんの引用: 言葉足らずで本当に申し訳ないのですが、読まなくてもいいですよ。 一番伝えたかったのは、こういう階層とメンバはどうだろう、ということで、半分は先に言っちゃってますし、言葉や図でも説明できるので、あとで追記しますね。気長にお待ちください。雰囲気だけ味わっていただいて、コードも見たいという方だけ見ていただければ。 |
|
|
|
投稿日時: 12/03/13 12:49:16
投稿者: 月
|
|---|---|
|
話があっちこっちしてしまいますが。
|
|
|
|
投稿日時: 12/03/13 13:43:54
投稿者: 月
|
|---|---|
yayadon さんの引用: 確認できました。なるほど〜。 |
|
|
|
投稿日時: 12/03/13 14:13:11
投稿者: 月
|
|---|---|
|
yayadonさんの説明は、コードを書いた私にしか腹に落ちないかもしれませんね。
|
|
|
|
投稿日時: 12/03/13 17:11:14
投稿者: Abyss
|
|---|---|
引用: Userformを土台どしているとしたら、UserformハンドルにTimerを掛けると 最低限の安全は保障されます。 |
|
|
|
投稿日時: 12/03/13 18:04:18
投稿者: Abyss
|
|---|---|
|
# 本題ですが、みそじのおじさん さんご提示クラスですが、
Public Function Item(Index) Attribute Item.VB_UserMemId = 0 End Function Public Function Add(Item) End Function Public Function Count&() End Function Public Sub Remove(Index) End Sub Public Function NewEnum() As IUnknown Attribute NewEnum.VB_UserMemId = -4 End Function ○ IRect interface Public Property Get pRect&() End Property ○ clsItem Private Declare Sub MoveMemory Lib "Kernel32" Alias "RtlMoveMemory" _
(pDest As Any, pSrc As Any, ByVal cbLen&)
Private bInit&
Private WithEvents mLb As MSForms.Label
Private aa!, bb!, fmWidth!, fmHeight!
Private mX!, mY!, mWidth!, mHeight!
Private mFm As [_UserForm]
Private mStr$, mPtr&
Event PosChange(ByVal n&, ByVal HmX&, ByVal HmY&)
Public Sub Attach(ByVal AttachTo As [_UserForm], ByVal Name$)
If bInit Then Exit Sub
Set mFm = AttachTo
mStr = Name
With mFm
fmWidth = .InsideWidth
fmHeight = .InsideHeight
Set mLb = .Controls.Add("Forms.Label.1")
End With
With mLb
.Height = 20
.Width = 70
mHeight = .Height
mWidth = .Width
.Name = ObjPtr(mLb)
.Caption = mStr
.BackColor = vbWhite
.ForeColor = vbBlue
.TextAlign = fmTextAlignCenter
.SpecialEffect = fmSpecialEffectEtched
End With
bInit = 1
End Sub
Public Property Get Parent() As clsContainer
Set Parent = ObjFromPtr(Val(mLb.Tag))
End Property
Public Property Set Parent(ByVal RHS As clsContainer)
Dim buf&, tmp As clsContainer
Dim IColl As ICollection
If bInit = 0 Then Exit Property
Set IColl = RHS
buf = Val(mLb.Tag)
If IColl Is Nothing Then
If buf Then
Set IColl = ObjFromPtr(buf)
IColl.Remove Me
mLb.Tag = vbNullString
Set tmp = IColl
If GetIDispatch(mFm).Visible Then tmp.Flush
End If
Else
If ObjPtr(RHS) <> buf Then
If IColl.Add(Me) Then
mLb.Tag = ObjPtr(RHS)
If buf Then
Set IColl = ObjFromPtr(buf)
IColl.Remove Me
Set tmp = IColl
tmp.Flush
End If
Else
If buf Then
Set tmp = ObjFromPtr(buf)
tmp.Flush
Else
SetPos mX, mY
End If
End If
End If
RHS.Flush
End If
End Property
Public Property Get Name$()
Name = mStr
End Property
Public Sub SetPos(ByVal X!, ByVal Y!)
If bInit = 0 Then Exit Sub
mLb.Move X, Y
End Sub
Public Property Get Height!()
If bInit = 0 Then Exit Property
Height = mLb.Height
End Property
Public Property Get Width!()
If bInit = 0 Then Exit Property
Width = mLb.Width
End Property
Private Sub Class_Terminate()
If bInit = 0 Then Exit Sub
mFm.Controls.Remove mLb.Name
bInit = 0
End Sub
Private Sub mLb_MouseDown _
(ByVal Button%, ByVal Shift%, ByVal X!, ByVal Y!)
If Button <> 1 Then Exit Sub
aa = X: bb = Y
Set GetIDispatch(mFm).cls = Me
With mLb
If Val(.Tag) = 0 Then
mX = .Left: mY = .Top
End If
.ZOrder 0
.BackColor = vbYellow
.SpecialEffect = fmSpecialEffectBump
End With
End Sub
Private Sub mLb_MouseMove _
(ByVal Button%, ByVal Shift%, ByVal X!, ByVal Y!)
Dim ma!, mb!, e
Dim tmp&, Ctnr As clsContainer
If Button <> 1 Then Exit Sub
With mLb
ma = .Left + X - aa
mb = .Top + Y - bb
If ma < 0 Then ma = 0
If mb < 0 Then mb = 0
If ma + mWidth > fmWidth Then
ma = fmWidth - mWidth
End If
If mb + mHeight > fmHeight Then
mb = fmHeight - mHeight
End If
.Move ma, mb
RaiseEvent PosChange(0, Hm(ma + X), Hm(mb + Y))
End With
End Sub
Private Sub mLb_MouseUp _
(ByVal Button%, ByVal Shift%, ByVal X!, ByVal Y!)
Dim tmp&
Dim ptr&, buf&
If Button <> 1 Then Exit Sub
With mLb
.BackColor = vbWhite
.SpecialEffect = fmSpecialEffectEtched
End With
With mLb
RaiseEvent PosChange(1, Hm(.Left + X), Hm(.Top + Y))
End With
End Sub
Private Function GetIDispatch(ByVal unk As IUnknown) As Object
Set GetIDispatch = unk
End Function
Private Sub AddTo(ByVal Ctn As ICollection)
If Ctn Is Nothing Then Exit Sub
Ctn.Add Me
End Sub
Private Sub RemoveFrom(ByVal Ctn As ICollection)
Ctn.Remove Me
End Sub
Private Function Hm&(ByVal m!)
Hm = m * 2540! \ 72!
End Function
Private Function ObjFromPtr(ByVal ptr&) As clsContainer
Dim tmp As clsContainer
MoveMemory tmp, ptr, 4
Set ObjFromPtr = tmp
MoveMemory tmp, 0&, 4
End Function
○ clsContainer Private mFm As [_UserForm]
Private bInit As Long, mName$
Private mLb As ILabelControl, mLbTtl As ILabelControl
Private mRect&(3), mMaxCnt&
Private mCol As Collection
Implements IRect
Implements ICollection
Event Alert(ByVal mCnt&)
Private Sub Class_Initialize()
Set mCol = New Collection
End Sub
Private Sub Class_Terminate()
Dim i&
If bInit = 0 Then Exit Sub
With mFm.Controls
.Remove mLb.Name
.Remove mLbTtl.Name
End With
For i = 1 To mCol.Count
mCol.Remove 1
Next
Set mCol = Nothing
End Sub
Public Sub Attach(ByVal AttachTo As [_UserForm], ByVal Name$)
Dim buf&
If bInit Then Exit Sub
Set mFm = AttachTo
Set mLbTtl = mFm.Controls.Add("Forms.Label.1")
With mLbTtl
.Name = ObjPtr(mLbTtl)
.Caption = Name
.ForeColor = vbWhite
.BackColor = &H800000
.TextAlign = fmTextAlignCenter
.SpecialEffect = fmSpecialEffectEtched
End With
Set mLb = mFm.Controls.Add("Forms.Label.1")
With mLb
.BackColor = vbWhite
.Name = ObjPtr(mLb)
.SpecialEffect = fmSpecialEffectEtched
End With
mName = Name
bInit = 1
GetRect mLb
End Sub
Public Sub SetPos(ByVal X!, ByVal Y!, Optional ByVal Width!, Optional ByVal Height!)
If bInit = 0 Then Exit Sub
mLb.Move X, Y, Width, Height
mLbTtl.Move X, Y - mLbTtl.Height, Width
GetRect mLb
End Sub
Public Property Let MaxCount(ByVal RHS&)
If bInit = 0 Then Exit Property
mMaxCnt = RHS
End Property
Public Property Get MaxCount&()
If bInit = 0 Then Exit Property
MaxCount = mMaxCnt
End Property
Public Sub Flush()
Dim IColl As ICollection
Dim e As clsItem, X!, Y!, mX!, mY!
If bInit = 0 Then Exit Sub
Set IColl = Me
mX = mLb.Left: mY = mLb.Top
X = mX: Y = mY
For Each e In IColl
If Y >= mY + mLb.Height Then
Y = mY: X = mX + e.Width
End If
e.SetPos X, Y
Y = Y + e.Height
Next
End Sub
Public Sub HiLite(ByVal f As Long)
If bInit = 0 Then Exit Sub
With mLb
If f Then
If .BorderStyle = fmBorderStyleSingle Then Exit Sub
.BorderStyle = fmBorderStyleSingle
.BorderColor = vbBlue
Else
If .SpecialEffect = fmSpecialEffectEtched Then Exit Sub
.SpecialEffect = fmSpecialEffectEtched
End If
End With
End Sub
Public Property Get Name$()
Name = mName
End Property
Private Sub GetRect(ByVal ctl As IControl)
Dim buf&
If bInit = 0 Then Exit Sub
With ctl
.[_GetLeft] mRect(0)
.[_GetTop] mRect(1)
.[_GetWidth] mRect(2)
.[_GetHeight] mRect(3)
End With
mRect(2) = mRect(2) + mRect(0)
mRect(3) = mRect(3) + mRect(1)
End Sub
Private Function ICollection_Add(Item)
Dim cls As clsItem
If IsItemFull Then
RaiseEvent Alert(mMaxCnt)
Exit Function
End If
Set cls = Item
mCol.Add cls, CStr(ObjPtr(cls))
ICollection_Add = 1
End Function
Private Function ICollection_Count() As Long
ICollection_Count = mCol.Count
End Function
Private Function IsItemFull() As Long
If mMaxCnt = 0 Then Exit Function
IsItemFull = mCol.Count = mMaxCnt
End Function
Private Function ICollection_Item(Index)
ICollection_Item = mCol(Index)
End Function
Private Function ICollection_NewEnum() As IUnknown
Set ICollection_NewEnum = mCol.[_NewEnum]
End Function
Private Sub ICollection_Remove(Index)
Select Case VarType(Index)
Case vbObject: mCol.Remove CStr(ObjPtr(Index))
Case vbLong: mCol.Remove Index
End Select
End Sub
Private Property Get IRect_pRect() As Long
IRect_pRect = VarPtr(mRect(0))
End Property
Private Function GetIDispatch(ByVal unk As IUnknown) As Object
Set GetIDispatch = unk
End Function |
|
|
|
投稿日時: 12/03/13 18:11:32
投稿者: Abyss
|
|---|---|
|
こちらが実際のUserformに乗せるコード。
Private Declare Function PtInRect& Lib "User32" _
(ByVal lprc&, ByVal X&, ByVal Y&)
Public WithEvents cls As clsItem
Private WithEvents Ctnr As clsContainer
Private WithEvents mCmd As MSForms.CommandButton
Private mText As MSForms.TextBox
Private colCtnr As Collection
Private colItem As Collection
Private Sub UserForm_Initialize()
Dim i&, e
Dim oItem As MSForms.Label
Dim S As String * 12
Set colCtnr = New Collection
Set colItem = New Collection
Width = 640!
Height = 320!
i = 20
For Each e In Array("Moug", _
"ExcelVBA講座", _
"AccessVBA講座", _
"WordVBA講座")
S = e
Set Ctnr = New clsContainer
Ctnr.Attach Me, S
Ctnr.SetPos i, 30, 140, 100
colCtnr.Add Ctnr, S
i = i + 150
Next
colCtnr(2).MaxCount = 3 'ExcelVBA
colCtnr(3).MaxCount = 4 'AccessVBA
For Each e In Array("yayadonさん", _
"ろひさん", _
"simpleさん", _
"月さん", _
"ゴマさん", _
"kumattiさん", _
"YU-TANGさん", _
"Kanabunさん", _
"Abyssさん", _
"みそじのおじさん")
Set cls = New clsItem
cls.Attach Me, CStr(e)
Set cls.Parent = colCtnr(1) ' Moug
colItem.Add cls
Next
FlushAll
Set mCmd = Me.Controls.Add("Forms.CommandButton.1")
With mCmd
.Move 20, 140, 50, 20
.Caption = "決定"
End With
''グループ分け結果の表示用テキストボックス作成
Set mText = Controls.Add("Forms.TextBox.1")
With mText
.Move 80, 160, 150, 120
.SpecialEffect = fmSpecialEffectEtched
.MultiLine = True
End With
''見出しのラベル作成
Set oItem = Me.Controls.Add("Forms.Label.1")
With oItem
.Move 80, 140, 150, 20
.SpecialEffect = fmSpecialEffectEtched
.Caption = "グループ分け結果"
.TextAlign = fmTextAlignCenter
End With
End Sub
Private Sub UserForm_Terminate()
Set cls = Nothing
Set Ctnr = Nothing
Set colCtnr = Nothing
Set colItem = Nothing
End Sub
Private Sub cls_PosChange(ByVal n&, ByVal HmX&, ByVal HmY&)
Dim e As clsContainer
Dim IColl As ICollection
Dim i&, j&, iOrder&, tmp&, flag&
If colCtnr Is Nothing Then Exit Sub
If colCtnr.Count = 0 Then Exit Sub
If n Then ' MouseUpイベント時
For Each e In colCtnr
e.HiLite 0
If CheckInRect(e, HmX, HmY) Then
Set Ctnr = e
Set cls.Parent = e
flag = 1
Exit For
End If
Next
If flag = 0 Then
Set cls.Parent = Nothing
End If
Exit Sub
End If
' MouseMoveイベント時
For Each e In colCtnr
e.HiLite CheckInRect(e, HmX, HmY)
Next
End Sub
Private Sub Ctnr_Alert(ByVal mCnt As Long)
MsgBox "限度Item数 = " & mCnt
End Sub
Private Function CheckInRect&(ByVal m As IRect, ByVal X&, ByVal Y&)
CheckInRect = PtInRect(m.pRect, X, Y)
End Function
Private Sub FlushAll()
Dim e As clsContainer
For Each e In colCtnr
e.Flush
Next
End Sub
Private Sub mCmd_Click()
Dim IColl As ICollection
Dim e As clsContainer
Dim m As clsItem
Dim S() As String
Dim i As Long
ReDim S(1 To colItem.Count)
For Each e In colCtnr
Set IColl = e
For Each m In IColl
i = i + 1
S(i) = e.Name & vbTab & m.Name
Next
Next
For Each m In colItem
If m.Parent Is Nothing Then
i = i + 1
S(i) = Space$(15) & vbTab & m.Name
End If
Next
mText.Text = Join(S, vbCr)
End Sub |
|
|
|
投稿日時: 12/03/13 23:16:11
投稿者: どんきち
|
|---|---|
月 さんの引用: instanceプロパティ等についいて詳しくはありませんが、参考意見のひとつとしてコメントさせてもらいます。(別プロジェクトを参照するようなプログラムは作ったことありません。開発支援のためのちょっとしたツール程度しか作ったことがないので、アドインの形で作ることがほとんどです。) 別のプロジェクトを参照設定するということは、Excelの場合、VBAのプログラムを格納するワークブックを複数作るってことですよね。ということは、あるワークブックの中にあるVBAのプログラムを正常に実行しようと思ったら、別のワークブックが必要になるということです。 ワークブックをわけてプログラムを作ったら、はじめて導入するときや、メンテナンスしたときなどに、エンドユーザーにプログラムを配布するときに複数のワークブックを整合性の取れた状態で配布しなければいけません。 クラスの再利用という観点以外に、VBAのプログラムが格納されているワークブックをわける必要があるのかどうかということも考えてみる必要があるのではないでしょうか。 自分がワークブックをわけるたくなるような例として思いついたのは以下の2つだけでした。 ・プログラム全体が大規模なので、機能単位でワークブックを分割したい。 ・配布する環境が社内の環境だけに限られている。 クラスを別のプログラムを採用したいけれど、わざわざ別ブックに分ける必要がないのであれば、一つ一つのクラスの独立性をできるだけ高くしておいて、エクスポート機能でテキストファイルに出力したあとで、別ファイルでインポート機能で取り込めば済む話のような気もします。 |
|
|
|
投稿日時: 12/03/14 07:15:14
投稿者: みそじのおじさん
|
|---|---|
|
▼月さん
|
|
|
|
投稿日時: 12/03/14 13:14:46
投稿者: 月
|
|---|---|
どんきち さんの引用: それでいい場合もありますし、ファイルを分ける選択肢もあると私は言いたいのです。 例えば、FileSystemObjectクラスを事前バインディングで使おうと思ったら、Microsoft Scripting Runtimeに参照設定しますが、参照設定せずにMicrosoft Scripting Runtimeの10個のクラスを自プロジェクトにインポートして使うと考えるとどうでしょう。めんどくさいですよね。クラス名などの名前の衝突の問題もあります。 |
|
|
|
投稿日時: 12/03/14 13:41:31
投稿者: 月
|
|---|---|
|
補足
Abyss さんの引用: 1. ICollectionクラスを作ってこのコードを貼り付ける 2. ICollectionクラスを ファイル > ファイルのエクスポート 3. ICollectionクラスを削除 4. エクスポートしたファイルを ファイル > ファイルのインポート として使います。 |
|
|
|
投稿日時: 12/03/14 16:44:55
投稿者: Abyss
|
|---|---|
|
月さん、ありがとうございます。
|
|
|
|
投稿日時: 12/03/14 22:01:59
投稿者: みそじのおじさん
|
|---|---|
|
こんばんは。今日は多少早く帰って来られました^^
|
|
|
|
投稿日時: 12/03/14 22:47:03
投稿者: どんきち
|
|---|---|
|
参照関係が3階層のときのPublicなクラスの参照(1)
|
|
|
|
投稿日時: 12/03/14 22:54:47
投稿者: どんきち
|
|---|---|
|
参照関係が3階層のときのPublicなクラスの参照(2)
|
|
|
|
投稿日時: 12/03/14 23:57:25
投稿者: 月
|
|---|---|
yayadon さんの引用: 仰る通りになりました。 mParentを使っている箇所をメソッドからイベントに変更したところ、すべてのクラスのTerminateイベントが発生しました。 明日追記します。 |
|
|
|
投稿日時: 12/03/15 03:15:42
投稿者: Abyss
|
|---|---|
|
今のところの出来コードです。
Public Function WndProc&(ByVal Hwnd&, ByVal uMsg&, _
ByVal wParam&, ByVal lParam&)
End Function
○ クラスモジュール(clsMonthCal) Private Declare Function InitCommonControlsEx& Lib "Comctl32" _
(ByVal lpInitCtrl&)
Private Declare Function CreateWindowExW& Lib "User32" _
(ByVal dwExStyle&, _
ByVal lpClassName&, _
ByVal lpWindowName&, _
ByVal dwStyle&, _
ByVal x&, ByVal y&, _
ByVal nWidth&, ByVal nHeight&, _
ByVal hParent&, _
ByVal hMenu&, _
ByVal hInstance&, _
ByVal lParam&)
Private Declare Function GetModuleHandleW& Lib "Kernel32" _
(ByVal lpModName&)
Private Declare Function GetProcAddress& Lib "Kernel32" _
(ByVal hMod&, _
ByVal lpProcName&)
Private Declare Function VirtualAlloc& Lib "Kernel32" _
(ByVal lpAddr&, _
ByVal dwSize&, _
ByVal fType&, _
ByVal flProtect&)
Private Declare Function VirtualFree& Lib "Kernel32" _
(ByVal lpAddr&, _
ByVal dwSize&, _
ByVal dwFreeType&)
Private Declare Function SetWindowLongW& Lib "User32" _
(ByVal Hwnd&, _
ByVal nIndex&, _
ByVal dwNewLong&)
Private Declare Function GetWindowLongW& Lib "User32" _
(ByVal Hwnd&, _
ByVal nIndex&)
Private Declare Function CallWindowProcW& Lib "User32" _
(ByVal lpPrevWndFunc&, _
ByVal Hwnd&, _
ByVal uMsg&, _
ByVal wParam&, _
ByVal lParam&)
Private Declare Function GetFocus& Lib "User32" ()
Private Declare Sub MoveMemory Lib "Kernel32" Alias "RtlMoveMemory" _
(pDest As Any, _
pSrc As Any, _
ByVal cbLen&)
Private Declare Function SendMessageW& Lib "User32" _
(ByVal Hwnd&, _
ByVal uMsg&, _
ByVal wParam&, _
ByVal lParam&)
Private Declare Function MoveWindow& Lib "User32" _
(ByVal Hwnd&, _
ByVal x&, ByVal y&, _
ByVal nWidth&, ByVal nHeight&, _
ByVal bRepaint&)
Private Declare Function SetFocusVB& Lib "User32" Alias "SetFocus" _
(ByVal Hwnd&)
Private Const GWL_WNDPROC = -4
Private Const MEM_RELEASE = &H8000&
Private Const MEM_COMMITRESERVE = &H3000&
Private Const PAGE_EXECUTE_READWRITE = &H40&
Private Const ICC_DATE_CLASSES = &H100&
Private Const WM_SETFOCUS = 7
Private Const WM_MOUSEACTIVATE = &H21
Private Const WM_SETFONT = &H30
Private Const WM_NOTIFY = &H4E
Private Const WM_NCDESTROY = &H82
Private Const WS_CHILD = &H40000000
Private Const WS_VISIBLE = &H10000000
Private Const MCS_DAYSTATE = 1
Private Const GMR_VISIBLE = 0
Private Const MCM_SETCOLOR = &H100A
Private Const MCM_GETMINREQRECT = &H1009
Private Const MCM_GETMAXTODAYWIDTH = &H1015
Private Const MCM_GETMONTHRANGE = &H1007
Private Const MCM_SETDAYSTATE = &H1008
Private Const MA_NOACTIVATE = 3
Private Const WS_CLIPSIBLINGS = &H4000000
Private Const WS_EX_CLIENTEDGE = &H200&
Private Type TT
hParent As Long
hChild As Long
pp As Long
dDate As Date
End Type
Private Type DD
m(2) As Long
End Type
Private TT As TT, acc As IAccessible, flg&, pPrev&
Event DateChange(ByVal dDate As Date)
Implements IProc
Private Sub Class_Initialize()
TT.dDate = Date
End Sub
Public Property Get GetDate() As Date
GetDate = TT.dDate
End Property
Public Sub Connect(ByVal fr As IOptionFrame)
Dim Cnt&
Dim ii&(1), Asm@(7)
Dim wd&, ht&, hMod&, hProc&, tmp$, ro#, rc&(3)
Dim fnt As stdole.IFont
Dim i&, buf&(3)
Dim DD As DD, Impl As IProc
Asm(0) = 3665925540493.8379@
Asm(1) = 111355235910746.6612@
Asm(2) = 585928097174388.7544@
Asm(3) = 373826806618259.4744@
Asm(4) = -394872433264837.8176@
Asm(5) = -36870567463020.0143@
Asm(6) = 3921298269300.3448@
Asm(7) = 1842688795.0591@
Cnt = (UBound(Asm) + 1) * 8
TT.hParent = GetHwnd(fr)
If TT.hParent = 0 Then Err.Raise 94
Set acc = fr
Set fnt = fr.Font
acc.accLocation 0, 0, wd, 0
ro = fr.Width / wd
ii(0) = 8
ii(1) = ICC_DATE_CLASSES
InitCommonControlsEx VarPtr(ii(0))
With TT
.hChild = CreateWindowExW(WS_EX_CLIENTEDGE, _
StrPtr("SysMonthCal32"), 0, _
WS_CHILD Or WS_VISIBLE Or WS_CLIPSIBLINGS Or MCS_DAYSTATE, _
0, 0, 0, 0, _
.hParent, 0, 0, 0)
' 色変更
SendMessageW .hChild, MCM_SETCOLOR, 4, &H99FFFF ' MCSC_MONTHBK
SendMessageW .hChild, MCM_SETCOLOR, 2, &H808000 ' MCSC_TITLEBK
' Font変更
SendMessageW .hChild, WM_SETFONT, fnt.hFont, 0&
SendMessageW .hChild, MCM_GETMINREQRECT, 0, VarPtr(rc(0))
' 日曜日を太字にする。
flg = ToBold(.hChild)
' 下段の「今日の日付」Width取得
i = SendMessageW(.hChild, MCM_GETMAXTODAYWIDTH, 0, 0)
rc(3) = rc(3) + 2
MoveWindow .hChild, 0, 0, i, rc(3), 0
End With
With fr
.Width = i * ro
.Height = rc(3) * ro
End With
i = VarPtr(Asm(0))
MoveMemory ByVal i + 17, GetWindowLongW(TT.hChild, GWL_WNDPROC), 4
hMod = GetModuleHandleW(StrPtr("User32"))
tmp = StrConv("CallWindowProcW", vbFromUnicode)
hProc = GetProcAddress(hMod, StrPtr(tmp))
MoveMemory ByVal i + 25, hProc, 4
Set Impl = Me
MoveMemory ByVal i + 49, ObjPtr(Impl), 4
i = VirtualAlloc(0, Cnt, MEM_COMMITRESERVE, PAGE_EXECUTE_READWRITE)
MoveMemory ByVal i, Asm(0), Cnt
With TT
.pp = i
SetWindowLongW .hChild, GWL_WNDPROC, .pp
pPrev = SetWindowLongW(.hParent, GWL_WNDPROC, .pp + 31)
End With
End Sub
Private Sub Class_Terminate()
VirtualFree TT.pp, 0, MEM_RELEASE
End Sub
Private Function IProc_WndProc&(ByVal Hwnd&, ByVal uMsg&, _
ByVal wParam&, ByVal lParam&)
Dim m%(7), Cd&
' Frame Window
If Hwnd = TT.hParent Then
Select Case uMsg
Case WM_SETFOCUS
SetFocusVB TT.hChild
Exit Function
Case WM_NOTIFY
MoveMemory Cd, ByVal lParam + 8, 4
If Cd = -749 Then 'MCN_SELCHANGE
MoveMemory m(0), ByVal lParam + 12, 8
MoveMemory Cd, m(0), 4
If Cd <> flg Then
flg = Cd
ToBold TT.hChild
End If
With TT
.dDate = DateSerial(m(0), m(1), m(3))
RaiseEvent DateChange(.dDate)
End With
Exit Function
End If
End Select
IProc_WndProc = CallWindowProcW(pPrev, Hwnd, uMsg, wParam, lParam)
' SysMonthCal32 Window
Else
Select Case uMsg
Case WM_MOUSEACTIVATE
If GetFocus() <> Hwnd Then
acc.accSelect 16&
acc.accSelect 1&
IProc_WndProc = MA_NOACTIVATE
End If
End Select
End If
End Function
Private Function GetHwnd&(ByVal ctl As MSForms.IControl)
GetHwnd = ctl.[_GethWnd]
End Function
Private Function ToBold&(ByVal Hwnd&)
Dim m%(15), aa&(2)
Dim p&, i&, buf&, mDay&, w&
p = VarPtr(m(0))
If SendMessageW(Hwnd, MCM_GETMONTHRANGE, GMR_VISIBLE, p) = 0 Then
Exit Function
End If
MoveMemory ToBold, m(0), 4
' VBA曜日取得関数があるので手抜き...
w = Weekday(DateSerial(m(0), m(1), m(3)), vbMonday)
buf = (7 - w) Mod 7
For i = 0 To 4
mDay = buf + 7 * i
If mDay < 31 Then
aa(1) = aa(1) Or 2 ^ mDay
End If
Next
SendMessageW Hwnd, MCM_SETDAYSTATE, 3, VarPtr(aa(0))
End Function
○ 実際のサンプル(Userform)前回同様、サンプルですので、 部品なしのUserformだけの用意を想定します。 Private oo As UserForm1
Private WithEvents cls As clsMonthCal
Private WithEvents Btn As MSForms.CommandButton
Private mDt As Date
Private Sub Btn_Click()
MsgBox mDt
End Sub
Private Sub UserForm_Initialize()
Dim fr As IOptionFrame
Set fr = Controls.Add("Forms.Frame.1")
With fr.Font
.Name = "MS PGothic"
.Size = 10
End With
Set cls = New clsMonthCal
cls.Connect fr
Set Btn = Controls.Add("Forms.CommandButton.1")
With fr
.Move 5, 5
Width = .Width + 10 + (Width - InsideWidth)
Btn.Top = .Top + .Height + 4
Btn.Width = .Width
Btn.Left = .Left
End With
With Btn
.Caption = "日付確認"
Height = .Top + .Height + 5 + (Height - InsideHeight)
End With
mDt = cls.GetDate
Caption = mDt
' 通常、モードレスFormの場合、Excelの最小化で
' Userformが見えなくなる不具合のおまじない。
Set oo = Me
End Sub
Private Sub cls_DateChange(ByVal dDate As Date)
mDt = dDate
Caption = mDt
End Sub
|
|
|
|
投稿日時: 12/03/15 03:23:55
投稿者: Abyss
|
|---|---|
|
モードレス時にも問題ないようにしています。
|
|
|
|
投稿日時: 12/03/15 08:22:58
投稿者: yayadon
|
|---|---|
|
# イベントのところで
WithEvents m_obj As Class1
'-----
Private Sub Class_Initialize()
Set m_obj = New Class1 ' A
End Sub
Private Sub Class_Terminate()
Set m_obj = Nothing ' B
End Sub
上記の A の地点で, Class1 の参照の設定と,イベントへの登録(Advise)が同時に行われます。 上記の B の地点で, イベントへの登録解除(Unadvise) と Class1 の参照の Release が同時に行われます。 ※ 実際は,B の一文がなくても,後で Unadvise & Release されます。 上記のコードだと, イベントの登録と解除が,インスタンスの設定/解除とシンクロしていますが, イベントは,イベントとして, イベントの利用の開始のお願い と イベントの利用の終了の通知 を 別に行える仕組みになっています。 やる必要がある場面はなさそうですが, WithEvents m_objA As Class1 Dim m_objB As Class1のように2つ用意して差し替えれば, イベントの解除だけをすることは可能です。 ===== 解放の順番をもう一度整理すると, 他のオブジェクト │ ↓ イベントを利用する オブジェクト ref count: 1 : │ ref count: 1 sink object │ ↑ │ 参照 参照 │ ↓ イベントを提供する オブジェクト ref count: 1 の状態で, [他のオブジェクト] が,[イベントを利用するオブジェクト] を Release すると, [イベントを利用するオブジェクト] の ref count は 0 になります。 他のオブジェクト │ ↓ イベントを利用する オブジェクト ref count: 0 : │ ref count: 1 sink object │ ↑ │ 参照 参照 │ ↓ イベントを提供する オブジェクト ref count: 1 そして, [イベントを利用するオブジェクト] は, 自身のインスタンスが破棄される際 もしくは WithEvents 付き変数 に Nothing がセットされた際に, まず先に,[イベントを提供するオブジェクト] の Unadvise を呼びます。 [イベントを提供するオブジェクト] は,Unadvise を呼ばれて, sink object を Release します。 その結果,sink object への参照が先に消えて 他のオブジェクト イベントを利用する オブジェクト ref count: 0 : │ ref count: 0 sink object │ │ 参照 ↓ イベントを提供する オブジェクト ref count: 1 の状態になります。 そして,オブジェクトへの参照を Release するので, 他のオブジェクト イベントを利用する オブジェクト ref count: 0 : ref count: 0 sink object イベントを提供する オブジェクト ref count: 0 の状態になります。 # イベント絡みのことで他に思いつく,参照カウント絡みのこと。 イベント通知があったときに, イベントを処理している間は,自身が不意に解放されないように, 自身の参照カウントを一時的に上げておく仕掛けがあります。 VBA で Event を実装した場合でも,そういう実装になっているハズ。 |
|
|
|
投稿日時: 12/03/15 12:27:40
投稿者: 月
|
|---|---|
月 さんの引用: できました。 親オブジェクトへの参照を保持せずに親オブジェクトへの参照を得る方法 ― Gist https://gist.github.com/2041685 |
|
|
|
投稿日時: 12/03/15 12:39:29
投稿者: 月
|
|---|---|
Abyss さんの引用: ObjPtr関数の戻り値をキーとするアイディア、いいですね〜。 CollectionクラスにオブジェクトをAddする時の、個人的ベストプラクティスになりそうです。 勉強になりました。 このスレ、興味深いものが多いです。 ゆっくり時間をとって見たいと思っているのですが、なかなかできません。 コメントされた方は何かしら返信を期待すると思いますが、そういった理由ですので、無視しているわけではないことをご了承ください。書いた本人が忘れた頃に返信させていただくかもしれません。 |
|
|
|
投稿日時: 12/03/15 13:54:56
投稿者: 月
|
|---|---|
月 さんの引用: 修正しました。 |
|
|
|
投稿日時: 12/03/15 14:51:44
投稿者: kumatti
|
|---|---|
|
> clsMonthCal
|
|
|
|
投稿日時: 12/03/15 15:04:05
投稿者: Abyss
|
|---|---|
引用: 本当は、edi, esiを指定し、rep movsdを掛けたかったですが、 昨日は後半部で疲れまして.....手抜きです。(笑) |
|
|
|
投稿日時: 12/03/15 17:41:35
投稿者: 角田
|
|---|---|
|
こんにちは。
引用: これは、下記の流れという理解で良いでしょうか? (同じ Parent の名称が紛らわしいので、区別できるように名前を変えてあります) Dim Parent1 As ParentA
Set Parent1 = New ParentA
Debug.Print Parent1.Child.Parent.Name
^^^^^@^^^^^^^
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
[ParentA]
Private WithEvents m_ChildA As ChildA
┌────────┐
┌→Private Sub m_ChildA_GetParent(ByRef argParent As ParentA) │
│ Set argParent = Me ^^^^^^ ↑ │
│ └──────────────┘ │
│ End Sub │
│ │
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
│ [ChildA] │
└────────┐ │
┌→Public Event GetParent(ByRef argParent As ParentA) │
│ ^^^^^^ │
│@Public Property Get Parent() As ParentA │
│ Dim Temp As ParentA │
└─ RaiseEvent GetParent(Temp) │
Set Parent = Temp ↑ │
End Property └─────────────────┘
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
テストプロシジャーから呼ぶ都合で、Parent が Public になっていますが、 話題の内容からすると、実際の利用場面では Private にして ChildA 内での利用に限る という理解で合っていますでしょうか? 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
[ParentA]
Private WithEvents m_ChildA As ChildA
Private Sub m_ChildA_GetParent(ByRef argParent As ParentA)
Public Sub xxx ・・・
Public Property Get yyy ・・・
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
[ChildA]
Public Event GetParent(ByRef argParent As ParentA)
Private Property Get Parent() As ParentA
End Property
'必要な時に、その都度、親オブジェクトの参照を一時的に取得し、即破棄する。
Me.Parent.xxx
Me.Parent.yyy
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
WithEvents を使う為、この方法は親子が1:1の場合のみ可能で、 親子が1:nのケースでは、従来通りに「親オブジェクト参照をクラス内に持つ」 しかない、という理解で良いでしょうか。 ず〜っと、ROMってましたが、大変興味深く拝見していました。 Friend は次回変更時に使ってみようと思います。 |
|
|
|
投稿日時: 12/03/15 18:20:47
投稿者: 月
|
|---|---|
|
角田さん、ご質問ありがとうございます。
角田 さんの引用: はい、仰るとおりです。 ぱっと見わかりづらいと思います、すみません。 1行で書けることを思いついて嬉しくなり、そうしてしまいました。 (角田さんはご理解されていますが)一応説明します。 戻り値のあるプロシージャは、 Function test() As String
test = "a"
End Function
このようにして戻り値を返します。 つまり、プロシージャ名と同名の変数が自動的に作成されます。 再掲: Public Property Get Parent() As ParentA
' 親オブジェクトへの参照を得る
RaiseEvent Parent(Parent)
End Property
この場合、Parentという変数が自動的に作成され、その変数をParentイベントに引数として渡しています。引数として渡す前の時点ではParent変数の中身はNothingです。 再掲: Private Sub m_ChildA_Parent(Parent As ParentA)
Set Parent = Me
End Sub
Parentイベント側では、参照渡しされた引数Parentに親オブジェクトを設定し、結果的に返しています。 ------------------------ ここで一旦区切ります。 余談1 角田 さんの引用: Dim Parent1 As ParentA
Set Parent1 = New ParentA
Debug.Print Parent1.Child.Parent.Name
^^^^^@^^^^^^^
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
[ParentA]
Private WithEvents m_ChildA As ChildA
┌────────┐
┌→Private Sub m_ChildA_GetParent(ByRef argParent As ParentA) │
│ Set argParent = Me ^^^^^^ ↑ │
│ └──────────────┘ │
│ End Sub │
│ │
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
│ [ChildA] │
└────────┐ │
┌→Public Event GetParent(ByRef argParent As ParentA) │
│ ^^^^^^ │
│@Public Property Get Parent() As ParentA │
│ Dim Temp As ParentA │
└─ RaiseEvent GetParent(Temp) │
Set Parent = Temp ↑ │
End Property └─────────────────┘
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
図説とはこういうものを言うんだなと勉強になりました。 余談2 みそじのおじさん、次のスレお願いします。 |
|
|
|
投稿日時: 12/03/15 18:45:24
投稿者: 月
|
|---|---|
角田 さんの引用: はい、合っています。 角田 さんの引用: そのロジックは仰る通りだと思うのですが、本当に方法がないのかどうかはパッとわかりません、すみません。 |
|
|
|
投稿日時: 12/03/15 19:09:12
投稿者: みそじのおじさん
|
|---|---|
|
会社で今見まして驚きました^^
|
|