Reputation: 5835
I have an application that is mostly operated through NotifyIcon's ContextMenuStrip
There are multiple levels of ToolStripMenuItems and the user can go through them.
The problem is, that when the user has two screen, the MenuItems jump to second screen when no space is available. like so:
How can I force them to stay on the same screen? I've tried to search through the web but couldn't find an appropriate answer.
Here is a sample piece of code i'm using to test this senario:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var resources = new ComponentResourceManager(typeof(Form1));
var notifyIcon1 = new NotifyIcon(components);
var contextMenuStrip1 = new ContextMenuStrip(components);
var level1ToolStripMenuItem = new ToolStripMenuItem("level 1 drop down");
var level2ToolStripMenuItem = new ToolStripMenuItem("level 2 drop down");
var level3ToolStripMenuItem = new ToolStripMenuItem("level 3 drop down");
notifyIcon1.ContextMenuStrip = contextMenuStrip1;
notifyIcon1.Icon = ((Icon)(resources.GetObject("notifyIcon1.Icon")));
notifyIcon1.Visible = true;
level2ToolStripMenuItem.DropDownItems.Add(level3ToolStripMenuItem);
level1ToolStripMenuItem.DropDownItems.Add(level2ToolStripMenuItem);
contextMenuStrip1.Items.Add(level1ToolStripMenuItem);
}
}
Upvotes: 6
Views: 1997
Reputation: 563
Not answering the question, but here is how I solved if for ContextMenuStrip:
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
// Get width of widest child item (skip separators!)
var maxWidth = contextMenuStrip1.Items.OfType<ToolStripMenuItem>().Select(m => m.Width).Max();
var currentScreen = Screen.FromPoint(e.Location);
var dir = contextMenuStrip1.DefaultDropDownDirection;
if (dir == ToolStripDropDownDirection.Right && p.X + maxWidth > currentScreen.Bounds.Right)
{
dir = ToolStripDropDownDirection.Left;
}
else if (dir == ToolStripDropDownDirection.Left && p.X - maxWidth < currentScreen.Bounds.Left)
{
dir = ToolStripDropDownDirection.Right;
}
contextMenuStrip1.Show(this, e.Location, dir);
}
Upvotes: 0
Reputation: 509
I did not try the solution by tombam. But since the others didn't seem to work, I came up with this simple solution:
private void MenuDropDownOpening(object sender, EventArgs e)
{
var menuItem = sender as ToolStripDropDownButton;
if (menuItem == null || menuItem.HasDropDownItems == false)
return; // not a drop down item
// Current bounds of the current monitor
var upperRightCornerOfMenuInScreenCoordinates = menuItem.GetCurrentParent().PointToScreen(new Point(menuItem.Bounds.Right, menuItem.Bounds.Top));
var currentScreen = Screen.FromPoint(upperRightCornerOfMenuInScreenCoordinates);
// Get width of widest child item (skip separators!)
var maxWidth = menuItem.DropDownItems.OfType<ToolStripMenuItem>().Select(m => m.Width).Max();
var farRight = upperRightCornerOfMenuInScreenCoordinates.X + maxWidth;
var currentMonitorRight = currentScreen.Bounds.Right;
menuItem.DropDownDirection = farRight > currentMonitorRight ? ToolStripDropDownDirection.Left :
ToolStripDropDownDirection.Right;
}
Note that in my world, I was not concerned about multiple levels of cascading menus (as in the OP), so I did not test my solution in that scenario. But this works correctly for a single ToolStripDropDownButton on a ToolStrip.
Upvotes: -2
Reputation: 1871
It is not easy, but you can write code in the DropDownOpening
event to look at where the menu is at (its bounds), the current screen, and then set the DropDownDirection
of the ToolStripMenuItem
:
private void submenu_DropDownOpening(object sender, EventArgs e)
{
ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
if (menuItem.HasDropDownItems == false)
{
return; // not a drop down item
}
// Current bounds of the current monitor
Rectangle Bounds = menuItem.GetCurrentParent().Bounds;
Screen CurrentScreen = Screen.FromPoint(Bounds.Location);
// Look how big our children are:
int MaxWidth = 0;
foreach (ToolStripMenuItem subitem in menuItem.DropDownItems)
{
MaxWidth = Math.Max(subitem.Width, MaxWidth);
}
MaxWidth += 10; // Add a little wiggle room
int FarRight = Bounds.Right + MaxWidth;
int CurrentMonitorRight = CurrentScreen.Bounds.Right;
if (FarRight > CurrentMonitorRight)
{
menuItem.DropDownDirection = ToolStripDropDownDirection.Left;
}
else
{
menuItem.DropDownDirection = ToolStripDropDownDirection.Right;
}
}
Also, make sure you have the DropDownOpening
event hooked up (you would really need to add this to every menu item):
level1ToolStripMenuItem += submenu_DropDownOpening;
Upvotes: 7
Reputation: 167
I have solved it this way:
For the ContextMenuStrip itself to open on a desired screen, I created a ContextMenuStripEx with the following methods:
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
Rectangle dropDownBounds = new Rectangle(x, y, width, height);
dropDownBounds = ConstrainToBounds(Screen.FromPoint(dropDownBounds.Location).Bounds, dropDownBounds);
base.SetBoundsCore(dropDownBounds.X, dropDownBounds.Y, dropDownBounds.Width, dropDownBounds.Height, specified);
}
internal static Rectangle ConstrainToBounds(Rectangle constrainingBounds, Rectangle bounds)
{
if (!constrainingBounds.Contains(bounds))
{
bounds.Size = new Size(Math.Min(constrainingBounds.Width - 2, bounds.Width), Math.Min(constrainingBounds.Height - 2, bounds.Height));
if (bounds.Right > constrainingBounds.Right)
{
bounds.X = constrainingBounds.Right - bounds.Width;
}
else if (bounds.Left < constrainingBounds.Left)
{
bounds.X = constrainingBounds.Left;
}
if (bounds.Bottom > constrainingBounds.Bottom)
{
bounds.Y = constrainingBounds.Bottom - 1 - bounds.Height;
}
else if (bounds.Top < constrainingBounds.Top)
{
bounds.Y = constrainingBounds.Top;
}
}
return bounds;
}
(ConstrainToBounds method is taken from the base class ToolStripDropDown via Reflector)
for the nested MenuItems to open on the same screen as ContextMenuStrip, I created a ToolStripMenuItemEx (which derives from ToolStripMenuItem). In my case it looks like this:
private ToolStripDropDownDirection? originalToolStripDropDownDirection;
protected override void OnDropDownShow(EventArgs e)
{
base.OnDropDownShow(e);
if (!Screen.FromControl(this.Owner).Equals(Screen.FromPoint(this.DropDownLocation)))
{
if (!originalToolStripDropDownDirection.HasValue)
originalToolStripDropDownDirection = this.DropDownDirection;
this.DropDownDirection = originalToolStripDropDownDirection.Value == ToolStripDropDownDirection.Left ? ToolStripDropDownDirection.Right : ToolStripDropDownDirection.Left;
}
}
Upvotes: 0
Reputation: 165
The code of @David does not fix if the menu is opened in the left side of second screen. I have improved that code to work on all screen corner.
private void subMenu_DropDownOpening(object sender, EventArgs e)
{
ToolStripMenuItem mnuItem = sender as ToolStripMenuItem;
if (mnuItem.HasDropDownItems == false)
{
return; // not a drop down item
}
//get position of current menu item
var pos = new Point(mnuItem.GetCurrentParent().Left, mnuItem.GetCurrentParent().Top);
// Current bounds of the current monitor
Rectangle bounds = Screen.GetWorkingArea(pos);
Screen currentScreen = Screen.FromPoint(pos);
// Find the width of sub-menu
int maxWidth = 0;
foreach (var subItem in mnuItem.DropDownItems)
{
if (subItem.GetType() == typeof(ToolStripMenuItem))
{
var mnu = (ToolStripMenuItem) subItem;
maxWidth = Math.Max(mnu.Width, maxWidth);
}
}
maxWidth += 10; // Add a little wiggle room
int farRight = pos.X + mnuMain.Width + maxWidth;
int farLeft = pos.X - maxWidth;
//get left and right distance to compare
int leftGap = farLeft - currentScreen.Bounds.Left;
int rightGap = currentScreen.Bounds.Right - farRight;
if (leftGap >= rightGap)
{
mnuItem.DropDownDirection = ToolStripDropDownDirection.Left;
}
else
{
mnuItem.DropDownDirection = ToolStripDropDownDirection.Right;
}
}
Upvotes: -1