ContextMenuStrip not closing once clicked - c#

I am creating a Context Menu Strip once a Rich Text Box is right clicked. There are 2 options, one to change the font and one to change the background color. However, once I click one of the menu options, the Context Menu Strip doesn't close and overlays dialogs that are displayed. I know I can make it "global" and force it to close, but I would rather not. What is the best way to handle this?
// If the console is right clicked then show font options
private void rtb_console_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
ContextMenuStrip menu = new ContextMenuStrip();
menu.Items.Add("Change Font");
menu.Items.Add("Change Background Color");
menu.Show(this, new Point(e.X, e.Y));
menu.ItemClicked += new ToolStripItemClickedEventHandler(menu_ItemClicked_ChangeFont);
}
}
// Determine whether to change the font or the font background color
void menu_ItemClicked_ChangeFont(object sender, ToolStripItemClickedEventArgs e)
{
Application.DoEvents(); // Read that this might help, but it doesn't
if (e.ClickedItem.Text == "Change Font")
{
FontDialog font = new FontDialog();
font.ShowColor = true;
font.Font = rtb_console.Font;
font.Color = rtb_console.ForeColor;
if (font.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
rtb_console.Font = font.Font;
rtb_console.ForeColor = font.Color;
}
}
else if (e.ClickedItem.Text == "Change Background Color")
{
ColorDialog color = new ColorDialog();
color.Color = rtb_console.BackColor;
if (color.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
rtb_console.BackColor = color.Color;
}
}
}
So this is what happens:

You don't want to create the ContextMenuStrip and show it manually each time. The better way to do this is to create the ContextMenuStrip once. Then assign it for the RichTextBox by assigning it to the ContextMenuStrip property of the RichTextBox. Doing this, you will no longer need to manually launch the ContextMenuStrip everytime a user clicks on it. It will occur automagically. It will also hide itself automagically in the way you would expect upon clicking on it.
Do this once and then remove your event handler for the MouseUp event:
ContextMenuStrip menu = new ContextMenuStrip();
menu.Items.Add("Change Font");
menu.Items.Add("Change Background Color");
menu.ItemClicked += new ToolStripItemClickedEventHandler(menu_ItemClicked_ChangeFont);
rtb_console.ContextStripMenu = menu;
Also, please please please don't use Application.DoEvents(); to try and force the UI to update itself. Head over to here and read the top answer. In general, if you are using Application.DoEvents(), you are doing something wrong and should considering changing your approach.
One thing you may also consider doing, but it is really just a matter of preference... If you are using Visual Studio, consider creating you ContextMenuStrip in the designer. That way, you can add your items, icons, and individual callbacks for each item very easily and visually. Just something I like to do out of pure personal preference.

In ToolStripItemClickedEventArgs just declare:
ContextMenuStrip menu = (ContextMenuStrip)(Sender)
if (e.ClickedItem.Text == "Change Font")
{
menu.hide();
/* and your code here*/...
}

Related

Handle DragDrop outside of control - DragEnd event?

