I have a form with a TableLayoutPanel with the following properties changed from default:

Dock = Fill
AutoSize = True
AutoSizeMode = GrowAndShrink
ColumnCount = 1
Margin.All = 0

one row set to AutoSize

This is a stripped-to-the-basics version of part of my app. I want to generate a vertical, scrollable array of buttons which will allow me to select groups of photos. Each button will represent a group of one or more photos in a collection. The app will scan a folder and determine which groups are present, and which files are in each group. I create the buttons (with AutoSize), then determine the width of the widest button (at the same time, setting the button anchor property to make all buttons the same width. I then set the form to accomodate the width of the widest button.

If the form is sized at design time so that all buttons fit vertically (no scrollbars) then everything sizes properly (attached 1). however, if the form is sized at design time so that the scroll bars appear when the buttons are added then the buttons do not resize properly (attached 2). If I then expand the width to accomodate the buttons then reshrink, the buttons resize properly (attached 3).

Any ideas on why, and how to correct this?

Bonus question - is there any way to decrease the space between the buttons?

The code for the example is

    Public Class Form1

        Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load

            'add some buttons to the Layout

            tblLayout.Controls.Add(NewButton("Tom"))
            tblLayout.Controls.Add(NewButton("George"))
            tblLayout.Controls.Add(NewButton("Fred, Dick and Jim"))
            tblLayout.Controls.Add(NewButton("Sharon"))
            tblLayout.Controls.Add(NewButton("Vacation 2010"))
            tblLayout.Controls.Add(NewButton("Summer 2011"))
            tblLayout.Controls.Add(NewButton("Christmas 2011"))

            'find the width of the widest button

            Dim maxwidth As Integer = 0

            For Each button As Button In tblLayout.Controls
                maxwidth = Math.Max(maxwidth, button.Width)
                button.Anchor = AnchorStyles.Left + AnchorStyles.Right + AnchorStyles.Top
            Next

            'size form to fit widest button + vertical scrollbar.

            Me.Width = maxwidth + (Me.Width - tblLayout.Width) + 5
            Me.MinimumSize = New Size(20, Me.Width)

        End Sub

        Private Function NewButton(text As String) As Button

            NewButton = New Button
            NewButton.AutoSize = True
            NewButton.Text = text

        End Function

    End Class

Edited 4 Years Ago by pyTony: fencing ~~~ vb code tags added

Attachments 1.jpg 15.2 KB 2.jpg 9.03 KB 3.jpg 13.41 KB

Got an idea just after posting. It works but there should be a better way.

Me.Width = 1000
Me.Width = maxwidth + (Me.Width - tblLayout.Width) + 5

Now about the spacing thing...

use the ~~~ vb style fencing code blocks for vb with quote comments, see Formatting Help.

I fixed the original post for you.

Edited 4 Years Ago by pyTony

I flowlayoutpanel would work great for this. Add a FlowLayoutPanel to a form and paste the following code. There will never be a horizontal scrollbar. With the flow panel if you set the width of the first conrol and then set all the remaining controls to dock style fill they will be the same size as the first. I set the width of the first control if its the first control added or the flow panel needs to layout. All other controls added will have there dock style set to fill. You can add controls at design time or runtime and they will stretch properly. When adding controls at design time don't worry about the width. When the program is run they will stretch automatically.

The flow layout panel needs to be set to FlowDirection = TopDown and you may need to set WrapContents = False.

To adjust the spacing of the controls set the controls margin property to the spacing you want.


Public Class Form1 Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load FlowLayoutPanel1.SuspendLayout() For i = 1 To 30 FlowLayoutPanel1.Controls.Add(New Button With {.Margin = New Padding(1)}) Next FlowLayoutPanel1.ResumeLayout(True) End Sub Private Sub FlowLayoutPanel1_ControlAdded(ByVal sender As Object, ByVal e As System.Windows.Forms.ControlEventArgs) Handles FlowLayoutPanel1.ControlAdded 'So we don't need to keep track of setting the width of the first control 'we can just set it right here if the control being added is the first. If FlowLayoutPanel1.Controls.IndexOf(e.Control) > 0 Then e.Control.Dock = DockStyle.Fill Else e.Control.Dock = DockStyle.None e.Control.Width = FlowLayoutPanel1.ClientSize.Width - FlowLayoutPanel1.Padding.Horizontal - e.Control.Margin.Horizontal End If End Sub Private Sub FlowLayoutPanel1_Layout(ByVal sender As Object, ByVal e As System.Windows.Forms.LayoutEventArgs) Handles FlowLayoutPanel1.Layout 'The layout will be called when the controls need to be resized. 'Set the first control to the width of the flow panel - scrollbars 'and margins and paddings If FlowLayoutPanel1.Controls.Count > 0 Then With FlowLayoutPanel1.Controls(0) .Width = FlowLayoutPanel1.ClientSize.Width - FlowLayoutPanel1.Padding.Horizontal - .Margin.Horizontal End With End If End Sub End Class

Edited 4 Years Ago by pyTony: fencing ~~~ vb code tags added

You would also need to handle the control removed handler to set the new first controls width to the flow panels width and dock to none.

Heres a better solution.

Add a new class named VerticalLayoutPanel and paste the following code. This panel will always list its controls from top down and stretch them all. After you rebuild the project you can paste as many of these as you like from the toolbar and they will also strech the controls at design time.

    Public Class VerticalLayoutPanel : Inherits System.Windows.Forms.FlowLayoutPanel

        'Disable some standard features of the flow panel

        <System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)> _
        <System.ComponentModel.Browsable(False)> _
        Shadows Property FlowDirection() As System.Windows.Forms.FlowDirection
            Get
                Return Windows.Forms.FlowDirection.TopDown
            End Get
            Set(ByVal value As System.Windows.Forms.FlowDirection)

            End Set
        End Property

        <System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)> _
        <System.ComponentModel.Browsable(False)> _
        Shadows Property WrapContents() As Boolean
            Get
                Return False
            End Get
            Set(ByVal value As Boolean)

            End Set
        End Property

        <System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)> _
        <System.ComponentModel.Browsable(False)> _
        Public Overrides Property AutoScroll() As Boolean
            Get
                Return MyBase.AutoScroll
            End Get
            Set(ByVal value As Boolean)

            End Set
        End Property

        Sub New()
            'set permanent properties to this control
            MyBase.FlowDirection = Windows.Forms.FlowDirection.TopDown
            MyBase.WrapContents = False
            MyBase.AutoScroll = True
        End Sub

        Protected Overrides Sub OnControlAdded(ByVal e As System.Windows.Forms.ControlEventArgs)

            'control the size of the first control and the dock style of the 
            'remaining
            MyBase.OnControlAdded(e)
            If Me.Controls.IndexOf(e.Control) > 0 Then
                e.Control.Dock = DockStyle.Fill
            Else
                e.Control.Dock = DockStyle.None
                e.Control.Width = Me.ClientSize.Width - Me.Padding.Horizontal - e.Control.Margin.Horizontal
            End If

            'Add a dock change block
            AddHandler e.Control.DockChanged, AddressOf ChildControl_DockChanged

        End Sub

        Protected Overrides Sub OnControlRemoved(ByVal e As System.Windows.Forms.ControlEventArgs)

            MyBase.OnControlRemoved(e)

            'set the dock to none for the new first control
            If Me.Controls.Count > 0 Then
                Me.Controls(0).Dock = DockStyle.None
            End If

            'remove the dock change block
            RemoveHandler e.Control.DockChanged, AddressOf ChildControl_DockChanged
        End Sub

        Protected Overrides Sub OnLayout(ByVal levent As System.Windows.Forms.LayoutEventArgs)

            If Me.Controls.Count > 0 Then
                With Me.Controls(0)
                    .Width = Me.ClientSize.Width - Me.Padding.Horizontal - .Margin.Horizontal
                End With
            End If

            MyBase.OnLayout(levent)
        End Sub

        Private Sub ChildControl_DockChanged(ByVal sender As Object, ByVal e As EventArgs)
            'Use this to disallow dock changing while in this container
            If sender IsNot Nothing AndAlso TypeOf sender Is Control Then
                If Me.Controls.Contains(sender) Then
                    If Me.Controls.IndexOf(sender) = 0 Then
                        CType(sender, Control).Dock = DockStyle.None
                    Else
                        CType(sender, Control).Dock = DockStyle.Fill
                    End If
                End If
            End If
        End Sub

    End Class

Edited 4 Years Ago by pyTony: fencing ~~~ vb code tags added

Comments
awesome

I posted a reply a few hours back thanking you for your amazing input (at the same time voting you up) but the post seems to have disappeared. I hope this one is a little stickier.

This question has already been answered. Start a new discussion instead.