I want to shown a context menu where the menu items are images that are laid out in a grid. However, when I set LayoutStyle
to ToolStripLayoutStyle.Table
in the ToolStripDropDown of a menu, it will only give a grid layout of the menu items if a new ToolStripDropDown object is created.
My problem is that I can create and assign a new ToolStripDropDown for a sub-menu, but not for ContextMenuStrip, because it is the ToolStripDropDown.
The following code demonstrates the problem. It will display a context menu that contains colours swatch images and also has two sub-menus with the same images. All three menus have the LayoutStyle
property set to ToolStripLayoutStyle.Table
, but only one will actually show as a grid.
private void FillDropDown(ToolStripDropDown drop_down)
{
// Set the drop down to a 2 column table layout
drop_down.LayoutStyle = ToolStripLayoutStyle.Table;
TableLayoutSettings table_layout_settings = (TableLayoutSettings)drop_down.LayoutSettings;
table_layout_settings.ColumnCount = 2;
// Fill the menu with some colour swatches
Color[] colours = { Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Purple };
foreach (Color colour in colours) {
ToolStripMenuItem item = new ToolStripMenuItem();
Bitmap swatch = new Bitmap(64, 64);
using (Graphics g = Graphics.FromImage(swatch))
using (SolidBrush b = new SolidBrush(colour)) {
g.FillRectangle(b, 0, 0, 64, 64);
}
item.Image = swatch;
item.DisplayStyle = ToolStripItemDisplayStyle.Image;
item.Margin = new Padding(2, 2, 2, 2);
drop_down.Items.Add(item);
}
}
private void ShowColorMenu(Point screen_location)
{
ContextMenuStrip context_menu = new ContextMenuStrip();
// The root context menu will not layout as a grid
FillDropDown(context_menu);
// This sub-menu will not layout as a grid
ToolStripMenuItem sub_menu = new ToolStripMenuItem("Sub-menu");
FillDropDown(sub_menu.DropDown);
context_menu.Items.Add(sub_menu);
// A sub-menu will layout as a grid if we create a new ToolStripDropDown for it
ToolStripMenuItem grid_sub_menu = new ToolStripMenuItem("Grid Sub-menu");
ToolStripDropDown new_drop_down = new ToolStripDropDown();
FillDropDown(new_drop_down);
grid_sub_menu.DropDown = new_drop_down;
context_menu.Items.Add(grid_sub_menu);
context_menu.Show(screen_location);
}
On my machine the result appears as follows:
I would like to have a grid of images in the root of the context menu. It would also be nice to understand why it is behaving like this. I have looked through the .NET reference source, but that didn't help on this occasion.
The ContextMenuStrip cannot show its ToolStripMenuItems in a Table layout, because of a limitation in Menu presentation/layout and you also cannot cast ContextMenuStrip to ToolStripDropDown (or the other way around; it's a wrapper around ToolStripDropDownMenu, it cannot be transformed into its indirect ancestor: it will retain its specific functionality (e.g., you can see both a TextBox and a ListBox as Control
, but it doesn't mean that, now, setting the Text of a ListBox will actually show a text somewhere, just because the Text property belongs to the Control class).
But you can directly use and show a ToolStripDropDown the same way as a ContextMenuStrip. The ToolStripDropDown's LayoutSettings can be cast directly to TableLayoutSettings and a LayoutStyle of type ToolStripLayoutStyle.Table is fully supported.
In the example, a ToolStripDropDown object, containing ToolStripMenuItems arranged in a Table layout, is used as a ContextMenuStrip to select a colored Image to be applied to a PictureBox Control, while the Color name is shown in a Label control.
The dropdown menu is created when the Form is initialized, it's shown clicking the Mouse right Button inside the Form's ClientArea and disposed of when the Form closes:
Note1: here, I'm using a Lambda to subscribe to the ToolStripDropDown.ItemClicked
event. It's however preferable, with this type of control, to use a method delegate instead.
Note2: the ToolStripDropDown is disposed of calling contextColorMenu.Dispose();
. If the container Form is opened and closed frequently, it may be better to explicitly dispose of the ToolStripMenuItems Images.
using System.Drawing;
using System.Windows.Forms;
public partial class SomeForm : Form
{
private ToolStripDropDown contextColorMenu = null;
public SomeForm()
{
InitializeComponent();
contextColorMenu = new ToolStripDropDown();
contextColorMenu.ItemClicked += (o, a) => {
// Assign the selected Bitmap to a PitureBox.Image
picColor.Image = a.ClickedItem.Image;
// Show the Color description in a Label
lblColor.Text = ((Color)a.ClickedItem.Tag).ToString();
};
FillDropDown(contextColorMenu);
}
private void ShowColorMenu(Point location) => contextColorMenu.Show(location);
private void FillDropDown(ToolStripDropDown dropDown)
{
dropDown.LayoutStyle = ToolStripLayoutStyle.Table;
(dropDown.LayoutSettings as TableLayoutSettings).ColumnCount = 2;
(dropDown.LayoutSettings as TableLayoutSettings).GrowStyle = TableLayoutPanelGrowStyle.AddRows;
Color[] colors = { Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Purple };
foreach (Color color in colors) {
var swatch = new Bitmap(64, 64);
using (var g = Graphics.FromImage(swatch)) {
g.Clear(color);
}
var item = new ToolStripMenuItem() {
DisplayStyle = ToolStripItemDisplayStyle.Image,
Image = swatch,
Tag = color,
Margin = new Padding(2),
Padding = new Padding(2, 1, 2, 1) // Fine tune the Items' Cell border
};
dropDown.Items.Add(item);
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Right) {
ShowColorMenu(MousePosition);
}
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
contextColorMenu?.Dispose();
}
}