i'm are trying to mimic the behavior of web browsers in a WinForm application where you can drag and drop tabs in and out of the browser and create another instance of the it when you drop the tab somewhere with no existing tabs.
Currently the WinForm application only has one main TabControl and I was looking at the DoDragDrop() related events, but they seem to only work when you have two TabControls and move TabPages around those two.
Is there a way to make it work with only one TabControl? Meaning, If you Drop a TabPage out of the TabControl then it will create a new TabControl with the TabPage in it?
I can only think of using:
private void TabControl_DragLeave(object sender, EventArgs e)
{
Form newInstance = new Form();
TabControl newTabControl = new TabControl();
newInstance.Controls.Add(newTabControl);
newTabControl.TabPages.Add(sender as TabPage);
newInstance.Show();
}
but that is pretty crud and will create the new tab every time you leave the TabControl.
It seems you are looking for an event which raises at the end of drop, regardless of ending over your control or outside of the control.
You can rely on QueryContinueDrag and check if the action is Drop, then check the mouse position and for example if it's not inside your control, just create another window and add the selected tab into a tab control inside the new window.
private void tabControl1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
tabControl1.DoDragDrop(tabControl1.SelectedTab, DragDropEffects.All);
}
}
private void tabControl1_DragOver(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(TabPage)))
e.Effect = DragDropEffects.Move;
}
private void tabControl1_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
if (e.Action == DragAction.Drop)
{
var tabPage = tabControl1.SelectedTab;
if (!tabControl1.RectangleToScreen(tabControl1.Bounds).Contains(Cursor.Position))
{
var form = new Form();
form.Text = tabPage.Text;
var tabControl = new TabControl();
tabControl.TabPages.Add(tabPage);
tabControl.Dock = DockStyle.Fill;
form.Controls.Add(tabControl);
form.FormBorderStyle = FormBorderStyle.SizableToolWindow;
form.StartPosition = FormStartPosition.Manual;
form.Location = new Point(Cursor.Position.X - form.Width / 2,
Cursor.Position.Y - SystemInformation.CaptionHeight / 2);
form.Show();
e.Action = DragAction.Cancel;
//You can comment tabControl.TabPages.Add
//Then set e.Action = DragAction.Continue
//Then the DragDrop event will raise and add the tab there.
}
}
}
private void tabControl1_DragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(TabPage)))
{
var tabPage = (TabPage)e.Data.GetData(typeof(TabPage));
tabControl1.TabPages.Remove(tabPage);
tabControl1.TabPages.Add(tabPage);
}
}
For more advanced scenarios and to enhance the code:
When start dragging, you can start dragging just if the mouse dragged at least for a specific points, for example 16 points. It's easy to calculate. Having p1 as mouse down point and p2 as mouse move point, and d as drag threshold. start dragging just in case (p1.X-p2.X)*(p1.X-p2.X) + (p1.Y-p2.Y)*(p1.Y-p2.Y) > d*d.
You can use GiveFeedback event to disable default cursor of the mouse and instead show a more suitable cursor while dragging, easily by e.UseDefaultCursors = false; and setting Cursor.Current = Cursors.SizeAll; for example.
You can encapsulate the logic and put it in a derived TabControl. Then in DragEnter and DragLeave events set a static property for tracking drop target. In case the drop-target has value, it means you are dropping on a derived tab control, otherwise it means you are dropping outside. Then drag and drop will be easily enabled for all your custom tab controls.
You can close the tool form, after a drag and drop, in case the form doesn't contain any other tab.
When adding the tab, you can insert it before/after the selected tab or the tab under cursor in target.

WinForms ContextMenu stays open when MenuItem is clicked

So I have a form DataGridView and when I right click on the grid I want to display a context menu that has one menu item in it. The menu item will open a second form that will provide some configuration options for the DataGridView.
Now all of this works absolutely fine, the context menu displays correctly and the second form opens correctly and all of the functionality on that form works correctly.
The only issue is that the context menu will only close if I click anywhere other than the menu item. No matter how many times I click on the menu item the context menu does not close.
I have tried looking for work arounds but as far as I can tell there is no way to programatically close the context menu.
Any help would be greatly appreciated. Below are copies of the click events for opening the context menu and for the menu item click event.
private void DataGridView_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
DataGridView dgv = (DataGridView)sender;
if (dgv.CurrentCell == null)
{
return;
}
else
{
Rectangle r = dgv.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false);
Point p = new Point(r.X + e.X, r.Y + e.Y);
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add(new MenuItem("Item", Item_Click));
cm.Show(dgv, p);
}
}
}
private void Item_Click(object sender, EventArgs e)
{
new SecondForm().Show();
}
UPDATE:
I solved the issue by replacing the ContextMenu class with the ContextMenuStrip class, removing the MouseClick event handler and assigning the ContextMenuStrip object to DataGridView.ContextMenuStrip. It appears as though the ContextMenuStrip class deals with showing the menu when it's relevant control is right clicked, so if you add a click event handler to deal with opening the menu it will repeatedly try to render the menu making it flicker several times before it is eventually rendered
in your class add a private variable
private bool CloseMenu = true;
on mouse down of your Context Menu
private void Item_Click(object sender, EventArgs e)
{
CloseMenu = false;
new SecondForm().Show();
}
add context menu closing event
private void contextMenuStripMy_Closing(object sender, ToolStripDropDownClosingEventArgs e)
{
e.Cancel = !CloseMenu;
CloseMenu = true;
}
I found some workdaround on this problem after many trials.
Firstly, this is about ContextMenu and not ContextMenuStrip.
This is not perfect solution but it works good if you do not have other alternative solution.
Here is how.
Just Set Visiable = false for all MenuItems under the Context Menu.
Only one problem with this is that it shows small square around the mouse.
I think this small square is the empty context menu box.
However, this small square will be removed quickly if user clicks anywhere and if the context menu lose focus.
It is really small and not bothering.
In code, this will look something like this in case you have only one menu item.
if (cm != null)
{
if (cm.MenuItems.Count > 0) cm.MenuItems[0].Visible = false;
}
If you have multiple of items, then just loop through all menu items.
if (cm != null)
{
for(int i = 0; i < cm.MenuItems.Count ; i++)
cm.MenuItems[i].Visible = false;
}
Hope this helps.
It worked for my case.
So I did not have to swtich to ContextMenuStrip from ContextMenu.

