Solved

Delete LineShape from screen, LineShape array, and Shapecontainer array

Posted on 2012-12-28
13
1,306 Views
Last Modified: 2013-01-05
I am adding a LineShape object to a Windows form using the following code, but I can't seem to delete it from the screen when I want to remove it.   Also, I can't remove the lineshape from the mLineShape and MShapeContainer arrays without leaving a Nothing in its array element, and without copy all remaining lineshapes into the lower array elements.  

If I know the correct index and .name of the new LineShape object, how can I remove it from being visible in the Form?


     ' declare ShapeContainer counter
        Dim siSCCount As Integer
        If IsNothing(mLineShapes) Then
            siSCCount = 0
        Else
            siSCCount = mLineShapes.Length
        End If

        ' create new ShapeContainer
        Dim sSCTemp As New ShapeContainer

        ' add ShapeContainer to Form
        sSCTemp.Parent = Me

        ' create new LineShape
        Dim sLSTemp As New LineShape
        sLSTemp.BorderColor = Color.Black
        sLSTemp.BorderWidth = 2
        sLSTemp.Cursor = Cursors.Cross
        ' add LineShape to ShapeContainer
        sLSTemp.Parent = sSCTemp

        ' set starting and ending coordinates for the line
        sLSTemp.StartPoint = New System.Drawing.Point(x, y)
        sLSTemp.EndPoint = New System.Drawing.Point(x + 80, y - 5)
        'sLSTemp.StartPoint = New System.Drawing.Point(siSCCount * 20, 60 + siSCCount * 60)
        'sLSTemp.EndPoint = New System.Drawing.Point(100 + siSCCount * 20, 110 + siSCCount * 60)

        ' set new LineShape to top of z-order
        sLSTemp.BringToFront()
        sSCTemp.BringToFront()

        ' connect ContextMenuStrip to LineShape
        sLSTemp.ContextMenuStrip = mLsCtm1

        ' add new LineShape to arrays
        ReDim Preserve mLineShapes(siSCCount)
        ReDim Preserve mShapeContainer(siSCCount)

        mLineShapes(siSCCount) = sLSTemp
        mLineShapes(siSCCount).Name = "LineShape" & siSCCount
        mShapeContainer(siSCCount) = sSCTemp
        mShapeContainer(siSCCount).Name = "ShapeContainer" & siSCCount

Open in new window

0
Comment
Question by:lep1
  • 7
  • 6
13 Comments
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
Instead of Arrays, use generic Lists.

Why are you creating a new ShapeContainer each time a line is created.  Shouldn't you be using the same, ONE, ShapeContainer and adding your new lines to that ShapeContainer?
0
 

Author Comment

by:lep1
Comment Utility
Only a temp shape container and a temp lineshape are generated each time a new line is created.  These temp line and shapecontainer are then added to the permanent arrays.  See the last six lines of code, especially the Redim Preserve lines.

What would be used to replace the Redim Preserve lines if a list was used, and how would list elements and the visible lineshape on the Form be removed?
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
I don't think you should be using multiple ShapeContainers.  Just have one ShapeContainer and add all the lines to that one ShapeContainer.

Here's a simple example of a List() that holds strings:

    Dim values As New List(Of String)

    values.Add("Hello ")
    values.Add("World!")

It grows automatically when you add items.  Later, when you remove an item, the "hole" will be automatically filled for you since items are shifted to fill the gap.

See "List Generic Class":
http://msdn.microsoft.com/en-us/library/6sh2ey19(v=vs.80).aspx
0
 

Author Comment

by:lep1
Comment Utility
Does the ShapeContainer need to be an array?   When I globally declare:

Dim mySC as New ShapeContainer

and then look at mySC in a subroutine, it is equal to nothing.
0
 
LVL 85

Accepted Solution

by:
Mike Tomlinson earned 500 total points
Comment Utility
No.  In this example, multiple dynamic Lines are all added to the SAME ShapeContainer:
Imports Microsoft.VisualBasic.PowerPacks
Public Class Form1

    Private SC As New ShapeContainer
    Private Lines As New List(Of LineShape)

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Me.Controls.Add(SC)
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Static R As New Random

        Dim LS As New LineShape
        LS.StartPoint = New Point(R.Next(SC.Width), R.Next(SC.Height))
        LS.EndPoint = New Point(R.Next(SC.Width), R.Next(SC.Height))
        Lines.Add(LS)
        SC.Shapes.Add(LS)
    End Sub

End Class

Open in new window

