1

I'm making a Battleship game, and I'm using buttons as the grid(playerboard). I'm using a picture box as the ship, and I'm trying to make it so that the picturebox snaps to the buttons that it is colliding with.

I have already done the drag and drop, and collision part, but I'm struggling with the snapping to buttons part. The picture box is the size of two buttons. I tried to align the picturebox with the buttons, by using picturebox.left = button.left, but it chooses the wrong button of the two.

Dim Off As Point
Private Sub picDestroyer_MouseDown(sender As Object, e As MouseEventArgs) Handles picDestroyer.MouseDown
    Off.X = MousePosition.X - sender.Left 'Click and Drag ship
    Off.Y = MousePosition.Y - sender.Top
End Sub

Private Sub picDestroyer_MouseMove(sender As Object, e As MouseEventArgs) Handles picDestroyer.MouseMove
    If e.Button = MouseButtons.Left Then
        sender.Left = MousePosition.X - Off.X 'Click and Drag ship
        sender.Top = MousePosition.Y - Off.Y
    End If
End Sub
Private Sub picDestroyer_DoubleClick(sender As Object, e As EventArgs) Handles picDestroyer.DoubleClick
    If picDestroyer.Size = New Size(52, 21) Then 'Rotate Pic if double clicked
        picDestroyer.Size = New Size(21, 52)
    ElseIf picDestroyer.Size = New Size(21, 52) Then
        picDestroyer.Size = New Size(52, 21)
    End If
End Sub
Private Sub picDestroyer_MouseLeave(sender As Object, e As EventArgs) Handles picDestroyer.MouseLeave

    For Each button In Me.Controls
        If picDestroyer.Bounds.IntersectsWith(button.bounds) Then
            button.backcolor = Color.Red
            picDestroyer.BackColor = SystemColors.Control
        End If
        If picDestroyer.Bounds.IntersectsWith(button.bounds) And picDestroyer.Size = New Size(52, 21) Then
            picDestroyer.Top = button.top
        End If
        If picDestroyer.Bounds.IntersectsWith(button.bounds) And picDestroyer.Size = New Size(21, 52) Then
            picDestroyer.Left = button.left
        End If
    Next
Erika
  • 13
  • 3

1 Answers1

0

To get the button which you want to snap to you can temporarily disable the PictureBox and call GetChildAtPoint() on the form to get the child control at the location of the PictureBox, specifying GetChildAtPointSkip.Disabled as the second parameter so that the search won't include your disabled PictureBox but all the other controls above/below it.

After that you can just set the PictureBox's coordinates to that of the button.

There are also a few improvements that can be made to your code:

  • You can use the MouseUp event instead of MouseLeave to update the picture box's position immediately after you drop it.

  • Set both the X- and Y-coordinates for the picture box when snapping (not just one of them, Left = X, Top = Y) so that it is snapped correctly in the top-left corner of the button.

  • In the loop iterate through Me.Controls.OfType(Of Button)() instead of just Me.Controls, since OfType(Of TResult) will get only the controls of a certain type (in this case Buttons). Iterating through only Me.Controls will for example include the PictureBox itself which could cause issues in the future (perhaps that was the reason you set its BackColor to Control every time?).

All that said, this code should work:

If e.Button = Windows.Forms.MouseButtons.Left Then

    picDestroyer.Enabled = False 'Temporarily disable the picture box so that it isn't included in the child search.
    Dim ChildBelowDestroyer As Control = Me.GetChildAtPoint(picDestroyer.Location, GetChildAtPointSkip.Disabled) 'Look for any child control that isn't disabled.
    picDestroyer.Enabled = True 'Re-enable the picture box.

    If ChildBelowDestroyer Is Nothing OrElse _
        ChildBelowDestroyer.GetType() IsNot GetType(Button) Then
        Return 'Do not continue if no child was found below the picture box, or if the child is not a button.
    End If

    picDestroyer.Location = ChildBelowDestroyer.Location 'Snap the picture box to the button (sets the X- and Y-coordinates).

    For Each button In Me.Controls.OfType(Of Button)() 'OfType() to only look for buttons.
        If picDestroyer.Bounds.IntersectsWith(button.Bounds) Then
            button.BackColor = Color.Red
            picDestroyer.BackColor = SystemColors.Control
        End If
    Next

End If

Another thing to keep in mind is to use AndAlso instead of And, and OrElse instead of Or in If-statements since AndAlso and OrElse are short-circuited.

Community
  • 1
  • 1
Visual Vincent
  • 18,045
  • 5
  • 28
  • 75
  • After adding the Exit For into the if statement, the button backcolor nolonger changes to red. Also the picDestroyer.top = button.top and picDestroyer.Left = button.left no longer works. I also combined the if statements, and my code looks identical to what you provided. I appreciate the help – Erika Jan 24 '17 at 00:46
  • @Erika : Hmm, that's strange... I'll have to try the code out then. – Visual Vincent Jan 24 '17 at 07:51
  • @Erika : I have updated the entire answer with new code and some explanations. Hope it'll be what you need! – Visual Vincent Jan 24 '17 at 14:03
  • Thank you so much for the help! When I tried `If e.Button = Windows.Forms.MouseButtons.Left Then`, `e.button` was underlined saying "e.button is not a member of "system.eventargs". I got rid of the if statement completely, and it still seemed to work. Thank you for the help, I greatly appreciate it! – Erika Jan 25 '17 at 00:10
  • @Erika : Glad I could help! -- For `e.Button` to work you must have a proper event declaration. The best way to get one is to remove any existing handlers and then select the event from the event box in Visual Studio. A proper declaration for `MouseUp` would be: `Private Sub picDestroyer_MouseUp(sender As Object, e As MouseEventArgs) Handles picDestroyer.MouseUp` - note the _"e As **MouseEventArgs**"_. – Visual Vincent Jan 25 '17 at 10:06