[[20150821004804]] 『ユーザーフォームの全ての種類のコントロールに同』(田吾作) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『ユーザーフォームの全ての種類のコントロールに同じイベントを割り付ける』(田吾作)

 クラスモジュールを使用し、ユーザーフォーム上の全てのコントロールに同じイベントを割り付けようとしています。

 現在は、ラベル限定のコードになっています。このコードはラベル上でマウスを動かすと、イベントが起動します。
 これはうまくいっています。

 'ユーザーフォーム

 Dim con() As New concls

 Private Sub UserForm_Initialize()
  Dim cnt As Integer
  Dim mycon As Object
   cnt = -1
   For Each mycon In Me.Controls
    If TypeName(mycon) = "Label" Then
       cnt = cnt + 1
       ReDim Preserve con(0 To cnt)
       Set con(cnt).ctrl = mycon
    End If
   Next mycon
 End Sub

 'クラス/クラス名:concls

 Public WithEvents ctrl As MSForms.Label

 Private Sub ctrl_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
  MsgBox ctrl.Name
 End Sub

 これを全ての種類のコントロールで作動するようにしたいのです。
 試しに↓のようにしましたがエラーになります。

 >Public WithEvents ctrl As MSForms.Control
 これの
 >MSForms.Control
 がまずいようです。

 実行時エラー '459'
 オブジェクトまたはクラスがこのイベントセットをサポートしていません

 というエラーが表示されます。

 全ての種類のコントロールに適用できるようにするにはどのように書き換えたらいいでしょうか?
 ご教示お願いいたします。

 なお、コントロールの種類ごとに別々のクラスを割り付けた場合はうまくいっています。

 'ユーザーフォーム

 Dim con() As New concls

 Private Sub UserForm_Initialize()
  Dim cnt As Integer
  Dim mycon As Object
   cnt = -1
   For Each mycon In Me.Controls
       cnt = cnt + 1
       ReDim Preserve con(0 To cnt)
       Set con(cnt).ctrl = mycon
   Next mycon
 End Sub

 'クラス/クラス名:concls

 Public WithEvents ctrl As MSForms.Control

 Private Sub ctrl_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
  MsgBox ctrl.Name
 End Sub

 なぜこんなことをしたいのか、と言いますと、↓のurlのユーザーフォーム上でドラッグしてユーザーフォームを移動する、という
 ことしたいのです。

 ユーザーフォーム自体および個別のコントロールのイベントに記述する方法ではうまくいってますが、全てのコントロールのイベント
 に記述するのが面倒だからです。
http://hatenachips.blog34.fc2.com/blog-entry-418.html

< 使用 Excel:Excel2007、使用 OS:WindowsVista >


 どうすれば、目的のテーマが達成できるか、おもしろそうなので、私も時間を見つけてチャレンジしてみたいと思いますが、それはさておき。

 たしかに、拒否されますね。種別を特定しないオブジェクトに対しては、WithEvents の規定が不可能という
 そういう仕様ではないでしょうか?

 WithEvents ではなく 通常のオブジェクトとして規定して、たとえば msg といったメソッドを

 Sub msg()
    MsgBox ctrl.Name
 End Sub

 として、ユーザーフォーム側から クラスオブジェクト.msg としてやりますと、そのオブジェクト名が表示されますので
 As MsForms.Control という記述そのものは、間違ってはいないのですがね。

 ところで、仮に、この Set が拒否されず、ちゃんと機能したとします。
 でも、このコントロールがサポートするイベント(VBE画面の左上のボックスで ctrlを選んだ時に右上のボックスに表示されるイベント)は

 AftrUpdate,BeforeUpdate,Enter,Exit のみですよね。なので、MouseMove は使いたくても使えないでしょうね。
 (その前に、Set 自体が拒否されるわけですが)

 一方、これまでクラスで、たとえばテキストボックスに対して通常のコード記述では BeforeUpdate 等のイベントを使いたくても使えず
 特別なコードを準備して対応していたわけですけど、つまり、クラスでのイベント処理には 通常の方法ではBeforeUpdate等は使えないと思っておりましたので
 今回、この右上のイベントに、これらがでてきて、びっくりしました。(でも Set できないので、いかんともしがたいですがね)

