1

I've got a WinForms Panel control which holds a large number of child controls. Each child is left docked, causing the horizontal width of the contents to grow. The containing Panel has its AutoScroll property set so that you can get to all the contents.

I'm running into a problem when the total width of the contents gets too large. Once you've hit this maximum width, additional content elements are placed on top of existing contents instead of being placed to the right. But, if I resize the Panel after it has done its initial layout, it corrects itself by expanding its logical width and placing each content element in the correct location. How do I get it to layout correctly before the user resizes the window?

Here's a simple example:

Form1.vb

Public Class Form1
    Protected Overrides Sub OnLoad(e As EventArgs)
        MyBase.OnLoad(e)

        For i As Integer = 1 To 200
            Dim gb As New GroupBox
            gb.Text = "Box " & i.ToString
            gb.Width = 250
            gb.Dock = DockStyle.Left
            Panel1.Controls.Add(gb)
            gb.BringToFront()
        Next
    End Sub
End Class

Form1.Designer.vb

Partial Class Form1
    Inherits System.Windows.Forms.Form

    Private Sub InitializeComponent()
        Me.Panel1 = New System.Windows.Forms.Panel()
        Me.SuspendLayout()
        '
        'Panel1
        '
        Me.Panel1.AutoScroll = True
        Me.Panel1.Dock = System.Windows.Forms.DockStyle.Fill
        Me.Panel1.Location = New System.Drawing.Point(0, 0)
        Me.Panel1.Name = "Panel1"
        Me.Panel1.Size = New System.Drawing.Size(284, 262)
        Me.Panel1.TabIndex = 0
        '
        'Form1
        '
        Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
        Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
        Me.ClientSize = New System.Drawing.Size(284, 262)
        Me.Controls.Add(Me.Panel1)
        Me.Name = "Form1"
        Me.Text = "Form1"
        Me.ResumeLayout(False)

    End Sub
    Friend WithEvents Panel1 As System.Windows.Forms.Panel
End Class

This is what the window looks like when it first comes up, scrolled nearly to the end so you can see the problem area. Notice that Box 183 to 199 are missing because they are placed on top of each other. This is not right.

What initially loads - bad layout

This is what the window looks like after you manually resize it, scrolled nearly to the end. The panel fixed itself in response to the resize; the total logical width of the panel was automatically extended enough to hold all the contents. This is what I want it to look like when it first comes up.

After user resizes - good layout

I've tried manually setting the location of each box, and I've tried calling PerformLayout() and several other functions. Nothing seems to work. So far I haven't found the magic combination to get the good layout. Does anyone know how to fix this?

Edit:

Here's a screenshot that might make the issue more obvious. I adjusted the box widths and the number of boxes to show the problem better. See how the last box overlaps box 656? Every box from 657 to 700 has the same incorrect location. Turning off docking and setting the location myself doesn't help.

Another example showing box overlap

Darryl
  • 5,907
  • 1
  • 25
  • 36
  • autoscroll is not autoposition, you need to set the location of the panels/boxes when you add them using `AutoScrollPosition` of the scrolling panel – Ňɏssa Pøngjǣrdenlarp Oct 23 '14 at 00:32
  • That doesn't address the issue. The problem is not where the panel is scrolled to, the problem is that the items you see after scrolling to the right are in the wrong location, stacked on top of each other. Take a closer look at what's wrong with the first screenshot. – Darryl Oct 23 '14 at 13:42

2 Answers2

2

Looks like a bug with the scrolling information. If you call PerformLayout when the Panel is scrolled all the way to the right, it correctly places the controls in the proper place. That requires some code in the OnShown method:

Protected Overrides Sub OnLoad(e As EventArgs)
  MyBase.OnLoad(e)
  Panel1.AutoScroll = True
  Panel1.SuspendLayout()
  For i As Integer = 1 To 200
    Dim gb As New GroupBox
    gb.Text = "Box " & i.ToString
    gb.Width = 250
    gb.Dock = DockStyle.Left
    Panel1.Controls.Add(gb)
    gb.BringToFront()
  Next
  Panel1.ResumeLayout(False)
