Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

Delete LineShape from screen, LineShape array, and Shapecontainer array

Posted on 2012-12-28
13
Medium Priority
?
1,498 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 86

Expert Comment

by:Mike Tomlinson
ID: 38728743
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
ID: 38729249
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 86

Expert Comment

by:Mike Tomlinson
ID: 38729332
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
Nothing ever in the clear!

This technical paper will help you implement VMware’s VM encryption as well as implement Veeam encryption which together will achieve the nothing ever in the clear goal. If a bad guy steals VMs, backups or traffic they get nothing.

 

Author Comment

by:lep1
ID: 38743882
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 86

Accepted Solution

by:
Mike Tomlinson earned 2000 total points
ID: 38744125
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 86

Expert Comment

by:Mike Tomlinson
ID: 38744134
Screenshot:
One ShapeContainer, Many LineShapes
0
 

Author Comment

by:lep1
ID: 38744911
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 86

Expert Comment

by:Mike Tomlinson
ID: 38745450
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
ID: 38745899
This was a great solution -- and was the method I chose
0
 

Author Comment

by:lep1
ID: 38745974
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 86

Expert Comment

by:Mike Tomlinson
ID: 38746214
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
ID: 38747927
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 86

Expert Comment

by:Mike Tomlinson
ID: 38747982
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

Free learning courses: Active Directory Deep Dive

Get a firm grasp on your IT environment when you learn Active Directory best practices with Veeam! Watch all, or choose any amount, of this three-part webinar series to improve your skills. From the basics to virtualization and backup, we got you covered.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Many of us here at EE write code. Many of us write exceptional code; just as many of us write exception-prone code. As we all should know, exceptions are a mechanism for handling errors which are typically out of our control. From database errors, t…
Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
Want to learn how to record your desktop screen without having to use an outside camera. Click on this video and learn how to use the cool google extension called "Screencastify"! Step 1: Open a new google tab Step 2: Go to the left hand upper corn…
We’ve all felt that sense of false security before—locking down external access to a database or component and feeling like we’ve done all we need to do to secure company data. But that feeling is fleeting. Attacks these days can happen in many w…

971 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