How to Move a Shape control with Mouse Events?
The Shape control in Microsoft.VisualBasic.PowerPacks provides several useful events, including MouseDown, MouseMove, MouseUp. Some useful actions you can work on a shape is to use the mouse to move or resize the shape. For example, you may want to drag an end point of a line and move the end point. You may also want to drag a side of a rectangle to resize it. Or you may just want to drag the whole shape to another position.
People tend to start with Shape’s MouseDown, MouseMove, MouseUp events but find out that it does not work well later: the mouse seems very slippery, or the drag stop in the halfway. This is because the shape control (or any control) has a limited region and will not fire the events when the mouse moves out of that region.
In the left picture, assume the small box is a RectangleShape control in a form, if you put mouse down at point 1 and then move the mouse along the line to the southeast, it will stop firing mouse events once it pass point 2.
One correct way to move a shape is to use the Shape’s MouseDown event to start the drag, and use its parent control to handle the MouseMove and MouseUp events. All shape controls in a form shares an invisible parent control called ShapeContainer. In the following code sample, I have a form contains an OvalShape1 and its parent control is ShapeContainer1. I added MouseDown handler to OvalShape and MouseMove and MouseUp to ShapeContainer1.
Public Class Form1
Dim dragging As Boolean = False
Dim oldShapePosition As Point
Dim mouseDownX As Integer 'Mouse position based on ShapeContainer
Dim mouseDownY As Integer
Private Sub OvalShape1_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) _
Handles OvalShape1.MouseDown
dragging = True
oldShapePosition = OvalShape1.Location
mouseDownX = e.X + OvalShape1.Location.X
mouseDownY = e.Y + OvalShape1.Location.Y
End Sub
Private Sub ShapeContainer1_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) _
Handles ShapeContainer1.MouseMove
If (dragging) Then
Dim deltaX As Integer = e.X - mouseDownX
Dim deltaY As Integer = e.Y - mouseDownY
Me.OvalShape1.Location = New Point(oldShapePosition.X + deltaX, oldShapePosition.Y + deltaY)
End If
End Sub
Private Sub ShapeContainer1_MouseUp(ByVal sender As Object, ByVal e As MouseEventArgs) _
Handles ShapeContainer1.MouseUp
dragging = False
End Sub
End Class
Because MouseEventArgs has the relative mouse position to the underline control, at mouse down, I need to record the mouse down position as below so it converts to the coordinates of the ShapeContainer1.
mouseDownX = e.X + OvalShape1.Location.X
mouseDownY = e.Y + OvalShape1.Location.Y
We can also let ShapeContainer to handle MouseDown event, this will relieve you from converting the mouse coordinate above. However, you will need to use the Shape.HitTest to tell if the mouse down is on a shape. The above sample for mouse down event handling can then be like this:
Private Sub ShapeContainer1_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) _
Handles ShapeContainer1.MouseDown
If (OvalShape1.HitTest(MousePosition.X, MousePosition.Y)) Then
dragging = True
oldShapePosition = Me.OvalShape1.Location
mouseDownX = e.X
mouseDownY = e.Y
End If
End Sub
The above code works fine for moving RectangleShape and OvalShape. To resize them, you will need some more work to hit test on the side and change the Size and/or Location.
Now let’s take a look at the LineShape, which is kind of special because it does not have the Location or Size properties. Instead, it has StartPoint and EndPoint properties. To move the whole line, we can move both StatPoint and EndPoint. To drag only one end, we just move either StartPoint or EndPoint. To test mouse down near an end, I add this help function:
Private Function MouseIsNearBy(ByVal testPoint As Point) As Boolean
testPoint = Me.PointToScreen(testPoint)
Return Math.Abs(testPoint.X - MousePosition.X) <= HitTestDelta _
AndAlso Math.Abs(testPoint.Y - MousePosition.Y) <= HitTestDelta
End Function
Here is the complete code that works for a Form with the LineShape control called lineShape1 and its parent container control called ShapeContainer1.
Public Class Form1
Const HitTestDelta As Integer = 3
' The mouse position when mouse down
Dim oldMouseX As Integer
Dim oldMouseY As Integer
' The line position when mouse down.
Dim oldStartPoint As Point
Dim oldEndPoint As Point
Dim dragStartPoint As Boolean = False
Dim dragEndPoint As Boolean = False
Private Sub ShapeContainer1_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) _
Handles ShapeContainer1.MouseDown
If (LineShape1.HitTest(MousePosition.X, MousePosition.Y)) Then
oldMouseX = e.X
oldMouseY = e.Y
oldStartPoint = LineShape1.StartPoint
oldEndPoint = LineShape1.EndPoint
dragStartPoint = MouseIsNearBy(oldStartPoint)
dragEndPoint = MouseIsNearBy(oldEndPoint)
If (Not dragStartPoint AndAlso Not dragEndPoint) Then
'If not drag either end, then drag both.
dragStartPoint = True
dragEndPoint = True
End If
End If
End Sub
Private Sub ShapeContainer1_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) _
Handles ShapeContainer1.MouseMove
If (dragStartPoint) Then
LineShape1.StartPoint = New Point(oldStartPoint.X + e.X - oldMouseX, _
oldStartPoint.Y + e.Y - oldMouseY)
End If
If (dragEndPoint) Then
LineShape1.EndPoint = New Point(oldEndPoint.X + e.X - oldMouseX, _
oldEndPoint.Y + e.Y - oldMouseY)
End If
End Sub
Private Sub ShapeContainer1_MouseUp(ByVal sender As Object, ByVal e As MouseEventArgs) _
Handles ShapeContainer1.MouseUp
dragStartPoint = False
dragEndPoint = False
End Sub
Private Function MouseIsNearBy(ByVal testPoint As Point) As Boolean
testPoint = Me.PointToScreen(testPoint)
Return Math.Abs(testPoint.X - MousePosition.X) <= HitTestDelta _
AndAlso Math.Abs(testPoint.Y - MousePosition.Y) <= HitTestDelta
End Function
End Class
The blog is meant to answer these MSDN forum questions:
Thanks!
Comments
- Anonymous
January 08, 2014
Hi, Iam pulling records from a table called person, which has fields like person_picture,person_name,Person_telephone and person_address. Now i want to display all these records using datarepeteater in a way like where in datarepeater it should show only first 4 rows from table person at top and then goes 4 rows down and so on. I mean row= picture,name telephone,address So it should show like this below . row1 row2 row3 row4 row5 rows6 rows7 row8 row9 row10 row11 row12 row13 rows14 rows15 row16 Please suggest urgently how can i achieve this. Iam doing it on windows application. If its not possible by datarepeater, Please suggest some other way. Please help at the earliest