Do not close ToolStripMenu on clicking in winforms

I work on a c# winform project that the main toolstripmenu have not to be hide after user clicks on its item, how can I do that?
Set the AutoClose property of the parent menu item to prevent the menu strip from closing.
To demonstrate:
ToolStripMenuItem file = new ToolStripMenuItem("File");
file.DropDown.AutoClose = false;
file.DropDownItems.Add("New");
file.DropDownItems.Add("Open");
file.DropDownItems.Add("Exit");
MenuStrip ms = new MenuStrip();
ms.Items.Add(file);
this.Controls.Add(ms);
Now the responsibility is on you to close the menu yourself:
file.DropDown.Close();
I found better answer on MSDN forum. Dropdown doesn't close on click, but closes in other cases:
DropDown.Closing += new ToolStripDropDownClosingEventHandler(DropDown_Closing);
...
private void DropDown_Closing(object sender, ToolStripDropDownClosingEventArgs e)
{
if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked)
{
e.Cancel = true;
}
}

"Unselect" event for MenuItem

I'm trying to implement a 'preview' scenario when the user hover a menu item.
For example, lets say a program has a context-menu with 'Set Color' sub-menu.
The sub-menu pop a list of color to choose from.
Now, when the mouse cursor is over a specific color, I want it to change a label of "Selected color".
And when the mouse cursor leave the selected color menu-item, I want to label to restore its original text.
The following code demonstrate changing the label when menu-item selected - mouse is over.
private void Init()
{
var mnuContextMenu = new ContextMenu();
this.ContextMenu = mnuContextMenu;
var smthingElseMenu = new MenuItem("Do something else");
var setColorMenu = new MenuItem("Set Color");
var colorBlue = new MenuItem("Blue");
var colorRed = new MenuItem("Red");
var colorGreen = new MenuItem("Green");
mnuContextMenu.MenuItems.Add(smthingElseMenu);
mnuContextMenu.MenuItems.Add(setColorMenu);
setColorMenu.MenuItems.Add(colorBlue);
setColorMenu.MenuItems.Add(colorRed);
setColorMenu.MenuItems.Add(colorGreen);
colorBlue.Select += ColorSelect;
colorRed.Select += ColorSelect;
colorGreen.Select += ColorSelect;
}
void ColorSelect(object sender, EventArgs e)
{
lblSelectedColor.Text = ((MenuItem) sender).Text;
}
But I couldn't find a way to make the label text restore when the mouse cursor leave the menu-item.
Any ideas how can I implement some kind of 'Unselect'/'MouseLeave' event for MenuItem?
There's no "un-select" event for MenuItems, unfortunately.
I would just catch the Collapse event of your context menu, and reset your label there. This would have the added benefit that if your user hovers over the "Red" option, then hovers off the context menu, the label should stay red until the context menu closes.
mnuContextMenu.Collapse += (s, e) => lblSelectedColor.Text = "None";
If you really need it to reset the label when your mouse leave the context menu, then you could catch the MouseEnter event of the Panel (or whatever) you have that surrounds the ContextMenu.
MyPanel.MouseEnter += (s, e) => lblSelectedColor.Text = "None";
EDIT Do consider using the ContextMenuStrip class instead. The ToolSTripMenuItem class has a MouseLeave event. And a Checked property, probably what you really want.
Can't you just save the old MenuItem ref.
private MenuItem _oldMenuItem;
void ColorSelect(object sender, EventArgs e)
{
if(_oldMenuItem != null) _oldMenuItem.Text = someText;
_oldMenuItem = sender as MenuItem;
lblSelectedColor.Text = ((MenuItem) sender).Text;
}
Use MouseEnter and MouseLeave events to handle everything. First is raised when when the mouse pointer enters the bounds of this element. And second mouse pointer leaves the bounds - at this point you restore default label text.
EDIT like Hans pointed in comment use ContextMenuStrip and ToolStripMenuItems and youll have those events.
Can't you use:
private void colorBlue_MouseEnter(object sender, EventArgs e)
{
// use color blue
}
private void colorBlue_MouseLeave(object sender, EventArgs e)
{
// use old color
}

