ほっとひといき給湯室 |
|
投稿日時: 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
投稿者: みそじのおじさん
|
---|---|
会社で今見まして驚きました^^
|