This is a CLR Project (.NET Framework 4.5).
I have a ContextMenuStrip
which is displayed when the right mouse button is pressed on a ToolStripMenuItem
, since ToolStripMenuItem
has no ContextMenuStrip
property I had to set the right click event in the MouseUp
event like this:
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)));
}
}
I want the DropDown
to remain open after a right mouse button click, I know that there's an AutoClose
property that I could set to false:
private: System::Void contextMenuStrip1_Opening(System::Object^ sender, System::ComponentModel::CancelEventArgs^ e) {
this->itemsToolStripMenuItem->DropDown->AutoClose = false;
}
The problem is when setting AutoClose
to false it won't close the DropDown
anymore. I couldn't find a Leave
event to close the DropDown
and set AutoClose
back to true.
I also need to keep a DropDown
item highlighted after a right mouse button click.
This is what I'm trying to achieve:
The menu.DropDown.Closing += ...;
event is used to prevent the menu from closing if an item was right clicked. The IsSelected
property is overridden in a derived ToolStripMenuItem3
class in order to appear to remain selected.
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;
}
}
}
}
The 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);
}
}
}
}