作成予定のボタンの動作で、MouseDownなどができないか

タグの編集
投稿者 kojiro  (社会人) 投稿日時 2020/3/30 20:53:46
いつもお世話になっております。いつも問だけですみません。
今回は無理かもしれません。以下
Imports System.Windows.Forms
Imports Microsoft.Win32
Imports System.Drawing
Imports System.Drawing.Imaging
Public Class Form1
    Private testButtons() As System.Windows.Forms.Button
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        'ボタンコントロール配列の作成(ここでは5つ作成)
        Me.testButtons = New System.Windows.Forms.Button(4) {}

        'ボタンコントロールのインスタンス作成し、プロパティを設定する
        Me.SuspendLayout()
        Dim i As Integer
        For i = 0 To Me.testButtons.Length - 1
            'インスタンス作成
            Me.testButtons(i) = New System.Windows.Forms.Button
            'プロパティ設定
            Me.testButtons(i).Name = "Button" + i.ToString()
            Me.testButtons(i).Text = i.ToString()
            Me.testButtons(i).Size = New Size(30, 30)
            Me.testButtons(i).Location = New Point(i * 30, 10)
            'イベントハンドラに関連付け
            AddHandler Me.testButtons(i).Click, AddressOf Me.testButtons_Click
        Next i

        'フォームにコントロールを追加
        Me.Controls.AddRange(Me.testButtons)
        Me.ResumeLayout(False)
    End Sub
    Private Sub testButtons_Click(ByVal sender As Object, _
        ByVal e As EventArgs)
        'クリックされたボタンのNameを表示する
        MessageBox.Show(CType(sender, System.Windows.Forms.Button).Name)
    End Sub
End Class
などで、クリック時の動作は、できますが、この作成するボタンのMouseDownやMouseUpの動作を、定義できますでしょうか?
投稿者 るきお  (社会人) 投稿日時 2020/3/30 21:22:13
そこまでできているのなら、Clickと同じようにMouseDownとMouseUpもできますよ。
Imports System.Windows.Forms
Imports Microsoft.Win32
Imports System.Drawing
Imports System.Drawing.Imaging
Public Class Form1
    Private testButtons() As System.Windows.Forms.Button
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        'ボタンコントロール配列の作成(ここでは5つ作成) 
        Me.testButtons = New System.Windows.Forms.Button(4) {}

        'ボタンコントロールのインスタンス作成し、プロパティを設定する 
        Me.SuspendLayout()
        Dim i As Integer
        For i = 0 To Me.testButtons.Length - 1
            'インスタンス作成 
            Me.testButtons(i) = New System.Windows.Forms.Button
            'プロパティ設定 
            Me.testButtons(i).Name = "Button" + i.ToString()
            Me.testButtons(i).Text = i.ToString()
            Me.testButtons(i).Size = New Size(30, 30)
            Me.testButtons(i).Location = New Point(i * 30, 10)
            'イベントハンドラに関連付け 
            AddHandler Me.testButtons(i).Click, AddressOf Me.testButtons_Click
            AddHandler Me.testButtons(i).MouseDown, AddressOf Me.testButtons_MouseDown
            AddHandler Me.testButtons(i).MouseUp, AddressOf Me.testButtons_MouseUp
        Next i

        'フォームにコントロールを追加 
        Me.Controls.AddRange(Me.testButtons)
        Me.ResumeLayout(False)
    End Sub
    Private Sub testButtons_Click(ByVal sender As Object,
        ByVal e As EventArgs)
        'クリックされたボタンのNameを表示する 
        MessageBox.Show(CType(sender, System.Windows.Forms.Button).Name)
    End Sub

    Private Sub testButtons_MouseDown(sender As Object, e As MouseEventArgs)
        DirectCast(sender, Button).BackColor = Color.Red
    End Sub

    Private Sub testButtons_MouseUp(sender As Object, e As MouseEventArgs)
        DirectCast(sender, Button).BackColor = Color.Blue
    End Sub
End Class