End Sub

Protected Overrides Sub OnShown(e As EventArgs)
  MyBase.OnShown(e)
  Panel1.AutoScrollPosition = New Point(Panel1.HorizontalScroll.Maximum - _
                                        Panel1.HorizontalScroll.LargeChange, 0)

  Panel1.PerformLayout()
  Panel1.AutoScrollPosition = Point.Empty
End Sub

Of course, having over 200 container controls on the form is never recommended.

LarsTech
  • 80,625
  • 14
  • 153
  • 225
1

AutoScroll is not AutoPositionMyChildren. From MSDN:

When adding controls programmatically to a form, use the AutoScrollPosition property to position the control either inside or outside of the current viewable scroll area.

If you looped thru the controls, to print their location, you's see at some point (probably around 130) that Location.Y becomes fixed at 32767 probably some default unscrolled max. This is also the point they start stacking because they in fact have the same initial location. Some of the code you have makes up for that but it isnt quite right. Once you scroll it, the panel fixes the coords on the child controls.

First, I would suggest that you set Panel1.AutoScrollMinSize to something like {480, 0} so that the HScroll bar appears at design time; this allows you to calc a good height for the boxes which wont cause a VScroll as you add controls.

Dim gb As GroupBox

' only 150 because problem is when (i * width) > 32k
For i As Integer = 0 To 150           

    gb = New GroupBox 
    gb.Name = i.ToString                     ' added
    gb.Text = "Box " & i.ToString
    gb.Width = 250
    ' no docking so set the height
    gb.Height = Panel1.Bounds.Height - 30       ' trying to avoid the VSCroll

    ' set location explicitly
    gb.Location = NewCtlLocation(Panel1.Controls.Count, 
                  Panel1.AutoScrollPosition.X)

    ' Dock and Anchor mess up the AutoScroll
    'gb.Dock = DockStyle.Left
    Panel1.Controls.Add(gb)

    ' allow panel to update its scroll positions
    Panel1.ScrollControlIntoView(gb)

    ' not needed; seems to offset something with Dock
    ' changing ZOrder may not always be desirable
    'gb.BringToFront()

    ' debug illumination
    Console.WriteLine("{0} {1} {2}", i.ToString, 
                          Panel1.AutoScrollPosition.X.ToString,
                          gb.Location.X.ToString)
Next
'Go back to start
Panel1.ScrollControlIntoView(Panel1.Controls("0"))

Location helper so you can tweak gutters or margins (dock replacement):

Friend Function NewCtlLocation(ByVal n As Integer, 
              ByVal ScrollPosX As Integer) As Point
    Const TopMargin As Integer = 5
    Const LeftMargin As Integer = 5

    Return New Point((n * 250) + ScrollPosX, 0)
End Function

Notes:

I have a vertical scroller which repeatedly adds up to 120 user controls which works well but it does not need/use ScrollControlIntoView and they never stack up like yours do. I suspect maybe because they are smaller. There is also at least a second or two before the next one can be added, which may matter. But, good to know.

It might be possible to use the ControlAdded event of the panel to do something, but it would likely amount to ScrollControlIntoView. Doing it once at the end only doesnt work, so using it as they are added is allowing something to get updated as you go.

With the right fiddling, you might be able to get Dock to work, but I suspect it may be part of the problem such as Height and Left set this way dont update the panel's internal scroll map.

Your boxes actually look narrower than 250 - is autosize on?

Suspend/Resume Layout hurt rather than help - they prevent the control from doing anything about the virtual area being populated. It should happen fast enough that no one will see anything. Results:

enter image description here

Works on My SystemTM

Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
  • "Your boxes actually look narrower than 250." The screenshots are based on a version of the code with smaller boxes. – Darryl Oct 24 '14 at 13:47