(β) 2015/08/21(金) 06:36


 メカニズム的には 出来ませんよね!! いくつかあるコントロールのイベントプロシジャーをどこかで
 記述しなければなりません。

 が、ユーザーフォームモジュール側でそれらしい記述を可能にすることはできます。
 再利用可能になるように作ってしまえば 、この様な事象が別で発生しても 簡単に実現できます。

 そのロジック自体は、何度か体験すれば 理解出来ると思いますが、
 それを作るのは結構大変です。 疑似からの脱却 を利用されたらよいと思います。

http://www.h3.dion.ne.jp/~sakatsu/Bpca_Common.htm#C2CP

  Bpca_Class_V20.zip

 利用方法や プロパティ、メソッドの使用方法は 上記サイトを参考にしてください。

 まずは、試してみてください。

(ichinose) 2015/08/21(金) 07:15


 直接の回答ではないのですが。

 おもしろそうなのでイベント処理ではなく、マウスカーソルの場所を追いかけながら、それがユーザーフォームのWindow領域内なら
 その動きの分、ユーザーフォームを移動させるコードを書いてみて試して、気が付いたんですが。

 現在のMouseMoveイベントでの処理コードが完成したとします。

 そうすると・・・

 マウスをユーザーフォームにもっていきます。最初は UserFormのMouseMoveに引っかかると思います。
 で、その瞬間に、ユーザーフォームをマウスの動きに合わせて「正しく移動」させると、マウスの位置は
 やはり他のコントロールではなく、最初にユーザーフォームにマウスが入った場所、つまり、「相対的に」みれば
 マウスポジションとユーザーフォームの位置関係は変わらない。

 つまり、どれだけマウスを動かそうが、せっかく他のコントロールに仕掛けた MouseMove は発生しないですよねぇ・・・

 動きとしては、ユーザーフォームの外側のどこかにマウスを当てて、ユーザーフォームを、そこから【押す】という感じではないでしょうかね。
 (しかも【ひっぱる】動きはできない?)

(β) 2015/08/21(金) 10:15


 βさん、ichinoseさん、ご回答ありがとうございます。

 ご紹介いただきました角田さんのHPからBpca_Class_V20.zipをダウンロード
 して展開してみました。

 標準モジュール/modBpcaConstの

 Public Const BPCA2_Except_MouseM As Long = BPCA2_All - BPCA2_MouseMove

 Public Const BPCA2_Except_MouseM As Long = BPCA2_All

 に変えることで
 ユーザーフォーム/frmBpca1の上半分の部分でコマンドボタンやテキストボックス、ラベルで
 MousMoveイベントが作動することが確認できました。

 コード全体を理解するのには時間がかかりそうですが勉強してみます。
(田吾作) 2015/08/21(金) 17:30

 全く意味のない遊びです。
 イベント処理は使わずマウスの場所をチェックしています。ただ、いわゆる MouseMoveの状態だと制御が
 (βには)荷が重いので、MouseDownの状態でのドラッグに反応させます。
 したがって、実際には、使えない代物ですね。(クリックイベント等はもちろん、実際のものには存在するでしょうから)

 まぁ、遊びということで。ユーザーフォームの任意の場所でMouseDownさせたまま移動させます。

 ユーザーフォームモジュール

 Private Sub UserForm_Activate()
    Application.OnTime Now(), "Moving"
 End Sub

 Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Fin
 End Sub

 標準モジュール

Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long
Declare Function GetCursorPos Lib "User32" (lpPoint As CurPT) As Long
Declare Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As Object, ByRef phwnd As Long) As Long
Declare Function GetWindowRect Lib "User32" (ByVal hWnd As Long, lpRect As RECT) As Long
Declare Function MoveWindow Lib "User32" (ByVal hWnd As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal bRepaint As Long) As Long
Type CurPT

    x As Long
    y As Long
End Type

Type RECT

      Left As Long
      Top As Long
      Right As Long
      Bottom As Long
End Type

Dim DoLoop As Boolean

