在这种情况下如何处理 ToolStripMenuItem?

How to deal with a ToolStripMenuItem in this case?

这是一个 CLR 项目 (.NET Framework 4.5)。

我有一个 ContextMenuStrip,当在 ToolStripMenuItem 上按下鼠标右键时会显示,因为 ToolStripMenuItem 没有 ContextMenuStrip 属性 我必须在 MouseUp 事件中设置右键单击事件,如下所示:

private: System::Void itemToolStripMenuItem_MouseUp(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {
    if (e->Button == System::Windows::Forms::MouseButtons::Right) {
        this->contextMenuStrip1->Show(this->menuStrip1, 
        this->menuStrip1->PointToClient(System::Drawing::Point(this->Cursor->Position.X, this->Cursor->Position.Y)));
    }
}

我希望 DropDown 在单击鼠标右键后保持打开状态,我知道有一个 AutoClose 属性 可以设置为 false:

private: System::Void contextMenuStrip1_Opening(System::Object^ sender, System::ComponentModel::CancelEventArgs^ e) {
    this->itemsToolStripMenuItem->DropDown->AutoClose = false;
}

问题是当将 AutoClose 设置为 false 时,它​​不会再关闭 DropDown。我找不到 Leave 事件来关闭 DropDown 并将 AutoClose 设置回 true。

我还需要在单击鼠标右键后使 DropDown 项目高亮显示。 这就是我想要实现的目标:

menu.DropDown.Closing += ...; 事件用于防止菜单在右键单击项目时关闭。 IsSelected 属性 在派生的 ToolStripMenuItem3 class 中被覆盖,以保持选中状态。

public class MyForm : Form {

    MenuStrip menubar = new MenuStrip { Dock = DockStyle.Top };
    ToolStripMenuItem menu = new ToolStripMenuItem("Menu");
    ToolStripDropDown menuRemove = new ToolStripDropDown();
    ToolStripMenuItem miRemove = new ToolStripMenuItem("Remove");
    ToolStripItem miClickedItem = null;
    Object tag = new Object();

    public MyForm() : base() {
        var item1 = new ToolStripMenuItem3("Item1");
        var item2 = new ToolStripMenuItem3("Item2");
        var item3 = new ToolStripMenuItem3("Item3");
        menu.DropDownItems.AddRange(new [] { item1, item2, item3 });
        menubar.Items.Add(menu);
        this.MainMenuStrip = menubar;

        Controls.Add(menubar);

        menuRemove.Items.Add(miRemove);

        menu.DropDown.MouseClick += DropDown_MouseClick;
        menu.DropDown.Closing += DropDown_Closing;

        menuRemove.Closed += menuRemove_Closed;
        menuRemove.ItemClicked += menuRemove_ItemClicked;
    }

    private class ToolStripMenuItem3 : ToolStripMenuItem {

        public ToolStripMenuItem3(String text) : base(text) { }

        public override bool Selected {
            get {
                bool b = base.Selected;
                return b || this.Tag != null;
            }
        }
    }

    void menuRemove_Closed(object sender, ToolStripDropDownClosedEventArgs e) {
        if (miClickedItem != null) {
            miClickedItem.Tag = null;
            miClickedItem.Invalidate();
            miClickedItem = null;
            menu.DropDown.Refresh();
        }

        // There is a bug that prevents the DropDown from auto-closing.
        // The menu.DropDown.Closing event is no longer received.
        // Calling Visible = true fixes this problem.
        menu.DropDown.Visible = true;
        bool inside = menu.DropDown.Bounds.Contains(Cursor.Position);
        if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked) { } // Remove clicked, keep menu open
        else if (e.CloseReason == ToolStripDropDownCloseReason.AppClicked) {
            if (!inside)
                menu.DropDown.Close(); // mouse was clicked outside of the menu bounds
        }
        else
            menu.DropDown.Close();
    }

    void menuRemove_ItemClicked(object sender, ToolStripItemClickedEventArgs e) {
        if (e.ClickedItem == miRemove) {
            menu.DropDown.Items.Remove(miClickedItem);
            miClickedItem = null;
            if (menu.DropDown.Items.Count == 0)
                menu.DropDown.Close(ToolStripDropDownCloseReason.CloseCalled);
        }
    }

    void DropDown_Closing(object sender, ToolStripDropDownClosingEventArgs e) {
        e.Cancel = (miClickedItem != null);
    }

    void DropDown_MouseClick(object sender, MouseEventArgs e) {
        var dropDown = (ToolStripDropDown) sender;
        if (e.Button == MouseButtons.Right) {
            Point p = e.Location;
            if (miClickedItem != null) {
                miClickedItem.Tag = null;
                miClickedItem.Invalidate();
            }

            miClickedItem = dropDown.GetItemAt(p);
            if (miClickedItem != null) {
                // miClickedItem is null if the mouse click is on the border of the menu
                miClickedItem.Tag = tag; // any non-null object
                menuRemove.Show(dropDown, p);
                miRemove.Select();
                dropDown.Invalidate();
            }
        }
    }

    protected override void Dispose(bool disposing) {
        base.Dispose(disposing);
        if (disposing) {
            if (menuRemove != null) {
                menuRemove.Dispose();
                menuRemove = null;
            }
        }
    }
}

