Search code examples
vb.netvisual-studio-2010line

Remove LineShape from Windows Form, LineShape and ShapeContainer Arrays


I am using the code below to add multiple LineShape controls to a Windows Form. Note the globally declared mLineShapes() and mShapeContainter() arrays (at bottom of code) which store each new LineShape object once it's created.

At present, I have been unsuccessful at removing a given LineShape control from the form (even if I know its array index), and also cannot removing an array element without causing a Nothing for the removed element. Obviously, once I remove the element from these arrays, it requires that all the remaining elements with greater indices are copied to lower values to fill in the Nothing voided element. Given these circumstances, can lists be used instead of the mLineShapes() and mShapeContainer() arrays?

        enter code here' 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(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

In addition to the above, the endpoints of each LineShape are selected from the arrays so that they can be moved. An example is below:

        Dim siSCId As Integer
        Dim myShapeContainer As ShapeContainer
        myShapeContainer = CType(sender, ShapeContainer)
        Dim myLineShape As LineShape
        ' get index of the actual ShapeContainer in ShapeContainer array
        siSCId = Array.IndexOf(mShapeContainer, sender)

        If siSCId > -1 Then
            myLineShape = mLineShapes(siSCId)
            If MouseIsNearBy(myLineShape.EndPoint) Then
                myLineShape.BorderColor = Color.Red
                NearLineEndPoint = True
            End If
            If MouseIsNearBy(myLineShape.EndPoint) = False Then
                myLineShape.BorderColor = Color.Black
                NearLineEndPoint = False
            End If
            If (dragStartPoint) Then
                myLineShape.StartPoint = New Point(oldStartPoint.X + e.X - oldMouseX, oldStartPoint.Y + e.Y - oldMouseY)
            End If
        End If 

Therefore, If I simply add a new LineShape to the form controls without using the mLineShapes() ans mShapeControl() arrays, how can I modify the above code (which finds the LineShape in the storage arrays) so that the line can be modified? I think that if I click on a LineShape, I can get its name using .sourcecontrol or .parent?

UPDATE 5/9/2019

After right clicking on a control on Form1 and selecting the "Link" command from a ContextMenuStrip, the following method (ctmsconnect) is fired to draw a new LineShape control that the user then drags and drops on to the next control in the workflow. Question is, is the list of LineShapes ("Lines") not needed?

(in Form1 class declarations):

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

  Private Sub ctmsconnect_Click(sender As System.Object, e As System.EventArgs) Handles ctmsconnect.Click
        mLineWidth = 1
        Dim myItem As ToolStripMenuItem = CType(sender, ToolStripMenuItem)
        Dim cms As ContextMenuStrip = CType(myItem.Owner, ContextMenuStrip)
        Dim x As Integer = cms.SourceControl.Right - 2
        Dim y As Integer = cms.SourceControl.Top + (cms.SourceControl.Height / 2 - 12)
        Dim LS As New LineShape
        NumLineShapes += 1
        LS.Name = "LineShape" & NumLineShapes
        LS.BorderColor = Color.Black
        LS.BorderWidth = 2

        'Set starting and ending coordinates for the line
        LS.StartPoint = New Point(x, y)
        LS.EndPoint = New Point(x + 80, y - 5)

        'Set new LineShape to top of z-order
        LS.BringToFront()

        Dim nxgContextMenuStrip As New ContextMenuStrip
        LS.ContextMenuStrip = nxgContextMenuStrip
        LS.Tag = "LineShape" & NumLineShapes & "_Delete"

        'Attach an event handler for the ContextMenuStrip control's Opening event. 
        AddHandler nxgContextMenuStrip.Opening, AddressOf cms_Opening
        numconnectedlineendpoints += 1
        Dim myValues As New List(Of String)
        myValues.Add(cms.SourceControl.Name)
        DropLineOriginalObjectName = cms.SourceControl.Name
        OrigDropControl = cms.SourceControl
        myValues.Add(LS.Name)
        myValues.Add("linestart")
        dicGUIControls.Add(numconnectedlineendpoints, myValues)
        Lines.Add(LS)
        SC.Shapes.Add(LS)
        Me.Refresh()

    End Sub

Solution

  • You shouldn't need the arrays, just use the controls collection. Instead of setting the parent of the controls, you should probably add them to the collection:

    'sSCTemp.Parent = Me
    Me.Controls.Add(sSCTemp)
    

    To remove them, you can reference them by the name property:

    If Me.Controls.ContainsKey("ShapeContainer1") Then
      Me.Controls.RemoveByKey("ShapeContainer1")
    End If
    

    The shape controls inside the ShapeContainer have to be accessed through the Shape collection:

    If Me.Controls.ContainsKey("ShapeContainer1") Then
      Dim sc As ShapeContainer = DirectCast(Me.Controls("ShapeContainer1"), ShapeContainer)
      If sc.Shapes.ContainsKey("LineShape2") Then
        sc.Shapes.RemoveAt(sc.Shapes.IndexOfKey("LineShape2"))
      End If
    End If
    

    Example of reading the StartPoint and EndPoint properties:

    Dim sb As New StringBuilder
    For Each ls As LineShape In Me.ShapeContainer1.Shapes
      sb.AppendLine(ls.StartPoint.ToString & " - " & ls.EndPoint.ToString)
    Next
    MessageBox.Show(sb.ToString)
    

    Note: ShapeContainer Class makes a special note:

    Be careful that you do not create more than one ShapeContainer for each form or container; doing this may introduce unexpected behavior. If you add a design-time line or shape control to a form or container after you write code to create one programmatically, you should modify that code to use the ShapeContainer created by the designer.