Sub Moving()

    Dim rtn As Long
    Dim hWnd As Long
    Dim rw As RECT
    Dim cp As CurPT
    Dim newL As Long
    Dim newT As Long
    Dim exX As Long
    Dim exY As Long
    Dim flag As Boolean
    Dim w As Long
    Dim h As Long

    DoLoop = True
    WindowFromAccessibleObject UserForm1, hWnd

    Do While DoLoop
        rtn = GetAsyncKeyState(&H1) '左クリック
        If rtn <> 0 Then
            'フォームの領域取得
            GetWindowRect hWnd, rw
            '現在のマウス位置の取得
            GetCursorPos cp
            flag = False
            If cp.x >= rw.Left Then
                If cp.x <= rw.Right Then
                    If cp.y >= rw.Top Then
                        If cp.y <= rw.Bottom Then
                            flag = True
                            If exX = 0 And exY = 0 Then
                                exX = cp.x
                                exY = cp.y
                            Else
                                newL = rw.Left + cp.x - exX
                                newT = rw.Top + cp.y - exY
                                'フォームウィンドウの位置変更
                                MoveWindow hWnd, newL, newT, rw.Right - rw.Left, rw.Bottom - rw.Top, 1
                                exX = cp.x
                                exY = cp.y
                            End If
                        End If
                    End If
                End If
                If Not flag Then
                    exX = 0
                    exY = 0
                End If
            End If
        Else
            exX = 0
            exY = 0
        End If

        DoEvents
        Sleep 10
    Loop

End Sub

Sub Fin()

    DoLoop = False
End Sub

(β) 2015/08/21(金) 17:55


 ↑ 操作が窮屈になりますが、Shift + 右クリック でつまんで動かせば、いわゆるクリックイベントとのバッティングはなくなりました。
 もちろん、GetAsyncKeyState(&H1) のチェックの代わりに GetAsyncKeyState(&H2) と GetAsyncKeyState(&H10) のチェックを使いますが。

 追記 20:41 通常の左クリックでコマンドボタン等をつかんで移動させても、
             考えてみれば、コマンドボタン等のクリックイベントとバッティングするわけではなく
       フォーム自体は移動しても、マウスを離したときに、イベントが発生するので
       使えるかも。(実際にやってみて違和感はなかったので)

(β) 2015/08/21(金) 19:41


 まだよくテストしてませんが、βさんのコードに少し手を加えて
 コマンドボタンクリックでフラグの上げ下げをしてユーザーフォーム
 をドラッグ&ドロップで移動するのを制御するようにしてみました。

 myflgがTrueのときにユーザーフォームが移動でき、Falseのときはユーザーフォーム
 をドラッグ&ドロップでは移動しない、というようにしてみました。

 '標準モジュール
 ↓付加
 Public myflg As Boolean

 Sub Moving()

 MoveWindow hWnd, newL, newT, rw.Right - rw.Left, rw.Bottom - rw.Top, 1
 ↑を↓に変更
 If myflg <> False Then MoveWindow hWnd, newL, newT, rw.Right - rw.Left, rw.Bottom - rw.Top, 1

 'ユーザーフォーム
 Private Sub CommandButton1_Click()
  myflg = Not myflg 'クリックするごとにフラグを上げ下げ
 End Sub

 Private Sub UserForm_Initialize()
  myflg = True
 End Sub

(田吾作) 2015/08/21(金) 22:34


 ↑の私の変更したものではちょっと不具合が出たので下のように変えてみました。

 'ユーザーフォーム

 Private Sub CommandButton1_Click()
  Dim mycon As Object
  myflg = Not myflg
  Call conseigyo
 End Sub

 Private Sub UserForm_Initialize()
  myflg = True
  Call conseigyo
 End Sub

 Private Function conseigyo()
  Dim mycon As Object
  If myflg = False Then 'myflgがFalseだったらコントロールを使えないようにする
     For Each mycon In Me.Controls
      On Error Resume Next
      If mycon.Name <> Me.Controls("CommandButton" & 1).Name Then mycon.Enabled = False 'CommandButon1は常に使えるようにする
      On Error GoTo 0
     Next mycon
  Else 'myflgがTrueだったらコントロールを使えるようにする
     For Each mycon In Me.Controls
      On Error Resume Next
      mycon.Enabled = True
      On Error GoTo 0
     Next mycon
  End If
 End Function

 '標準モジュール

 If myflg <> False Then MoveWindow hWnd, newL, newT, rw.Right - rw.Left, rw.Bottom - rw.Top, 1
 ↑を↓に変更
 If myflg = False Then MoveWindow hWnd, newL, newT, rw.Right - rw.Left, rw.Bottom - rw.Top, 1