0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
Screenshot:
One ShapeContainer, Many LineShapes
0
What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 

Author Comment

by:lep1
Comment Utility
Great, that works.   However, now I need to identify which LS the cursor position is near, and would need to search through the SC to find it.   Would this use the GetChildAtPoint feature of a shape container?

Below is how I first determine which line the mouse point is near to modify the endpoints, and then also use a mouse down and mouseup command.

    Public Sub ShapeContainerMouseMoveEventHandler(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
           Dim myLS as LineShape
        
           myLS = ? 'How can I find the SC clicked on, then set the LS clicked on to myLS so the endpoints can be moved?

           If MouseIsNearBy(myLS.EndPoint) Then
                myLS.BorderColor = Color.Red
                NearLineEndPoint = True
            End If
            If MouseIsNearBy(myLS.EndPoint) = False Then
                myLS.BorderColor = Color.Black
                NearLineEndPoint = False
            End If
            If (dragStartPoint) Then
                myLS.StartPoint = New Point(oldStartPoint.X + e.X - oldMouseX, oldStartPoint.Y + e.Y - oldMouseY)
            End If
            If (dragEndPoint) Then
                myLS.EndPoint = New Point(oldEndPoint.X + e.X - oldMouseX, oldEndPoint.Y + e.Y - oldMouseY)
            End If
            
            myLS.Invalidate()
 
      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
  

Open in new window

0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
Here's an example of allowing the user to drag the endpoints of the lines around with the mouse.

*Note that SC_MouseDown() and SC_MouseMove() are handling events from both the FORM and the SHAPECONTAINER.  This may need modification if the ShapeContainer is contained by something else such as a Panel:
Imports Microsoft.VisualBasic.PowerPacks
Public Class Form1

    Public Enum LineEnd
        StartPoint
        EndPoint
    End Enum

    Private DragEnd As LineEnd
    Private HitTestDelta As Integer = 10
    Private Lines As New List(Of LineShape)
    Private WithEvents SC As New ShapeContainer
    Private CurLineShape As LineShape = Nothing

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        SC.Dock = DockStyle.Fill
        Me.Controls.Add(SC)
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Static R As New Random

        Dim LS As New LineShape
        LS.StartPoint = New Point(R.Next(SC.Width), R.Next(SC.Height))
        LS.EndPoint = New Point(R.Next(SC.Width), R.Next(SC.Height))
        LS.BorderStyle = Drawing2D.DashStyle.Solid
        Lines.Add(LS)
        SC.Shapes.Add(LS)
    End Sub

    Private Sub SC_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown, SC.MouseDown
        If e.Button = Windows.Forms.MouseButtons.Left Then
            If Not IsNothing(CurLineShape) Then
                Cursor.Position = SC.PointToScreen(IIf(DragEnd = LineEnd.StartPoint, CurLineShape.StartPoint, CurLineShape.EndPoint))
            End If
        End If
    End Sub

    Public Sub SC_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove, SC.MouseMove
        If e.Button = Windows.Forms.MouseButtons.None Then
            Dim pt As Point = SC.PointToClient(Cursor.Position)

            ' See if we are over a Line Endpoint:
            Dim myLS As LineShape = Nothing
            For Each LS As LineShape In Lines
                If PointToPointDist(pt, LS.StartPoint) <= HitTestDelta Then
                    myLS = LS
                    DragEnd = LineEnd.StartPoint
                    Exit For
                ElseIf PointToPointDist(pt, LS.EndPoint) <= HitTestDelta Then
                    myLS = LS
                    DragEnd = LineEnd.EndPoint
                    Exit For
                End If
            Next

            If Not IsNothing(myLS) Then ' If we are over a Line Endpoint...
                ' Change the Color of the Previous Line back to Black:
                If Not IsNothing(CurLineShape) AndAlso Not myLS Is CurLineShape Then
                    CurLineShape.BorderColor = Color.Black
                End If

                ' Change Color of the New Line to Red:
                CurLineShape = myLS
                CurLineShape.BorderColor = Color.Red
            Else ' We are NOT over a Line Endpoint ...
                ' Change the Color of the Previous Line back to Black, and reset the "CurLineShape" variable:
                If Not IsNothing(CurLineShape) Then
                    CurLineShape.BorderColor = Color.Black
                    CurLineShape = Nothing
                End If
            End If
        ElseIf e.Button = Windows.Forms.MouseButtons.Left Then
            If Not IsNothing(CurLineShape) Then
                Dim pt As Point = SC.PointToClient(Cursor.Position)
                If DragEnd = LineEnd.StartPoint Then
                    CurLineShape.StartPoint = pt
                Else
                    CurLineShape.EndPoint = pt
                End If
            End If
        End If
    End Sub

    Public Shared Function PointToPointDist(ByVal ptA As Point, ByVal ptB As Point) As Single
        Return PointToPointDist(ptA.X, ptA.Y, ptB.X, ptB.Y)
    End Function

    Public Shared Function PointToPointDist(ByVal Ax As Single, ByVal Ay As Single, ByVal Bx As Single, ByVal By As Single) As Single
        ' PointToPointDist = SquareRoot((Bx - Ax)^2 + (By - Ay)^2)
        Return Math.Sqrt((Bx - Ax) * (Bx - Ax) + (By - Ay) * (By - Ay))
    End Function

End Class

Open in new window

1
 

Author Closing Comment

by:lep1
Comment Utility
This was a great solution -- and was the method I chose
0
 

Author Comment

by:lep1
Comment Utility
Idle_Mind, this is great code.   What does the SC.Dock = DockStyle.Fill do in the form load?   Looks like you also use Euclidean distance to determine if the mouse pointer is close to the lineshape endpoint -- which throws a little math on the subject and is very precise.  

It worked out that I declared a global Dim SelectedLS as LineShape and I don't ever use the Lines list, but rather loop through the LS using e.g.

        For Each ls As LineShape In Me.SC.Shapes
            If MouseIsNearBy(ls.EndPoint) = True Then
                SelectedLS = ls
                SelectedLS.BorderColor = Color.Red
                NearLineEndPoint = True
            End If
        Next

Open in new window


If the mouse cursor points are near an endpoint then I move the SelectedLS.endpoint.X and .Y

Last, the lineshapes are kind of "jittery" and look jumpy when they are dragged.  Is there a way to calm this down?  Thx in advance.  

p.s. I have seen some code that allows a Bezier type line to be dragged from an icon, and when dropped on the final icon retains it's curved look.   I'll probably look into that to get away from the straight line look.  This code also used a small circle at the LineShape endpoints which could be grabbed by the user -- at present I switch the line color to red to let the user know it can be dragged.   But if the mouse cursor was in the circle, then clicked it might be more aesthetic (pleasing).
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
The Dock() line might not be necessary at all.  I was simply trying to ensure that the ShapeContainer filled up the entire client area of the Form so that it would fire events everywhere (even where lines are not); but that seemed not to be the case.  It looks like the ShapeContainer only exists in the areas where shapes are actually rendered.  This is why the mouse movement and clicking handlers needed to include the Form's events as well.

Right, that's the standard formula for distance between two points:
http://www.purplemath.com/modules/distform.htm

Iterating over "Me.SC.Shapes" is fine, too.  I wasn't sure if you needed the separate list for something else as you were previously asking about arrays.  You'd also need to check the type of the shape then if other shapes were contained in the ShapeContainer too.

I didn't experience any jittery behavior on my side.  It might be due to the number of controls on your form, etc.  Not sure.  Try enabling DoubleBuffered() on the Form and see if that helps.

You can add circles to the ends of the lines using the Paint() event of the Form.  Something like the below.  Note that we have to call Refresh(), though, to get the circles to change color and move with the lines as they are dragged:
Imports Microsoft.VisualBasic.PowerPacks
Public Class Form1

    Public Enum LineEnd
        StartPoint
        EndPoint
    End Enum

    Private DragEnd As LineEnd
    Private HitTestDelta As Integer = 10
    Private Lines As New List(Of LineShape)
    Private WithEvents SC As New ShapeContainer
    Private CurLineShape As LineShape = Nothing

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Me.DoubleBuffered = True
        Me.Controls.Add(SC)
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Static R As New Random

        Dim LS As New LineShape
        LS.StartPoint = New Point(R.Next(SC.Width), R.Next(SC.Height))
        LS.EndPoint = New Point(R.Next(SC.Width), R.Next(SC.Height))
        LS.BorderStyle = Drawing2D.DashStyle.Solid
        Lines.Add(LS)
        SC.Shapes.Add(LS)
        Me.Refresh()
    End Sub

    Private Sub SC_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown, SC.MouseDown
        If e.Button = Windows.Forms.MouseButtons.Left Then
            If Not IsNothing(CurLineShape) Then
                Cursor.Position = SC.PointToScreen(IIf(DragEnd = LineEnd.StartPoint, CurLineShape.StartPoint, CurLineShape.EndPoint))
            End If
        End If
    End Sub

    Public Sub SC_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove, SC.MouseMove
        If e.Button = Windows.Forms.MouseButtons.None Then
            Dim pt As Point = SC.PointToClient(Cursor.Position)

            ' See if we are over a Line Endpoint:
            Dim myLS As LineShape = Nothing
            For Each LS As LineShape In Lines
                If PointToPointDist(pt, LS.StartPoint) <= HitTestDelta Then
                    myLS = LS
                    DragEnd = LineEnd.StartPoint
                    Exit For
                ElseIf PointToPointDist(pt, LS.EndPoint) <= HitTestDelta Then
                    myLS = LS
                    DragEnd = LineEnd.EndPoint
                    Exit For
                End If
            Next

            If Not IsNothing(myLS) Then ' If we are over a Line Endpoint...
                ' Change the Color of the Previous Line back to Black:
                If Not IsNothing(CurLineShape) AndAlso Not myLS Is CurLineShape Then
                    CurLineShape.BorderColor = Color.Black
                End If

                ' Change Color of the New Line to Red:
                CurLineShape = myLS
                CurLineShape.BorderColor = Color.Red
                Me.Refresh()
            Else ' We are NOT over a Line Endpoint ...
                ' Change the Color of the Previous Line back to Black, and reset the "CurLineShape" variable:
                If Not IsNothing(CurLineShape) Then
                    CurLineShape.BorderColor = Color.Black
                    CurLineShape = Nothing
                    Me.Refresh()
                End If
            End If
        ElseIf e.Button = Windows.Forms.MouseButtons.Left Then
            If Not IsNothing(CurLineShape) Then
                Dim pt As Point = SC.PointToClient(Cursor.Position)
                If DragEnd = LineEnd.StartPoint Then
                    CurLineShape.StartPoint = pt
                Else
                    CurLineShape.EndPoint = pt
                End If
                Me.Refresh()
            End If
        End If
    End Sub

    Public Shared Function PointToPointDist(ByVal ptA As Point, ByVal ptB As Point) As Single
        Return PointToPointDist(ptA.X, ptA.Y, ptB.X, ptB.Y)
    End Function

    Public Shared Function PointToPointDist(ByVal Ax As Single, ByVal Ay As Single, ByVal Bx As Single, ByVal By As Single) As Single
        ' PointToPointDist = SquareRoot((Bx - Ax)^2 + (By - Ay)^2)
        Return Math.Sqrt((Bx - Ax) * (Bx - Ax) + (By - Ay) * (By - Ay))
    End Function

    Private Sub Form1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
        For Each LS As LineShape In Lines
            Dim pt As Point = Me.PointToClient(SC.PointToScreen(LS.StartPoint))
            Dim RC As New Rectangle(pt, New Size(1, 1))
            RC.Inflate(HitTestDelta / 2, HitTestDelta / 2)
            e.Graphics.FillEllipse(IIf(LS Is CurLineShape, Brushes.Red, Brushes.Black), RC)

            pt = Me.PointToClient(SC.PointToScreen(LS.EndPoint))
            RC = New Rectangle(pt, New Size(1, 1))
            RC.Inflate(HitTestDelta / 2, HitTestDelta / 2)
            e.Graphics.FillEllipse(IIf(LS Is CurLineShape, Brushes.Red, Brushes.Black), RC)
        Next
    End Sub

End Class

Open in new window

0
 

Author Comment

by:lep1
Comment Utility
I tried the paint method to draw an ellipse, and actually used pens(Brushes.Red, 2)  and drawellipse to get an empty circle.   However, only a remnant of the circle is visible and a trail of remnants is left on the screen.   It would be nice to have a small circle of diameter 10 which inflates to 20 when the mouse is nearby.   I may just go with the hand option of the cursor property for the moving LS.Endpoint.
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
The remnant and/or partial issue is because you need to call Refresh() against the Form at key points in time.  If you look closely at my last example you can see multiple calls to Refresh() at line numbers 29, 67, 73 and 84.
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

In my previous article (http://www.experts-exchange.com/Programming/Languages/.NET/.NET_Framework_3.x/A_4362-Serialization-in-NET-1.html) we saw the basics of serialization and how types/objects can be serialized to Binary format. In this blog we wi…
It was really hard time for me to get the understanding of Delegates in C#. I went through many websites and articles but I found them very clumsy. After going through those sites, I noted down the points in a easy way so here I am sharing that unde…
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now