2

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

2 Answers2

2

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.

LarsTech
  • 80,625
  • 14
  • 153
  • 225
  • I'm adding a lineshape as a child to shapecontainer, setting endpoint and startpoint, adding to me.controls, but when I loop through controls in another subroutine to find the child lineshape, the .X and .Y values for endpoint and startpoint are zero. I think this may be why the globally declared mLineShapes() and mShapeContainer() arrays were used to store these objects using redim preserve. OOtherwise, how can .X and .Y be conserved when adding the shapecontainer to me.controls (only). Thx. –  Jan 04 '13 at 14:27
  • @LEP I can see why I never used those controls: they're pretty goofy to work with. I updated the answer with how I got it to work. – LarsTech Jan 04 '13 at 15:21
  • The intent for knowing what the parent is for each LS is because the workflow involves chaining together of a series of LS's which connect controls sitting on Form1. For example, a user drops 5 controls (in series, not parallel) on to Form1, and then right-clicks each control to select "Link", a new LS appears, and then connects control 1 to 2 with LS1, then 2 to 3 with LS2, then 3 to 4 with LS3, then 4 to 5 with LS4. When done, Control 1 is the parent of LS1, Control 2 is the parent of LS2 and so forth. For your method, how would you keep track of who the parent is for each LS? –  May 09 '19 at 19:20
0
Public Class Form1

    Dim canvas As New Microsoft.VisualBasic.PowerPacks.ShapeContainer
    ' Set the form as the parent of the ShapeContainer.

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        canvas.Parent = Me
        ' Set the ShapeContainer as the parent of the LineShape.
    End Sub

    Private Sub Form1_MouseClick(sender As Object, e As MouseEventArgs) Handles Me.MouseClick
        If RadioButton1.Checked = True Then
            Dim line1 As New Microsoft.VisualBasic.PowerPacks.LineShape
            line1.Parent = canvas
            ' Set the starting and ending coordinates for the line.
            line1.StartPoint = New System.Drawing.Point(Me.Width / 2, 0)
            line1.EndPoint = New System.Drawing.Point(e.X, e.Y)
            TextBox1.Text = canvas.Shapes.Count.ToString
            line1.Name = "MyShape"
            canvas.Shapes.Add(line1)
            AddHandler line1.Click, AddressOf LineClick
        End If
    End Sub

    Private Sub LineClick(sender As Object, e As EventArgs)
        ' Here is where we take the object that is sender from the arguments and cast it to its specific control
        If RadioButton2.Checked = True Then
            ' I could just as easily use
            CType(sender, PowerPacks.LineShape).Dispose()
            TextBox1.Text = canvas.Shapes.Count
        End If
    End Sub

End Class
AndySavage
  • 1,729
  • 1
  • 20
  • 34