ToolStripMenuItemX class:

public class MyForm2 : Form {

    MenuStrip menubar = new MenuStrip { Dock = DockStyle.Top };
    ToolStripMenuItem menu = new ToolStripMenuItem("Menu");

    public MyForm2() : base() {
        var item1 = new ToolStripMenuItemX("Item1");
        var item2 = new ToolStripMenuItemX("Item2");
        var item3 = new ToolStripMenuItemX("Item3");
        menu.DropDownItems.AddRange(new [] { item1, item2, item3 });
        menubar.Items.Add(menu);
        this.MainMenuStrip = menubar;

        Controls.Add(menubar);
    }
}

public class ToolStripMenuItemX : ToolStripMenuItem {

    DrawItemState closeState = DrawItemState.None;
    bool isHit = false;
    bool cancelClose = false;
    Point ptMouse = Point.Empty;
    ToolStripDropDown menuCurrent;
    bool dispose = false;

    public ToolStripMenuItemX(String text) : base(text) {
    }

    protected override void OnParentChanged(ToolStrip oldParent, ToolStrip newParent) {
        base.OnParentChanged(oldParent, newParent);
        if (newParent is ToolStripDropDown) {
            var menu = (ToolStripDropDown) newParent;
            menu.Closing += menu_Closing;
            menuCurrent = menu;
        }
    }

    void menu_Closing(object sender, ToolStripDropDownClosingEventArgs e) {
        if (cancelClose || isHit)
            e.Cancel = true;

        if (dispose) {
            // after the MouseUp event, this menu_Closing is called immediately after, one last time
            // unhook the event and dispose this item
            menuCurrent.Closing -= menu_Closing;
            menuCurrent = null;
            Dispose();
        }
    }

    protected override void OnMouseMove(MouseEventArgs mea) {
        ptMouse = mea.Location;
        var r = GetCloseRectangle();
        closeState = (ptMouse.X >= r.X ? DrawItemState.HotLight : DrawItemState.Selected);
        if (!isHit && mea.Button == MouseButtons.Left) {
            // originally the user clicked the item, but then moved the mouse over the X button
            // in this case, don't close the 
            cancelClose = (ptMouse.X >= r.X);
        }
        base.OnMouseMove(mea);
        Invalidate();
    }

    protected override void OnMouseLeave(EventArgs e) {
        base.OnMouseLeave(e);
        closeState = DrawItemState.None;
        cancelClose = false;
        isHit = false;
        Invalidate();
    }

    protected override void OnMouseDown(MouseEventArgs e) { 
        var r = GetCloseRectangle();
        isHit = r.Contains(e.Location);
        cancelClose = isHit;
        base.OnMouseDown(e);
        Invalidate();
    }

    protected override void OnMouseUp(MouseEventArgs e) {
        base.OnMouseUp(e);
        cancelClose = false;

        // OnMouseUp is called after the 'Closing' event is processed, so setting canceClose has no effect here
        var r = GetCloseRectangle();
        if (isHit) {
            if (r.Contains(e.Location)) {
                // remove the menu item, keep the drop down menu open
                menuCurrent.Items.Remove(this);
                menuCurrent.Invalidate();
                dispose = true; // flags this item for disposal
            }
        }
    }

    public override bool Selected {
        get {
            Rectangle r = GetCloseRectangle();
            return (ptMouse.X >= r.X ? false : base.Selected);
        }
    }

    private Rectangle GetCloseRectangle() {
        var r = this.ContentRectangle;
        int h = r.Height;
        return new Rectangle(r.Right - h, r.Y, h, h);
    }

    protected override void OnPaint(PaintEventArgs e) {
        base.OnPaint(e);
        var g = e.Graphics;
        if (closeState == DrawItemState.HotLight || closeState == DrawItemState.Selected) { 
            var r = GetCloseRectangle();

            if (closeState == DrawItemState.HotLight) {
                var renderer = (ToolStripProfessionalRenderer) this.DropDown.Renderer;
                var ct = renderer.ColorTable;
                Color c1 = (isHit ? ct.ButtonPressedHighlight : ct.ButtonSelectedHighlight);
                Color c2 = (isHit ? ct.ButtonPressedBorder : ct.ButtonSelectedBorder);

                using (var b = new SolidBrush(c1))
                    g.FillRectangle(b, r);

                using (var p = new Pen(c2))
                    g.DrawRectangle(p, r);
            }

            using (var sf = StringFormat.GenericDefault) {
                sf.Alignment = StringAlignment.Center;
                sf.LineAlignment = StringAlignment.Center;
                g.DrawString("x", this.Font, SystemBrushes.ActiveCaptionText, r, sf);
            }
        }
    }
}