防止ToolStripMenuItems跳转到第二个屏幕

问题描述 投票:0回答:5

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

enter image description here

如何强制它们保持在同一屏幕上?我尝试在网络上搜索但找不到合适的答案。

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

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);
    }
}
c# winforms multiple-monitors notifyicon toolstripdropdown
5个回答
6
投票

这并不容易,但是你可以在

DropDownOpening
事件中编写代码来查看菜单所在的位置(其边界)、当前屏幕,然后设置
DropDownDirection
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;
    }
}

另外,请确保您已连接

DropDownOpening
事件(您确实需要将其添加到每个菜单项中):

level1ToolStripMenuItem += submenu_DropDownOpening;

0
投票

我是这样解决的:

  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 方法是通过 Reflector 从基类 ToolStripDropDown 获取的)

  1. 为了使嵌套的 MenuItems 在与 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;
        }
    }
    

0
投票

没有回答问题,但这是我如何解决 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 = e.Location.X + maxWidth > currentScreen.Bounds.Right ?
            ToolStripDropDownDirection.Left : ToolStripDropDownDirection.Right;
        contextMenuStrip1.Show(this, e.Location, dir);
    }

-1
投票

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

-2
投票

我没有尝试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 可以正常工作。

© www.soinside.com 2019 - 2024. All rights reserved.