直ぐに試せるサンプル付きだったので、簡単に回答できました。
投稿者 (削除されました)  () 投稿日時 2020/3/30 22:10:20
(削除されました)
投稿者 kojiro  (社会人) 投稿日時 2020/3/30 22:21:01
えーと、うごきますね。
ここで、ボタン1をマウスダウンしたとき、ボタン1が赤く染まり、そこから、ボタン2の場所で、マウスアップしたときには、ボタン1の色が青くないrます。これをボタン2を青くしたいのですが・・できるでしょうかw
投稿者 るきお  (社会人) 投稿日時 2020/3/31 08:44:37
それでは、testButtons_MouseUp をたとえば次のように修正するとよいと思います。

Private Sub testButtons_MouseUp(sender As Object, e As MouseEventArgs)

    'イベントを発生させたボタンを取得します。 
    'これはこの瞬間のマウスの座標とは関係なくMouseDownを発生させたボタンを同じです。 
    Dim eventSourceControl As Button = DirectCast(sender, Button)

    'この瞬間のマウスの位置の下にあるコントロールを取得します。 
    Dim xInParent As Integer = e.Location.X + eventSourceControl.Location.X
    Dim yInParent As Integer = e.Location.Y + eventSourceControl.Location.Y
    Dim locationInParent As New Point(xInParent, yInParent)
    Dim controlMouseOn As Control = Me.GetChildAtPoint(locationInParent)

    'マウスの下に何かコントロールがあるのであれば、それの背景色を変更します。 
    If controlMouseOn IsNot Nothing Then
        controlMouseOn.BackColor = Color.Blue
    End If

End Sub


MouseUpイベントはそのときのマウスの位置とは関係がなく、MouseDownイベントを発生させたボタンと同じボタンで発生します。
MouseUpイベントでのsenderはMouseDownイベントでのsenderと同じコントロールを指します。

一方、今回 kojiro さんは、そうではなく、マウスアップした瞬間にマウスの下にあったボタンの色を青くしたいということなので、senderではなく、マウスの座標をもとにその座標にあるコントロールを取得する必要があります。これにはGetChildAdPointメソッドを使用します。

イベントの引数(e.Location)が表す座標は、イベントを発生させたボタンの左上が(0,0)という座標系ですので、フォームの座標系に変換するためにそのボタンの座標をたし算をしています。

この構造にすると、コントロールがどのような階層構造で配置されているかが重要になります。この例ではフォームに直接配置されているのでフォームのGetChildAtPointメソッドを使っていますが、PanelやPictureBoxなどフォームでないものに配置しているのであれば、変える必要がありますし、汎用で作るのは少し面倒になります。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2020/3/31 11:30:37
> 変える必要がありますし、汎用で作るのは少し面倒になります。
Cursor.Position を使えば、現在のカーソル位置をスクリーン座標で得ることができます。

> Dim eventSourceControl As Button = DirectCast(sender, Button)
親コントロールは Parent プロパティ、親フォームは FindForm メソッドで得られるので、
それぞれのクライアント座標に変換するために、こういう方法が使えるかもしれません。

Dim posScreen = Cursor.Position
Dim posSelf = eventSourceControl.PointToClient(posScreen)
Dim posContainer = eventSourceControl.Parent.PointToClient(posScreen)
Dim posForm = eventSourceControl.FindForm().PointToClient(posScreen)



> MouseUpイベントはそのときのマウスの位置とは関係がなく、MouseDownイベントを発生させたボタンと同じボタンで発生します。

たとえば Label を MouseDown した後、Button の上で MouseUp したとしても、
Button の MouseUp イベントは発生しません。
(ドラッグ処理を実装するような場合は、むしろその方が扱いやすい)