custom dialog with a text field in winmobile

I'm looking to have a simple custom dialog box, like a message box, that has a label and a TextBox. If there's a simple way to do this, sorry! I'm really not well versed in the dialog stuff.
Thanks for any help, guys!
Here is how to make a small custom dialog box in Windows Mobile that looks like this:
alt text http://www.freeimagehosting.net/uploads/b8fb5421d6.jpg
Add a form to your project, and set its FormBorderStyle property to None. This allows the form to be resized and positioned, but also means it has no border or titlebar, and there's no way for the user to move it.
So you have to fake all three. The easiest way to fake the border and the title bar is to make the BackColor of your form SystemColors.WindowFrame, then put a label (where it says "Dialog" in the picture) with BackColor = SystemColors.Highlight and ForeColor = SystemColor.HighlightText (and bold the font), then put a panel with BackColor = SystemColors.Window where you see white in the picture. You have to tweak the positions and sizes of the label and the panel so you have a 1-pixel border (which is just the BackColor of your form showing through).
To enable the form to be dragged around by its fake titlebar, add this code to the form (and of course you have to wire up the events, too):
private bool _Moving = false;
private Point _Offset;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
_Moving = true;
_Offset = new Point(e.X, e.Y);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (_Moving)
{
Point newlocation = this.Location;
newlocation.X += e.X - _Offset.X;
newlocation.Y += e.Y - _Offset.Y;
this.Location = newlocation;
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (_Moving)
{
_Moving = false;
}
}
One other problem is that because there isn't a real titlebar, there's no way for the user to close the form. You have to add an OK (or Close) button, and put this in the button's Click event:
this.DialogResult = DialogResult.OK;
Normally you would use the mouse event on the title bar to facilitate dragging, but the label control doesn't have any mouse events. With this code you could actually grab anywhere on the form and drag it, except the panel blocks this and makes the title bar the only place to grab and drag.
My other answer has more details on getting information back from custom dialogs.
Update: actually, there's only no obvious way to close a borderless form without adding your own OK button. As long as you don't set your form's ControlBox property to False, the OK or X button in the upper right corner of the Today screen will close your dialog, even if it doesn't look like it will since it's not actually on the form.
If you want a super-simple but[t] ugly solution, you can include a reference in your project to Microsoft.VisualBasic, which lets you use the VB function InputBox like this:
string s = Microsoft.VisualBasic.Interaction.InputBox("prompt text",
"title text", "default value", 0, 0);
The dialog takes up the entire screen, but is simple to use. But is incredibly ugly, as I mentioned.
I'm assuming you basically want a custom dialog box that returns a string entered by the user. One way is to add a reference to Microsoft.VisualBasic to your project, which gives you access to the InputBox method, which is basically a message box with a text box on it. But that's no fun and I'm not sure it would work on a smartphone anyway.
To roll your own, you just add a form (named CustomDialog) to your project and drag a textbox (textBox1), a label (label1), and a button (labeled "OK") onto it.
To set the label text, add a parameter to the form's constructor like this:
public CustomDialog(string textCaption)
{
label1.Text = textCaption;
}
To expose the entered text to the caller, add this code to the form:
public override string Text
{
get
{
return textBox1.Text;
}
}
In the OK button's click event, put this code:
this.DialogResult = DialogResult.OK; // this will close the form, too
To use this dialog from your main form, you create an instance of this form, show it, check to see that the OK button was clicked, and then read its Text property (which returns what the user entered) like so:
using (CustomDialog dialog = new CustomDialog("What is your name"))
{
if (dialog.ShowDialog(this) == DialogResult.OK)
{
string enteredText = dialog.Text;
}
}
You can get fancier, but those are the basics.

Categories

Resources