How to add a scrollbar to a Menu item dropdown? - c#

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.

Related

How to align some MenuItems to the right with MenuStrip LayoutStyle set to Flow?

I would like to make it so that some of the buttons on a MenuStrip aligned to the right of the MenuStrip. For example, Focus ON and Focus OFF on the right side of the menu strip:
MenuStrip
I can get this to work if I set the MenuStrip's LayoutStyle to StackWithOverFlow, but then if the window size is decreased the menu items get cropped:
MenuStrip with LayoutStyle set to StackWithOverFlow
How can I make it so that I can align the menu items to the right with the MenuStrip LayoutStyle set to Flow? That way, when the form size decreases, the menu items go to the next line?
Also, how can I make it that the other controls are pushed down a little bit when the MenuStrip makes a new line for more menu items?
In order to right-align some menu items, you need to set the item's Alignment value to Right. However, right alignment only applies in the StackWithOverflow layout styles. If you are using Flow alignment style, then the items will always flow from left-to-right.
Also, when you right-align items in StackWithOverflow layout style, the item flow from the outside in, so if your original layout is 1 2 3 4 5, your right-aligned items would be 1 2 3 <gap> 5 4.
The solution to your problem is in two parts:
Track the SizeChanged event to determine if you need Flow or StackWithOverflow based on the width of all menu items and the available width of the window.
If you have to change the layout style, swap the right-aligned items so that they appear in the correct order in either layout style.
private void Form1_SizeChanged(object sender, EventArgs e)
{
int width = 0;
// add up the width of all items in the menu strip
foreach (ToolStripItem item in menuStrip1.Items)
width += item.Width;
// get the current layout style
ToolStripLayoutStyle oldStyle = menuStrip1.LayoutStyle;
// determine the new layout style
ToolStripLayoutStyle newStyle = (width < this.ClientSize.Width)
? menuStrip1.LayoutStyle = ToolStripLayoutStyle.StackWithOverflow
: menuStrip1.LayoutStyle = ToolStripLayoutStyle.Flow;
// do we need to change layout styles?
if (oldStyle != newStyle)
{
// update the layout style
menuStrip1.LayoutStyle = newStyle;
// swap the last item with the second-to-last item
int last = menuStrip1.Items.Count - 1;
ToolStripItem item = menuStrip1.Items[last];
menuStrip1.Items.RemoveAt(last);
menuStrip1.Items.Insert(last - 1, item);
}
}
The process of swapping the right-aligned items will have to be adapted more carefully if you have more than two items. The code above simply swaps them, but if you have three or more items, you'll need to reverse their order entirely.

WrapPanel creates a new column for each dynamically expanding control

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.

Why is the ListBox's DesiredSize.Width always zero?

I am creating a custom control in C#, and want to have a grid cell that contains a ListBox, which can be hidden or shown as desired. Hiding it is easy, I just set the Width to zero, however when I want to show it, I need to know the width that the ListBox would like to use.
I thought that DesiredSize.Width would give me this vale, but it's always zero, even after calling Measure(). Here is the code I'm using to show the ListBox...
_lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
_lb.Width = _lb.DesiredSize.Width;
Any ideas how I find the desired width?
If your ListBox is in the cell of a grid, give that grid column a name. Then in code, you can access the .ActualWidth property of that grid column and use that value to set the width of your ListBox.
That assumes of course that the width of your grid column is not set to Auto, because that would still give you a 0 value.
_lb.Width = myGridColumn.ActualWidth
You might need to subtract a little bit from the column width to make your control fit nicely.
EDIT
One thing that I've found is that the ListBox must have items added to it before it will return anything other than 0 when it is measured.
string myItem = "Don't ask for a bath in Athabaska";
_lb.Items.Add(myItem);
_lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
double width = _lb.DesiredSize.Width;
As long as the ListBox has already been added to the window/usercontrol/grid, the above code returns a value of 227.53 for the width variable; using my defaults for font family and size.
If the ListBox has not been added to the window, or it doesn't have any items in it, it will return 0 for the .DesiredSize.Width property.
Also, if the .Visibility property is set to Collapsed instead of Hidden, the width will be 0.
Don't set the width to 0 when starting. Leave the width alone initially, set the .Visibility to Hidden. It will render to the needed width, but won't be shown. Then you can measure it and start playing around with the width.

Keeping distance between panels the same

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);

Is there a way to get the bounds of a TabPage in a Winforms TabControl?

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.

Categories