しかし、どこで MouseDown されたとしても、常に MouseUp した場所の座標を
捕らえたいような場合には、かわりに RawInput を使うこともできます。
https://www.nuget.org/packages/SharpDX.RawInput/

Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        SharpDX.RawInput.Device.RegisterDevice(
            SharpDX.Multimedia.UsagePage.Generic,
            SharpDX.Multimedia.UsageId.GenericMouse,
            SharpDX.RawInput.DeviceFlags.None)
        AddHandler SharpDX.RawInput.Device.MouseInput, AddressOf RawInput_MouseInput
    End Sub

    Private Sub RawInput_MouseInput(sender As Object, e As SharpDX.RawInput.MouseInputEventArgs)
        Dim isMouseDown = e.ButtonFlags.HasFlag(SharpDX.RawInput.MouseButtonFlags.LeftButtonDown)
        Dim isMouseUp = e.ButtonFlags.HasFlag(SharpDX.RawInput.MouseButtonFlags.LeftButtonUp)

        '左マウスボタンが操作されていないので、何もしない 
        If Not (isMouseDown OrElse isMouseUp) Then Return

        '現在のマウス位置のコントロールを調べる 
        Dim child = HitTest(Me, MousePosition)

        'それが Button でない場合は何もしない 
        If TypeOf child IsNot Button Then Return

        'MouseDown 箇所のボタンを赤く染める 
        If isMouseDown Then child.BackColor = Color.Red

        'MouseUp 箇所のボタンを青く染める 
        If isMouseUp Then child.BackColor = Color.Blue
    End Sub

    Private Shared Function HitTest(root As Control, screenPos As Point) As Control
        Dim current = root
        Dim last As Control = Nothing
        Do Until current Is Nothing
            last = current
            current = current.GetChildAtPoint(current.PointToClient(screenPos))
        Loop
        Return last
    End Function

End Class
投稿者 kojiro  (社会人) 投稿日時 2020/3/31 14:39:47
るきおさんの回答で、完全に動作しました。驚きました。魔界さんのおっしゃる点は、確認しておりませんん。ごめんなさい。以下のように、tabを作成し、その中にボタンを配置して、同様に記述してみました。
Imports System.Windows.Forms
Imports Microsoft.Win32
Imports System.Drawing
Imports System.Drawing.Imaging
Public Class Form1
    Dim button As System.Windows.Forms.Button
    Dim btnar As New List(Of Button)
    Dim tb As New System.Windows.Forms.TabControl
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim tb As New System.Windows.Forms.TabControl
        Dim i As Integer
        With tb
            .Name = "TabControl1"
            .Left = 20 '470
            .Top = 20
            .Width = 250
            .Height = 220
            .Visible = True
        End With
        Controls.Add(tb)
        For i = 0 To 2
            Dim tab As New TabPage
            tab.UseVisualStyleBackColor = True '色が白くなります
            tab.Text = CStr(i)  ' Cell_x2)   '"tab" & CStr(i + 1) 'TabPageの表題です
            tab.Name = "tabpage" & CStr(i + 1) 'TabPageに名前を付けます
            'tb.Add(tab) 'TabPageをリストにします
            Me.Controls("TabControl1").Controls.Add(tab) 'TabPageをコントロールにAddします
        Next

        Dim str_c As String
        For i = 0 To 2
            Dim tab_name As String = "tabpage" & CStr(i + 1)
            For k = 1 To 3    '************* 7
                '------------------------------------------------------
                button = New System.Windows.Forms.Button()
                str_c = CStr(i) & CStr(k)
                button.Name = str_c
                button.Text = str_c
                button.Location = New Point(20 + 70 * (k - 1), 50)
                button.Size = New System.Drawing.Size(70, 20)
                btnar.Add(button)
                tb.Controls(tab_name).Controls.Add(button)
                AddHandler button.Click, AddressOf Me.Button_Click 'system_SessionEnding  'EventHandler
                AddHandler button.MouseUp, AddressOf Button_MouseUp 'system_SessionEnding  'EventHandler
                AddHandler button.MouseDown, AddressOf Button_MouseDown 'system_SessionEnding  'EventHandler

            Next
        Next
    End Sub
    Private Sub Button_Click(sender As Object, e As EventArgs)
        'クリックされたボタンのNameを表示する
        MessageBox.Show(CType(sender, System.Windows.Forms.Button).Name)
    End Sub
    Private Sub Button_MouseUp(sender As Object, e As MouseEventArgs)
        'イベントを発生させたボタンを取得します。 
        'これはこの瞬間のマウスの座標とは関係なくMouseDownを発生させたボタンを同じです。 
        Dim eventSourceControl As Button = DirectCast(sender, Button)

        'この瞬間のマウスの位置の下にあるコントロールを取得します。 
        Dim xInParent As Integer = e.Location.X + eventSourceControl.Location.X
        Dim yInParent As Integer = e.Location.Y + eventSourceControl.Location.Y
        Dim locationInParent As New Point(xInParent, yInParent)
        Dim controlMouseOn As Control = Me.GetChildAtPoint(locationInParent)
        'マウスの下に何かコントロールがあるのであれば、それの背景色を変更します。 
        If controlMouseOn IsNot Nothing Then
            controlMouseOn.BackColor = Color.Blue
        End If
    End Sub

    Private Sub Button_MouseDown(sender As Object, e As MouseEventArgs)
        DirectCast(sender, Button).BackColor = Color.Red
    End Sub
