【问题标题】:How to deal with a ToolStripMenuItem in this case?在这种情况下如何处理 ToolStripMenuItem?
【发布时间】:2021-02-06 04:51:48
【问题描述】:

这是一个 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 项目。 这就是我想要实现的目标:

【问题讨论】:

    标签: .net winforms toolstrip


    【解决方案1】:

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

    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 类:

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

    【讨论】:

    • 赞成,这个解决方案似乎可行,但需要做一些调整,我的自定义控件在 .dll 库中,所以我需要摆脱 MyForm 类型,可能是什么使用而不是 MyForm 实例来存储 miClickedItem?当右键单击另一个ToolStripMenuItemmenuRemove_Closed 会关闭DropDown,我的意思是它应该让用户右键单击另一个项目而不关闭DropDown。你能用这些调整更新你的答案吗?所以我会把它标记为接受的答案。
    • @Simple 解决方案已更新。移除项目现在可以使用,如果项目被移除,菜单将保持打开状态。如果显示 Remove 菜单,并且用户在其他地方单击,则两个菜单都将关闭。 MyForm 被替换为 non-null 对象,设置为 Tag 属性以保持 Selected 状态。
    • @Simple 我重现了异常,并更新了答案。您是否考虑过具有内置删除按钮的自定义ToolStripMenuItemX? (参见添加到答案的图片)。
    • @Simple 答案已更新为包含ToolStripMenuItemX 类。整合这两种解决方案已经超出了我目前的时间。
    • 另一种思路:如果弹出菜单只包含Edit和Delete两项,那么在X按钮之前画一个Edit按钮。 pencil 字符(使用Segoe UI Symbol 字体)或... 可用于编辑按钮Text
    猜你喜欢
    • 2014-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-12
    • 2021-08-01
    • 1970-01-01
    相关资源
    最近更新 更多