I have several panels in a windows form application, they are sorted in two columns and maximally 4 rows, so maximally 8 panels. The number of elements included in every single panel changes during the runtime, so not to waste place on monitor I set all of them to autosize. The problem is, I dont know how I can keep them placed correctly, like how to make that when the first one shrinks that the other three come up a bit so there is not too much space between them.
Try to use the TableLayoutPanel or the FlowLayoutPanel (or possibly even a SplitContainer). They all can be very useful for this kind of task. You find them in the section Containers in the Toolbox. You can keep the right distance by setting the margins of the panels appropriately. The TableLayoutPanel gives you different options for sizing the rows and columns (absolute or percent size or auto). Also by working with the Dock or the Anchor properties of the panels and controls you can attain a dynamic behavior when resizing or adding and removing controls.
You might also have to set the MinimumSize and MaximumSize properties of the controls.
You can add controls like this the TableLayoutPanel
int count = tableLayoutPanel1.Controls.Count;
int newColumn = count % 2;
int newRow = count / 2;
if (newRow >= tableLayoutPanel1.RowCount) {
tableLayoutPanel1.RowCount++;
// Set appropriate row style
tableLayoutPanel1.RowStyles.Add(new RowStyle { SizeType = SizeType.AutoSize });
}
var newControl = new Button { Dock = DockStyle.Fill };
tableLayoutPanel1.Controls.Add(newControl, newColumn, newRow);
Related
There are too many items in the menu strip item.
Like this:
It looks very long and bad. I want to add a scroll bar to menu strip items.
I want to see 3 of these items with the help of the scroll bar.
How can I do it?
You can set the maximum height of a MenuItem's DropDown using the ToolStripMenuItem.DropDown MaximumSize property.
You can determine the height of the DropDown based on the maximum number of Items you want to show. Or any other measure that makes sense in your scenario (maybe a measure that fits the current ClientSize.Height of a Form).
To specify a height relative to a maximum number of sub items (here, maxSubMenuItems), sum the Height of the first maxSubMenuItems sub items and use this measure to set the MaximumSize property.
The standard (top and bottom) scroll buttons will appear.
Here, I'm using a maxHeight + (maxHeight / maxSubMenuItems) value, to add some space, at the top and bottom of the dropdown, otherwise a menu may not fit in and it also looks better :)
Insert this code in the Form's Constructor (after InitializeComponent()) or in Form.Load:
int maxSubMenuItems = 6;
if (toolStripMenuItem1.DropDownItems.Count > maxSubMenuItems) {
int maxHeight = toolStripMenuItem1.DropDownItems
.OfType<ToolStripMenuItem>()
.Take(maxSubMenuItems)
.Sum(itm => itm.Height);
toolStripMenuItem1.DropDown.MaximumSize =
new Size(toolStripMenuItem1.DropDown.Width, maxHeight + (maxHeight / maxSubMenuItems));
}
► This can come in handy when you have to show a list of sub-items that represent Fonts, for example. So you may want to show a string drawn using the Font name in the list. Or similar situations, when you have to present a single, potentially long, dropdown.
In other cases, try to limit the number of items per ToolStripMenuItem using sub menus.
I have a vertical WarpPanel that is populated with controls at runtime. The types and number of controls is determined at runtime. It works good with controls that have a fixed height, but controls that expand according to their contents (e.g. Listbox) often create a new column. I somehow need to force the panel to place the controls in the last column as the other, fixed height controls UNLESS the space available in the column is less than MinHeight of the control we are trying to place. Setting Height or MaxHeight for the controls is not an option.
The image below demonstrates the problem. The two listboxes' widths are the same, but instead of putting them in the same column, one of them ends up half-invisible.
Instead of that I would expect to get this:
Is there any way to implement this without making/using a custom panel?
Code:
**Panel:**
<WrapPanel x:Name="wp" Orientation="Vertical">
**Adding controls:**
private void AddControl(bool isListBox)
{
if (isListBox)
{
var lb = new ListBox();
lb.MinHeight = 310;
lb.Width = 310;
lb.MaxWidth = 310;
lb.MinWidth = 310;
wp.Children.Add(lb);
}
else
{
var cb = new ComboBox();
cb.Width = 310;
cb.MaxWidth = 310;
wp.Children.Add(cb);
}
}
The problem here is that the WrapPanel is always going to give the ListBox as much space as it wants, up to the available height in the WrapPanel. What you want to have happen is something more like a UniformGrid effect, but only for expanding Height elements in the column and only as long as the MinHeight constraint isn't violated. This gets a bit tricky, especially if you have other fixed height elements in-between the ListBox elements or other elements with different MinHeight constraints.
It's possible to do the computation, but I think you'll need to create a custom Panel to get this behavior. Basically, it would work like the WrapPanel code, but when you have variable height elements (elements whose Measure returns unbounded size in the wrap dimension), it needs to look at their MinHeight and accumulate these with the fixed Height elements in the same column, ultimately dividing the remaining non-fixed Height by the number of variable elements, to produce the height(s) that will be provided in the Arrange pass.
I'm facing with nasty performance issue when using TableLayoutPanel. I have a simple user control with RadioButton and LinkLabel. Text of LinkLabel is dynamic so entire control have AutoSize property set to true.
Now I have a Panel with AutoScroll set to true and TableLayoutPanel auto sized and with 2 columns inside it. This TableLayoutPanel is populated with above-mentioned user controls:
private void PopulateLocationItemsTable(Control[] Controls)
{
//Suspend outher Panel and set AutoScroll to false just in case.
panelLocationItemsTableCountainer.SuspendLayout();
panelLocationItemsTableCountainer.AutoScroll = false;
//Suspend TableLayoutPanel
tableLocationItems.SuspendLayout();
Controls = Controls.OrderBy(c => c.Text).ToArray();
//Populate left column
int verticalPosition = 3;
int leftColumnControlsNumber = Controls.Length / 2;
for (int i = 0; i < leftColumnControlsNumber; i++)
{
tableLocationItems.Controls.Add(Controls[i], 0,0);
Controls[i].Location = new Point(10, verticalPosition);
verticalPosition += 17;
}
//Populate right column
verticalPosition = 3;
for (int i = leftColumnControlsNumber; i < Controls.Length; i++)
{
tableLocationItems.Controls.Add(Controls[i], 0, 1);
Controls[i].Location = new Point(10, verticalPosition);
verticalPosition += 17;
}
//Resume TableLayoutPanel
tableLocationItems.ResumeLayout(true);
//And restore outher Panel state
panelLocationItemsTableCountainer.AutoScroll = true;
panelLocationItemsTableCountainer.ResumeLayout(true);
}
The problem is that user controls initially populated in FormLoad event and the Form just hangs for around 10 seconds before it actually appears. This is completely unacceptable for me.
This issue goes away if I set AutoSize property of user control to false. I also was tried to put user controls directly to the outher Panel and it also works fine. The problem is just with TableLayoutPanel. Is anyone faced such issue and found the solution? Of corse I can place my user controls myself directly to the Panel calculating right coordinales but using TableLayoutPanel is a "correct" way for such tasks.
Using the TableLayoutPanel is the right approach but you'll want to think of the columns in that control as static widths. I had an application where I faced almost exactly the same problem using that panel and realized I'd just been looking at it all wrong.
If there are two columns, and my container (e.g. a form) is 300 pixels wide, then each column is 150 pixels wide (minus padding and stuff) so the controls inside those columns have to react rather than the columns reacting.
The other reason you really need to look at it this way is because the engine doesn't layout everything in memory first (like the WPF framework does) so it's extremely in efficient at its core since it commits the changes immediately.
I have a Form with only a single TabControl, with many tabs, where each tab has only square buttons side by side. I am trying to make it so that when the user clicks on a tab, the form resizes itself to a size where you can see all the buttons in a particular tab or a size where you can see all the tabs, whichever is greater.
I am just curious if there is a way to query where the last control in a tab page is? So I can just do something like:
tabForm.Width = currentTabPage.UsedContentBorder + 10;
Or do I have to do this by adding all the controls and the sizes between them, etc?
You want to find out the maximum coordinates of all controls in a specific tab? Easy with LINQ:
int right = tab.Controls.Cast<Control>().Max(c => c.Right);
int bottom = tab.Controls.Cast<Control>().Max(c => c.Bottom);
Now, to properly choose the size of the form, I imagine you just have to figure out how much larger the Form is than its TabPages... I would guess something like this:
int extraWidth = form.Width - tabControl.SelectedTab.Width;
int extraHeight = form.Height - tabControl.SelectedTab.Height;
Then you just do
form.Size = new Size(right + extraWidth, bottom + extraHeight);
(the TabControl will resize automatically if its Anchor property is set to all four sides.) It occurs to me that this may malfunction if the user resizes the form very small... you may be able to compensate by calculating extraWidth and extraHeight in the Form.Load event and then saving those values for when you need them later.
.NET Framework / C# / Windows Forms
I'd like the FlowLayoutPanel to automatically adjust its width or height depending on number of controls inside of it. It also should change the number of columns/rows if there is not enough space (wrap its content). The problem is that if I set autosize then the flowlayoutpanel doesn't wrap controls I insert. Which solution is the best?
Thanks!
Set the FlowLayoutPanel's MaximumSize to the width you want it to wrap at. Set WrapContents = true.
Have you tried using the TableLayoutPanel? It's very useful for placing controls within cells.
There is no such thing like impossible in software development. Impossible just takes longer.
I've investigated the problem. If there is really need for Flow Layout, it can be done with a bit of work. Since FlowLayoutPanel lays out the controls without particularly thinking about the number of rows/columns, but rather on cumulative width/height, you may need to keep track of how many controls you've already added. First of all, set the autosize to false, then hook your own size management logic to the ControlAdded/ControlRemoved events. The idea is to set the width and height of the panel in such a way, that you'll get your desired number of 'columns' there
Dirty proof of concept:
private void flowLayoutPanel1_ControlAdded(object sender, ControlEventArgs e)
{
int count = this.flowLayoutPanel1.Controls.Count;
if (count % 4 == 0)
{
this.flowLayoutPanel1.Height = this.flowLayoutPanel1.Height + 70;
}
}
if the panel has initially width for 4 controls, it will generate row for new ones. ControlRemoved handler should check the same and decrease the panel height, or get all contained controls and place them again. You should think about it, it may not be the kind of thing you want. It depends on the usage scenarios. Will all the controls be of the same size? If not, you'd need more complicated logic.
But really, think about table layout - you can wrap it in a helper class or derive new control from it, where you'd resolve all the control placing logic. FlowLayout makes it easy to add and remove controls, but then the size management code goes in. TableLayout gives you a good mechanism for rows and columns, managing width and height is easier, but you'd need more code to change the placement of all controls if you want to remove one from the form dynamically.
If possible, I suggest you re-size the FlowLayoutPanel so that it makes use of all the width that is available and then anchor it at Top, Left and Right. This should make it grow in height as needed while still wrapping the controls.
I know this is an old thread but if anyone else wonders on here then here's the solution I produced - set autosize to true on the panel and call this extension method from the flow panel's Resize event:
public static void ReOrganise(this FlowLayoutPanel panel)
{
var width = 0;
Control prevChildCtrl = null;
panel.SuspendLayout();
//Clear flow breaks
foreach (Control childCtrl in panel.Controls)
{
panel.SetFlowBreak(childCtrl, false);
}
foreach (Control childCtrl in panel.Controls)
{
width = width + childCtrl.Width;
if(width > panel.Width && prevChildCtrl != null)
{
panel.SetFlowBreak(prevChildCtrl, true);
width = childCtrl.Width;
}
prevChildCtrl = childCtrl;
}
panel.ResumeLayout();
}
Are you adding the controls dynamically basing on the user's actions? I'm afraid you'd need to change the FlowLayout properties on the fly in code, when adding new controls to it, then refreshing the form would do the trick.