(田吾作) 2015/08/21(金) 22:59

 複数のユーザーフォームで使えるようにしてみました。

 'ユーザーフォーム

 'UserForm1

 Private Sub CommandButton1_Click()
  Dim mycon As Object
  myflguf = Not myflguf
  Call conseigyo(Me.Controls("CommandButton1"))
 End Sub

 Private Sub CommandButton2_Click()
  MsgBox "xx"
 End Sub

 Private Sub CommandButton3_Click()
  'Me.Hide
  Unload Me
  UserForm2.Show 0
 End Sub

 Private Sub UserForm_Activate()
  Application.OnTime Now(), "Moving"
  End Sub

 Private Sub UserForm_Initialize()
  Set myuf = Me
  myflguf = True
  Call conseigyo(Me.Controls("CommandButton1"))
 End Sub

 Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
  Fin
 End Sub

 'UserForm2

 Private Sub CommandButton1_Click()
  Dim mycon As Object
  myflguf = Not myflguf
  Call conseigyo(Me.Controls("CommandButton1"))
 End Sub

 Private Sub CommandButton2_Click()
  MsgBox "xx"
 End Sub

 Private Sub CommandButton3_Click()
  'Me.Hide
  Unload Me
  UserForm1.Show 0
 End Sub

 Private Sub UserForm_Activate()
  Application.OnTime Now(), "Moving"
  End Sub

 Private Sub UserForm_Initialize()
  Set myuf = Me
  myflguf = True
  Call conseigyo(Me.Controls("CommandButton1"))
 End Sub

 Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
  Fin
 End Sub

 '標準モジュール

 Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
 Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long
 Declare Function GetCursorPos Lib "User32" (lpPoint As CurPT) As Long
 Declare Function WindowFromAccessibleObject Lib "oleacc" (ByVal pacc As Object, ByRef phwnd As Long) As Long
  Declare Function GetWindowRect Lib "User32" (ByVal hWnd As Long, lpRect As RECT) As Long
  Declare Function MoveWindow Lib "User32" (ByVal hWnd As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal bRepaint As Long) As Long
  Type CurPT

     x As Long
     y As Long
 End Type

 Type RECT

       Left As Long
       Top As Long
       Right As Long
       Bottom As Long
 End Type

 Dim DoLoop As Boolean

 Public myflguf As Boolean
 Public myuf As UserForm

 Sub Moving()

     Dim rtn As Long
     Dim hWnd As Long
     Dim rw As RECT
     Dim cp As CurPT
     Dim newL As Long
     Dim newT As Long
     Dim exX As Long
     Dim exY As Long
     Dim flag As Boolean
     Dim w As Long
     Dim h As Long

     DoLoop = True
     WindowFromAccessibleObject myuf, hWnd

     Do While DoLoop
         rtn = GetAsyncKeyState(&H1) '左クリック
         If rtn <> 0 Then
             'フォームの領域取得
             GetWindowRect hWnd, rw
             '現在のマウス位置の取得
             GetCursorPos cp
             flag = False
             If cp.x >= rw.Left Then
                 If cp.x <= rw.Right Then
                     If cp.y >= rw.Top Then
                         If cp.y <= rw.Bottom Then
                             flag = True
                             If exX = 0 And exY = 0 Then
                                 exX = cp.x
                                 exY = cp.y
                             Else
                                 newL = rw.Left + cp.x - exX
                                 newT = rw.Top + cp.y - exY
                                 'フォームウィンドウの位置変更
                                 If myflguf = False Then MoveWindow hWnd, newL, newT, rw.Right - rw.Left, rw.Bottom - rw.Top, 1
                                 exX = cp.x
                                 exY = cp.y
                             End If
                         End If
                     End If
                 End If
                 If Not flag Then
                     exX = 0
                     exY = 0
                 End If
             End If
         Else
             exX = 0
             exY = 0
         End If

         DoEvents
         Sleep 10
     Loop

 End Sub

 Public Function conseigyo(ByVal cmd As Object)
  Dim mycon As Object
  If myflguf = False Then
     For Each mycon In myuf.Controls
      On Error Resume Next
      If mycon.Name <> cmd.Name Then mycon.Enabled = False
      On Error GoTo 0
     Next mycon
  Else
     For Each mycon In myuf.Controls
      On Error Resume Next
      mycon.Enabled = True
      On Error GoTo 0
     Next mycon
  End If
 End Function

 Sub Fin()
  DoLoop = False
 End Sub
(田吾作) 2015/08/22(土) 00:55

コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.