none
TableLayoutPanel-Draw Rectangles

    Question

  • Hi All

    I am trying to create a set of tablelayoutpanel controls base off of user inputs.  I can create to first control and paint rectangles in the usercontrol I have placed in a row of the tablelayoutpanel.  The problem seems to be when i try to create another tablelayoutpanel.  I try to name each tablelayoutpanel control as such: tablelayoutpanel1,  tablelayoutpanel2, tablelayoutpanel3... this is to make each panel unique.   As I stated the first tablelayoutpanel is created and works as expected.  When the next tablelayoutpanel is created,   then usercontrol should create a new tablelayoutpanel but the Window Form simply clears itself and I get a blank screen.   The other problem is the search code  to find the new tablelayoutpanel object seems to find no new tablelayoutpanel the code should have just created.   See the code sample below.

    I would like to understand the following:

    • Why can I not seem to create more than one TableLayoutPanel Control?
    • Is there a way to keep a tablelayoutpanel controls from disappearing when the next control is created?   My attempt to solve this is to use a List array.
    • How can I keep the TableLayoutPanel control from disappearing when the WindowForm is resized?
             

    Public Shared Myrectangles As List(Of Myrectangle) = New List(Of Myrectangle)

    Dim oObj As Object . . LayoutForm.CreateTableLayoutPanel(SectionNo_NumericUpDown.Value - 1) 'Create new TableLayoutPanel . . . ' This loops to find and draw each section of numerous pine sections. For number As Integer = 1 To ListCount Step 1 notAmazing = "TableLayoutPanel" & number 'Unique Section Name LayoutForm.NewTableLayoutPanel = LayoutForm.Controls.Find(notAmazing, True).FirstOrDefault() 'Find tablelayoutpanel with unque Name With DirectCast(LayoutForm.NewTableLayoutPanel.Controls(0), LayoutUserControl) .DrawSection(number) 'Create a new section of the Pipe with rectangle info from user End With With DirectCast(LayoutForm.NewTableLayoutPanel.Controls(1), TextBox) .Text = SectionNo_NumericUpDown.Value - 1 'Labels the Section of Pipe End With Next 'Loop to next section . . . Public Sub CreateTableLayoutPanel(ByRef PanelNumber As Integer) NewTableLayoutPanel = New TableLayoutPanel 'Create new control NewTableLayoutPanel.Controls.Add(New LayoutUserControl) 'Add User Control NewTableLayoutPanel.Controls.Add(New TextBox) 'Add Text Box NewTableLayoutPanel.Enabled = False 'Force non-editable With DirectCast(NewTableLayoutPanel.Controls(0), LayoutUserControl) .Name = "LayoutUserControl" & Convert.ToString(PanelNumber) 'Create a unique Name for TableLayoutPanel .Dock = DockStyle.Fill 'Control to fill Row #1 .Visible = True 'Make control visible End With With DirectCast(NewTableLayoutPanel.Controls(1), TextBox) .BorderStyle = BorderStyle.None 'Make TextBox without Border .BackColor = SystemColors.Control 'Make color same as background color .Dock = DockStyle.Fill 'TextBox to fill row .ForeColor = Color.Red 'Make Text color Red .Font = New Font("Ariel", 10, FontStyle.Bold) 'Make Font Ariel, size 10 and Font Bold .Text = Convert.ToString(PanelNumber) 'Add text to TextBox .TextAlign = HorizontalAlignment.Center 'Align to Center End With End Sub Public Sub DrawLine(ByRef ID As Double, OD As Double, Name As String, SectionLength As Double) Dim ZoomFactor As Double = 1 Dim tablelayoutcount As Integer = CInt(EditCoaxLine.SectionNo_NumericUpDown.Value - 1) Dim InchToPixelConvertion As Double = 500 Dim Yvalue As Double = 0 Dim Xposition As Double Dim CalculatedDielectricSpacing As Double = 0 Dim GroudThickness As Double = 30 Dim ContactThickness As Double = IDBasic * InchToPixelConvertion * ZoomFactor Dim SectionLengthPixels As Double = SectionLength * InchToPixelConvertion * ZoomFactor Dim YPositionforContact As Double = ((310 / 2) - ((IDBasic / 2) * InchToPixelConvertion)) * ZoomFactor Dim YPositionforDielectric As Double = (((310 / 2) - GroudThickness) - ((IDBasic / 2) * InchToPixelConvertion)) * ZoomFactor Try Dim g As Graphics = CreateGraphics() 'The screen size is 779 wide and 310 deep If tablelayoutcount = 0 Then Xposition = 0 Else End If GroundPen = New SolidBrush(Color.Gray) ContactPen = New SolidBrush(Color.Gold) CalculatedDielectricSpacing = Math.Abs((310 / 2) - GroudThickness) - ((IDBasic / 2) * InchToPixelConvertion) Select Case Dielectric Case "PTFE" DielectricPen = New SolidBrush(Color.White) 'PTFE Case "Air" DielectricPen = New SolidBrush(Color.Aquamarine) 'Air Case "Glass" DielectricPen = New SolidBrush(Color.DarkSeaGreen) 'Glass Case "Ultem" DielectricPen = New SolidBrush(Color.Beige) 'Ultem Case "Torlon" DielectricPen = New SolidBrush(Color.LawnGreen) 'Torlon Case "Delrin" DielectricPen = New SolidBrush(Color.Cornsilk) 'Delrin Case "User Entry" DielectricPen = New SolidBrush(Color.Maroon) 'User Entry End Select Dim rect As New Rectangle(Xposition, 0, SectionLengthPixels, GroudThickness) Dim rect1 As New Rectangle(Xposition, GroudThickness, SectionLengthPixels, CalculatedDielectricSpacing) Dim rect2 As New Rectangle(Xposition, YPositionforContact, SectionLengthPixels, ContactThickness) Dim rect3 As New Rectangle(Xposition, ContactThickness + YPositionforContact, SectionLengthPixels, CalculatedDielectricSpacing) Dim rect4 As New Rectangle(Xposition, ContactThickness + YPositionforContact + CalculatedDielectricSpacing, SectionLengthPixels, GroudThickness) If tablelayoutcount = 1 Then LayoutForm.NewTableLayoutPanel.Size = New Size(rect4.Width, 344) Else LayoutForm.NewTableLayoutPanel.Location = New Point(rect4.Width, 344) End If Myrectangles.Add(New Main.Myrectangle(tablelayoutcount, GroundPen, rect)) Myrectangles.Add(New Main.Myrectangle(tablelayoutcount, DielectricPen, rect1)) Myrectangles.Add(New Main.Myrectangle(tablelayoutcount, ContactPen, rect2)) Myrectangles.Add(New Main.Myrectangle(tablelayoutcount, DielectricPen, rect3)) Myrectangles.Add(New Main.Myrectangle(tablelayoutcount, GroundPen, rect4)) Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub

    Thanks

    Nuadha

    Tuesday, March 6, 2018 2:07 AM

