0

I am developing a WinForms app with a layered interface like this:

My UI Layout

Ignore Panel A. Next to that I have a TabControl with 2 tabs. On the TabPage of the 2nd tab, I have a few controls at the top for filtering data, and below that I have Panel B, a FlowLayoutPanel which displays a list of records from a database. Each record is displayed in a Panel C, which contains a variety of text fields, depending on the contents of the record.

The UI looks pretty good when it loads (but see problem #2, below). The first issue that I'm struggling with is properly handling a resize event for the main window. When the user drags the side of the main window to make it wider, the main form resizes the TabPage and Panel B, then it calls a method on Panel B to resize its contents. The controls at the top of the TabPage automatically spread out evenly across the top, in accordance with their settings. So far so good.

Panel B goes through its list of C Panels, adjusting the width of each. (Later I will add code for C Panel to adjust the widths of each text field accordingly.)

But at the moment, I'm stuck on 2 possibly related problems:

  1. I can't seem to get the C Panels to change their width. I had initially hoped that with the right settings of AutoSize, AutoScroll, etc. I could get what I want, but when that didn't work, I decided to add a resize event handler to take more control. That's not currently working right.

  2. Even prior to the resize, I set the width of Panel B to fit within the TabPage, and I set the FlowDirection of Panel B to FlowDirection.TopDown, yet once there are enough C Panels to exceed the height of Panel B, Panel B becomes double-width, and there is a 2nd column of C Panels in it. What I really want is to add a vertical scroll bar to scroll through the "TopDown" list.

When I had set AutoScroll to true for Panel B, I didn't get a vertical scroll bar (which I expected for a TopDown panel); instead I got a horizontal scroll bar & the panel grew to the right! So I turned off AutoScroll.

Here's some relevant code:

    class MainForm : Form
{
    private int priorHeight;
    private int priorWidth;
    private System.Windows.Forms.TabPage MyTabPage;
    private SelectableListPanel PanelB = null;

    ...

    private void OnResize(object sender, EventArgs e)
    {
        this.SuspendLayout();

        int vDiff = this.Height - priorHeight;
        int hDiff = this.Width - priorWidth;

        MyTabPage.Height += vDiff;
        MyTabPage.Width += hDiff;

        PanelB.Height += vDiff;
        PanelB.Width += hDiff;

        // tell PanelB to resize its record panels
        PanelB.PropagateResize(hDiff);

        ResumeLayout(false);
        PerformLayout();

        priorHeight = this.Height;
        priorWidth = this.Width;
    }
}

class SelectableListPanel : FlowLayoutPanel
{
    protected List<Panel> panels = new List<Panel>();

    public SelectableListPanel(List<Panel> Panels) : base()
    {
        FlowDirection = FlowDirection.TopDown;
        AutoScroll = false;
        AutoSize = false;
        panels = Panels;
    }

    ...

    public void PropagateResize(int hDiff)
    {
        foreach (Panel genPanel in panels)
        {
            genPanel.Width += hDiff;
            // TODO:  genPanel.PropagateResize(hDiff)
        }
    }
}

Any thoughts / suggestions?

Todd Hoatson
  • 123
  • 2
  • 18
  • 1
    Guess you just need to set the Anchor properties of the controls that should resize with the parent container. – LarsTech May 09 '20 at 23:06
  • Set the `FlowLayoutPanel.FlowDirection` to `LeftToRight`, with `AutoSize = false` and `AutoScroll = true`. Make some tests, to learn how to correctly use the anchors and docking features of your controls. – Jimi May 09 '20 at 23:08
  • @LarsTech, I tried going back over things to set the Anchor properties - Left, Right, Top on TabPage & Panel B; Left, Right on C Panels - but it didn't improve anything... – Todd Hoatson May 13 '20 at 02:41
  • @Jimi, not sure how setting FlowDirection to LeftToRight is supposed to help anything. Please explain. – Todd Hoatson May 13 '20 at 02:42
  • `FlowDirection = LeftToRight` sets the priority to the Left. With `TopDown`, the priority goes to `Top`. But I didn't refer to just the `FlowDirection`, right? It's the combination of settings that matters. Also, the `Anchor` and `Dock` properties play a quite relevant role here. – Jimi May 13 '20 at 06:11
  • @Jimi, I did as you suggested and wrote a test program to understand more of how the anchor features worked. The test program worked, and based on what I learned there, I was able to get my C Panels to respond to the resize. But I found I had to replace the FlowLayoutPanel with a plain, vanilla Panel. I only wanted the C Panels to flow from Top to Bottom, but I now see that FlowDirection.TopDown does not do what the name would suggest. – Todd Hoatson May 19 '20 at 02:05
  • 1
    As described, if you want the child Controls of a FLP to flow top-down, you have to *Set the `FlowLayoutPanel.FlowDirection` to `LeftToRight`, set `AutoSize = false` and `AutoScroll = true`*. The child controls should be anchored to the FLP and the FLP needs to be anchored to the outer container: the Form itself or another container. The child Control will be attached to the LEFT. That's how it works. If you set `FlowDirection = TopDown` and `AutoSize = true`, the child Controls will flow from left to right (attached to the TOP), since the FLP will change its size to host new Controls. – Jimi May 19 '20 at 06:43
  • The `FlowDirection` defines a priority: when `TopDown`, the priority goes to the `Top`, then (if the current conditions dictate it) `Down`, restarting the Flow from the left. It's similar to how the Docking priority works. If you want to make a docked Control to stay on top of others, you have to call `SendToBack()` on it (not `BringToFront()`: your - the observer - specific *point of view* is irrelevant). This because `SendToBack()` gives the Control a lower Z-Order and Controls with lower Z-Order have more priority in the layout than Controls with higher Z-Order (the Layout mechanics count). – Jimi May 19 '20 at 06:54
  • @Jimi, you wrote "as described...", but you didn't write by whom it was described. The Microsoft online docs (https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.flowlayoutpanel.flowdirection?view=netcore-3.1) state: "FlowDirection - One of the FlowDirection values indicating the direction of consecutive placement of controls in the panel. The default is LeftToRight." This is nowhere near sufficient for someone who needs to refer to documentation. It contains none of the details you have now provided. – Todd Hoatson May 22 '20 at 02:15
  • @Jimi, I appreciate your more thorough responses, and they have been very helpful. I now am able to move toward a solution. I do not wish to be argumentative, I simply wish to passionately advocate for user-friendly/intuitive interfaces, not only for apps, but also for frameworks and APIs. We should be kind to each other. Yes, Layout mechanics count - for the implementer, not the user! The observer's point of view is everything! (BTW, where do they document that " Controls with lower Z-Order have more priority in the layout than Controls with higher Z-Order"?) – Todd Hoatson May 22 '20 at 02:18
  • We're devs here, not users. When I refer to [Layout](https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.layout) and [LayoutEngine](https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.layout.layoutengine) mechanics, it's a technical reference. The *user experience* is not taken into consideration, it's not relevant here: this is about the platform functionality and I know nothing about your app. The Z-Order/Z-Index priorities mechanics are common to all interfaces, Web included. Each with it's own specific implementations and behaviors, which you learn by experience. – Jimi May 22 '20 at 07:20
  • Note that these settings (`FlowDirection`, `AutoSize`, `AutoSizeMode`, `AutoScroll`) work together to produce different results. `FlowDirection = LeftToRight` + `AutoSize = true` is not the same as `FlowDirection = LeftToRight` + `AutoSize = false`. The combination (and the sequence) of these properties generates different layouts and layout responses. Some are meant to create a *dynamic* response, some are meant to be *fixed* layouts, or layouts that change in one direction only, with no changes in the *flow* but just in the size and/or number of the items shown and so on. – Jimi May 22 '20 at 08:24

2 Answers2

2

Wanted to post some code which allowed me to test the suggestions I was receiving in the comments. My demo program does this:

enter image description here

And here's the code for it:

    public Form1()
{
    Panel BluePanel;
    Panel OrangePanel;
    Panel GreenPanel;
    List<Panel> PurplePanels;

    InitializeComponent();
    SuspendLayout();

    // Field 1
    TextBox f1 = new TextBox();
    f1.Text = "Main Form";
    f1.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
    f1.Font = new Font("Microsoft Sans Serif", 14F, FontStyle.Regular,
       GraphicsUnit.Point, (byte) 0);
    f1.Location = new Point(12, 12);
    f1.Multiline = true;
    f1.Name = "Field1";
    f1.Size = new Size(460, 32);
    f1.TabIndex = 0;

    // BluePanel
    BluePanel = new Panel();
    BluePanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
    BluePanel.BackColor = Color.Blue;
    BluePanel.Location = new Point(12, 50);
    BluePanel.Name = "BluePanel";
    BluePanel.Size = new Size(460, 402);
    BluePanel.TabIndex = 1;

    // Field 2
    TextBox f2 = new TextBox();
    f2.Text = "Blue Panel";
    f2.Anchor = f1.Anchor;
    f2.Font = f1.Font;
    f2.Location = f1.Location;
    f2.Name = "Field2";
    f2.Size = new Size(BluePanel.Width - 24, f1.Height);
    f2.TabIndex = 0;
    BluePanel.Controls.Add(f2);

    // OrangePanel
    OrangePanel = new Panel();
    OrangePanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
    OrangePanel.BackColor = Color.Orange;
    OrangePanel.Location = new Point(f2.Left, f2.Bottom + 6);
    OrangePanel.Name = "OrangePanel";
    OrangePanel.Size = new Size(BluePanel.Width - 24, BluePanel.Height - (f2.Bottom + 6 + 12));
    OrangePanel.TabIndex = 1;
    BluePanel.Controls.Add(OrangePanel);

    // Field 3
    TextBox f3 = new TextBox();
    f3.Text = "Orange Panel";
    f3.Anchor = f1.Anchor;
    f3.Font = f1.Font;
    f3.Location = f1.Location;
    f3.Name = "Field3";
    f3.Size = new Size(OrangePanel.Width - 24, f1.Height);
    f3.TabIndex = 0;
    OrangePanel.Controls.Add(f3);

    // GreenPanel
//    GreenPanel = new FlowLayoutPanel();
//    GreenPanel.FlowDirection = FlowDirection.LeftToRight;
    GreenPanel = new Panel();
    GreenPanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
    GreenPanel.AutoScroll = true;
    GreenPanel.AutoSize = false;
    GreenPanel.BackColor = Color.Green;
    GreenPanel.Location = new Point(f3.Left, f3.Bottom + 6);
    GreenPanel.Name = "GreenPanel";
    GreenPanel.Size = new Size(OrangePanel.Width - 24, OrangePanel.Height - (f3.Bottom + 6 + 12));
    GreenPanel.TabIndex = 1;
    OrangePanel.Controls.Add(GreenPanel);

    // PurplePanels
    PurplePanels = new List<Panel>();
    for (int i = 0; i < 10; i++)
    {
        Panel PurplePanel = new Panel();
        PurplePanel.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;

        PurplePanel.BackColor = Color.Purple;
        PurplePanel.Size = new Size(GreenPanel.Width - 24, 32 + 24);
        PurplePanel.Location = new Point(12, 6 + (i * (32 + 24 + 6)));
        PurplePanel.Name = "PurplePanel" + i.ToString();
        PurplePanel.TabIndex = i;

        // Fields
        TextBox fi = new TextBox();
        fi.Text = "Purple Panel " + i.ToString();
        fi.Anchor = f1.Anchor;
        fi.Font = f1.Font;
        fi.Location = f1.Location;
        fi.Name = "Fieldi" + i.ToString();
        fi.Size = new Size(PurplePanel.Width - 24, f1.Height);
        fi.TabIndex = 0;
        PurplePanel.Controls.Add(fi);

        GreenPanel.Controls.Add(PurplePanel);
        PurplePanels.Add(PurplePanel);
    }

    ResumeLayout(false);
    PerformLayout();
}

To see what is different with the FlowLayoutPanel, simply un-comment the relevant lines in the Green Panel section, and comment out the "GreenPanel = new Panel();" line. There you will see that FlowDirection.LeftToRight does not produce the desired result.

I also want to give a shout out to @Jimi and @LarsTech for pointing me in the right direction.

Todd Hoatson
  • 123
  • 2
  • 18
0

general notice related to this topic:

Consider container A with autoscoll contains container B. Container B is not docked, autosize = true. You dynamically add content to B, awaiting B will grow up and container A shows scroll bar. But it's not happening.

In this case check B has max 2 anchors defined instead all of them (Anchor.Left | Anchor.Top | Anchor.Bottom | Anchor.Right)

Joe
  • 1