【问题标题】:Prevent ToolStripMenuItems from jumping to second screen防止 ToolStripMenuItems 跳转到第二个屏幕
【发布时间】:2014-10-27 12:43:24
【问题描述】:

我有一个主要通过 NotifyIcon 的 ContextMenuStrip 操作的应用程序
ToolStripMenuItem 有多个级别,用户可以浏览它们。
问题是,当用户有两个屏幕时,MenuItems 会在没有可用空间时跳转到第二个屏幕。像这样:

如何强制他们停留在同一个屏幕上?我尝试在网上搜索,但找不到合适的答案。

这是我用来测试这个场景的一段代码示例:

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

【问题讨论】:

  • 尝试用表单设计器添加,看看生成了什么代码。也许你只是错过了一项任务。行为看起来很奇怪,例如“3 级下拉菜单”无法确定父级(与父级保持相同的屏幕)。
  • 这是表单设计器。我只是为了可读性对其进行了一些重构。 (将字段转换为本地,并删除了不必要的行)
  • 它一直在发生。我玩了一段时间,似乎如果上下文菜单足够近,就像图片中一样,它将扩展到第二个监视器,但是如果我将名称更改为“级别#工具提示”,这是一个字符少比原来的,然后不知何故它仍然在同一个屏幕上。

标签: c# winforms multiple-monitors notifyicon toolstripdropdown


【解决方案1】:

这并不容易,但是你可以在DropDownOpening事件中编写代码来查看菜单在哪里(它的边界),当前屏幕,然后设置ToolStripMenuItemDropDownDirection

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

此外,请确保您已连接 DropDownOpening 事件(您确实需要将其添加到每个菜单项):

level1ToolStripMenuItem += submenu_DropDownOpening;

【讨论】:

  • 谢谢伙计!我早就忘记了这个应用程序,但我现在就试试看:)
  • 这段代码似乎没有考虑到 menuItem.GetCurrentParent() - 获取工具条 - 具有相对于其父窗口的边界。如果该窗口没有被猛击到屏幕左侧,则计算可能会中断,并且菜单仍然会在错误的屏幕上打开。
【解决方案2】:

我是这样解决的:

  1. 为了让 ContextMenuStrip 本身在所需屏幕上打开,我使用以下方法创建了一个 ContextMenuStripEx:

    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 方法取自基类 ToolStripDropDown 通过 Reflector)

  1. 为了让嵌套的 MenuItem 在与 ContextMenuStrip 相同的屏幕上打开,我创建了一个 ToolStripMenuItemEx(它派生自 ToolStripMenuItem)。就我而言,它看起来像这样:

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

【讨论】:

    【解决方案3】:

    @David的代码如果菜单在第二个屏幕的左侧打开,则无法修复。我已经改进了该代码以在所有屏幕角落工作。

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

    【讨论】:

    • 此解决方案似乎不适用于任何有趣的配置。
    • 好吧,我已经使用这个多年了,没有任何问题
    • 好。我们显然有在某些方面不同的场景和/或需求。但是,当我尝试在我的程序(没有级联菜单)中用此代码替换已接受的解决方案时,行为从部分成功变为完全失败。也许还有一个更好的解决方案的空间,适用于每个人。我的解决方案对我有用,更短,并且避免了“回旋余地”的需要(如果可能的话,我更愿意在代码中避免)。
    【解决方案4】:

    我没有尝试tombam 的解决方案。但由于其他人似乎没有工作,我想出了这个简单的解决方案:

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

    请注意,在我的世界里,我并不关心多级级联菜单(如在 OP 中),因此我没有在那种情况下测试我的解决方案。但这适用于 ToolStrip 上的单个 ToolStripDropDownButton。

    【讨论】:

    • 虽然它不是必需的,但如果您在对答案投反对票时添加评论,说明问题是什么或哪里/为什么它无法工作,这对其他人(甚至可能对我)真的很有帮助.
    猜你喜欢
    • 1970-01-01
    • 2020-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-08
    • 2012-04-06
    • 2022-07-06
    相关资源
    最近更新 更多