All replies

  • What event calls DrawLine called?


    In this:

       Dim g As Graphics = CreateGraphics() 

    no control specified so the form is used. You would be drawing on the form, however I dont see any actual drawing code.


    This is not how drawing works:

     Myrectangles.Add(New Main.Myrectangle(tablelayoutcount, GroundPen, rect))

    Should be:

     g.DrawRectangle(GroundPen, rect)

    Or am I missing what you are doing?

    One draws on a graphics surface. That is what CreateGraphics gives us a reference to. What we draw is later transfered to the screen by the system.

    You should make your list of rectangles in a user event like button click or mouse up (or some other way) and then draw the list of rectangles later, as required, preferably in the paint event where the graphics object is given to us as e.graphics.

    So make the rectangle list is one thing. Then drawing the list is something else.

    Here is the drawrectangle method:

    https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Drawing.Graphics.DrawRectangle);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-VB)&rd=true

    Here is an example:

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/a81a4c4d-cfd1-42d1-8aa8-451ca6921926/losing-graphics-on-scrolling-picturebox?forum=vbgeneral

    Tuesday, March 6, 2018 12:16 PM
  • Hi tommytotrain

    Thank you for the links.  

    I neglected to add the code that draws the rectangles stored in the List array, see below.

    I believe that the addition of the "Dim g As Graphics = CreateGraphics()" was an error.  In the above code it does nothing and will be removed.

    So when the user enters the data, they click an "Apply" button to activate the drawing code.

         Public Sub DrawSection(ByRef ListcountNumber As Integer)
    
            Dim g As Graphics = CreateGraphics()
    
            For Each K As Main.Myrectangle In Main.Myrectangles
    
                If ListcountNumber = K.TableRowPosition Then
    
                    Main.MyTemprectangles.Add(New Main.Myrectangle(ListcountNumber, K.myPen, K.RectangleToDraw))
    
                End If
    
            Next
    
            For Each L As Main.Myrectangle In Main.MyTemprectangles
    
                ' Draw recyangles to screen.
                g.FillRectangle(L.myPen, L.RectangleToDraw)
    
            Next
    
            Main.MyTemprectangles.Clear()
            LayoutForm.NewTableLayoutPanel.Invalidate()
    
        End Sub
    
    
    

    Nuadha

    Wednesday, March 7, 2018 11:55 PM
  • Nu,

    Assuming the rest of your code works I don't think you use the pen correctly. there is no L.myPen?

    You need to change this:

                g.FillRectangle(L.myPen, L.RectangleToDraw)

    to this:

                Using p As New Pen(Color.Red, 3)
                    g.FillRectangle( p, L.RectangleToDraw)
                End Using

    The using statement creates and disposes a pen.

    You should make a practice form and draw a rectangle on it. I also suggest you learn to use the paint event to draw. I don't think you are Persisting your graphics. After you call the sub and create graphics and draw on the control, the control is being updated by the system for some other thing like the form being covered by another app or the user resizing or lots of reasons etc.

    You cant just draw the rectangle once when the user clicks a button if I understand what you have.

    The drawing surface (the control) will get updated almost immediately. So you need to draw your drawing again. But you don't know when to draw it. This is why we use the paint event. The paint event fires when the control is being updated by the system for other reasons.

    Look at the examples in the link I gave. See the paint event? See the drawing that is done there? That is what you need to do. Draw in the paint event of your control.

    PS You should put this:

       Option Strict On

    as the first line at the top of your form before the class declare (or set it in the project settings). Then Visual Studio will point out errors to you. Put the mouse pointer over the errors you can get suggestions and etc.


    Thursday, March 8, 2018 1:35 AM
  • I am trying to create a set of tablelayoutpanel controls base off of user inputs.  I can create to first control and paint rectangles in the usercontrol I have placed in a row of the tablelayoutpanel.  The problem seems to be when i try to create another tablelayoutpanel.  I try to name each tablelayoutpanel control as such: tablelayoutpanel1,  tablelayoutpanel2, tablelayoutpanel3... this is to make each panel unique.   As I stated the first tablelayoutpanel is created and works as expected.  When the next tablelayoutpanel is created,   then usercontrol should create a new tablelayoutpanel but the Window Form simply clears itself and I get a blank screen.   The other problem is the search code  to find the new tablelayoutpanel object seems to find no new tablelayoutpanel the code should have just created.   

    You are making an assumption about the sequence of items in the Controls collection.  That won't work - the sequence is not guaranteed, and is not what you expect it to be.

    Don't use the controls collection for accessing the panel. Add the new object to the Controls collection in the normal way so that the control appears, but also create your own collection, such as a List(Of T). Add each control to that collection as created.  That way you know what its index is and you can use that index with that collection to reference the control.

    To ensure that you can access each control that you create, the variable used to reference the control while it is being created must be local to the method where it gets created.  You are using a variable that is scoped at a different level, so your reference is always to the last control created. The variable you use can be local to the method because you are adding the new control to a collection you created for the purpose, and that ensures that you can refer to it as required using that collection and the index.

    Thursday, March 8, 2018 7:35 AM
  • Thank you tommytwotrain and Acamar

    Your comments have helped but I am not here yet.  I was able to create two list arrays, one for the rectangles an another for the tablelayoutpanel controls.  I have not yet been able to use the paint event since the painting is done by code and not a mouse or button event.

    My problem now is painting the indexed rectangles in the list array into an indexed control.  So the there is no event to call the "e As PaintEventArgs".  There is a button the user presses to begin the calculations but not in the same form as the control I am trying to paint the rectangles.

    So I need to know how the paint a set of rectangles in the indexed array into the control from another indexed control array.

    I have tried to attach a few pictures to show my current progress but this site would not let me upload the pics.

    Thanks

    Nuadha

    Monday, March 12, 2018 2:00 AM
  • My problem now is painting the indexed rectangles in the list array into an indexed control.  So the there is no event to call the "e As PaintEventArgs".  There is a button the user presses to begin the calculations but not in the same form as the control I am trying to paint the rectangles.

    There will always be a paint event for the controls.  There might not be a paint event handler - you have to create that.   If you are creating the controls in code  then you need to connect each control to its event handler as you create the control. see:
    https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/addhandler-statement

    You can get a template for a control's paint event handler by temporarily adding the control to the form and selecting the paint event from the events list.  A Handles clause will be created - you don't need it because you are using AddHandler instead, so it can be deleted.

    You will never call the paint event handler directly.  It will be invoked automatically as required by windows forms management, or you can force the paint sequence (which includes the paint event) using Refresh or Invalidate on the control.

    Monday, March 12, 2018 2:12 AM
  • Well this may not be helpful but the TableLayoutPanel Class does have a paint event so you can use a paint event sub with it and add, remove handlers if necessary rather than hardcoding Handles.

    In that sub for painting controls that allow painting and are in the TableLayoutPanel you could use g As Graphics = Graphics.FromHwnd(Controls.Handle) I believe to possibly persist graphics in the controls but you would have to use some kind of loop getting controls handles and then the painting may not be the same for each control which could make it difficult as you would have to distinguish between control types for how to paint them I suppose.

    And possibly the graphics may not persist as the paint event for the TableLayoutPanel would be called when it needs repainting but I don't know if its controls need repainting if its paint event fires through a bubbling effect as perhaps it is called to have its controls repaint. But I don't know for sure.


    La vida loca

    Monday, March 12, 2018 2:12 AM
  • Here is an example. It is easier to show the code that to describe it. Its not exactly what you want you will need to modify it.

    BTW you could simply draw all the rects on the form without any controls if that is the goal.

    Public Class Form4
        Private WithEvents TableLayout As TableLayoutPanel
        Private WithEvents Button1 As New Button With {.Parent = Me, .Location = New Point(30, 10),
            .Text = "Paint"}
        Private PaintIndex As Integer
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
    
            TableLayout = New TableLayoutPanel
            With TableLayout
                .Name = "tableLayout"
                .Margin = New System.Windows.Forms.Padding(0, 0, 0, 0)
                .ColumnCount = 0
                .RowCount = 0
                .AutoSizeMode = AutoSizeMode.GrowAndShrink
                .AutoSize = True
                .CellBorderStyle = TableLayoutPanelCellBorderStyle.Single
                .ForeColor = Color.Red
                .Location = New Point(10, 70)
            End With
            Me.Controls.Add(TableLayout)
    
    
            For x As Integer = 0 To 2
                Me.TableLayout.ColumnCount += 1
                Me.TableLayout.ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize))
                For y As Integer = 0 To 2
                    If y = 0 Then
                        Me.TableLayout.RowCount += 1
                        Me.TableLayout.RowStyles.Add(New ColumnStyle(SizeType.AutoSize))
                    End If
    
                    Dim thisPic As New PictureBox
                    With thisPic
                        .Name = "Pic " & (3 * x + y).ToString
                        .Size = New Size(50, 50)
                        AddHandler .Paint, AddressOf OnPicPaint
                    End With
                    Me.TableLayout.Controls.Add(thisPic, y, x)
                Next
            Next
        End Sub
    
        Private Sub OnPicPaint(sender As Object, e As PaintEventArgs)
            Dim rect As Rectangle
            Dim pic As PictureBox = DirectCast(sender, PictureBox)
            Dim picColor As Color
    
            With e.Graphics
                rect = pic.ClientRectangle
                rect.Inflate(-10, -10)
                If PaintIndex = 0 Then
                    picColor = Color.AntiqueWhite
                Else
    
                    Select Case pic.Name
                        Case "Pic 0"
                            picColor = Color.Red
                        Case "Pic 1"
                            picColor = Color.Green
                        Case "Pic 2"
                            picColor = Color.Yellow
                        Case "Pic 3"
                            picColor = Color.Blue
                        Case "Pic 4"
                            picColor = Color.Purple
                        Case "Pic 5"
                            picColor = Color.Brown
                        Case "Pic 6"
                            picColor = Color.Gray
                        Case "Pic 7"
                            picColor = Color.Teal
                        Case "Pic 8"
                            picColor = Color.Black
    
                    End Select
                End If
                .Clear(Color.White)
                .DrawRectangle(New Pen(picColor, 5), rect)
            End With
    
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            If PaintIndex = 0 Then PaintIndex = 1 Else PaintIndex = 0
            TableLayout.Refresh()
        End Sub
    End Class

    • Proposed as answer by Mr. Monkeyboy Monday, March 12, 2018 2:12 PM
    Monday, March 12, 2018 2:00 PM