End Class

MouseDownで赤くはなるのですが、MouseUpで青くはなりません。おっしゃるように、 Me.GetChildAtPoint(locationInParent)が動いていないのかもしれません。
投稿者 (削除されました)  () 投稿日時 2020/4/1 14:53:34
(削除されました)
投稿者 kojiro  (社会人) 投稿日時 2020/4/1 15:33:28
上記のコードで、
 MsgBox(e.Location.X)
        MsgBox(e.Location.Y)
        MsgBox(eventSourceControl.Location.X)
        MsgBox(eventSourceControl.Location.Y)
で、同じボタンでUpしても
e.Location.X=35
e.Location.Y=8
eventSourceControl.Location.X=20
eventSourceControl.Location.Y=50
で、おかしいですね。20、50は、最初に作られたButtonの位置になります。
buttonが配列になっていないからか・・
tabはあきらめようと思います。るきおさん・・大変ありがとうございます。ボタンに登録したデータを右クリックのドロップダウンのような動作で、移動できるのかな、と思っています。
魔界さんのおっしゃるSharpDX.RawInputを、.NETに追加する方法を、今後の参考に、教えて頂けたら、幸いです。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2020/4/1 15:57:09
マウス・キーボード・フォーカス遷移系のイベントデータを確認する際に、MsgBox を使用することは避けてください。
イベントの流れが阻害されてしまうため、連続して実行した場合とで、動作が変わってしまうことがあります。

代わりに、Debug.WriteLine を使うなどして確認頂くことをお奨めします。


> 20、50は、最初に作られたButtonの位置になります。
るきおさんの説明にもありましたが、MouseUpイベントはそのときのマウスの位置とは関係がなく、
MouseDown イベントを発生させたボタンと同じボタンで発生するからです。

つまり、MouseUp イベントの sender は、現在のマウス座標に関係なく、
MouseDown が発生した時の sender と同じである、ということです。

Private Sub Buttons_MouseUp(sender As Object, e As MouseEventArgs) Handles Button1.MouseUp, Button2.MouseUp, Button3.MouseUp
  Label1.Text = CStr(Button1.Capture)
  Label2.Text = CStr(Button2.Capture)
  Label3.Text = CStr(Button3.Capture)
End Sub


上記を試すと、マウスボタンを離した時の座標がどこにあったとしても、
Capture が True になるのは、MouseDown された時のボタンであることが分かるかと思います。


> SharpDX.RawInputを、.NETに追加する方法を、今後の参考に、教えて頂けたら、幸いです。

お使いの Visual Studio のバージョンにもよりますが、よほど古い物でなければ、
[ツール]-[NuGet パッケージ マネージャー] というメニュー項目があると思います。

そこから NuGet パッケージの管理画面を起動し、
パッケージ ソースに "nuget.org" を選択した上で、
「参照」タブに切り替えて検索ボックスに「SharpDX.RawInput」と入力して、
プロジェクトにインストールしてみてください。