How to Leave ToolStripMenu Open After Clicking an Item - c#

I'm creating a ToolStripMenu shown below that is supposed to allow the user to interact with the items "XML" and "Non XML" as though they are regular check boxes on a form. However, when one item is checked/unchecked the menu closes. How can I allow an item to be checked/unchecked without closing the menu? Or is there a different standard method of achieving the same behavior?
So what I want is to be able to click on "Non XML", show a check box and leave the menu open.
The idea is that the last menu item will be "Done" and when it's clicked the "G2S" sub items will remain open but the "Display" sub items ( XML, Non XML ) will close.
Any ideas?
Note: I am aware that this is likely not the best user interface design. I'd like to know however how this could be accomplished just to gain some technical knowledge about handling menus.

Interesting concept is described in this thread on Stackoverflow:
Here is the essence of the accepted answer:
ParentMenu.DropDown.AutoClose = false;
It does exactly what you are asking for - prevent menu from closing when subitem is clicked.

Here's a useful extension that requires user to click outside of menu item + dropdowns to close.
public static void KeepOpenOnDropdownCheck (this ToolStripMenuItem ctl)
{
foreach (var item in ctl.DropDownItems.OfType<ToolStripMenuItem>())
{
item.MouseEnter += (o, e) => ctl.DropDown.AutoClose = false;
item.MouseLeave += (o, e) => ctl.DropDown.AutoClose = true;
}
}

Posted in case somebody finds it helpful.
Instead of trying to do exactly what I had originally intended, I've come up with the following:
1- Use a ContextMenuStrip
2- When the user clicks on the ToolStripMenu item I display the ContextMenuStrip at a location near the menu item as shown below: ( note the positioning still needs adjusting )
To get this working I build the ContextMenuStrip in code at run-time so that the items in the ContextMenuStrip can be build dynamically based on the situation.
Code snippets:
Show the ContextMenuStrip when the menu item is clicked:
private void filterToolStripMenuItem_Click(object sender, EventArgs e)
{
contextMenuStrip1.Show(this, 180, 20);
}
Build the ContextMenuStrip:
if (protInfo.Name == "QCOM" )
{
BroadCast = new CheckBox();
BroadCast.Text = "Date/Time Broadcast";
BroadCast.Checked = FlagSet(CurrentFilter, (Byte)Filter.DateTimeBC);
ToolStripControlHost Ch1 = new ToolStripControlHost(BroadCast);
GenPoll = new CheckBox();
GenPoll.Text = "Status Poll";
GenPoll.Checked = FlagSet(CurrentFilter, (Byte)Filter.GenStatusPoll);
ToolStripControlHost Ch2 = new ToolStripControlHost(GenPoll);
GenPollResp = new CheckBox();
GenPollResp.Text = "Status Poll Response";
GenPollResp.Checked = FlagSet(CurrentFilter, (Byte)Filter.GenStatusResponse);
ToolStripControlHost Ch3 = new ToolStripControlHost(GenPollResp);
Button btnDone = new Button();
btnDone.Text = "Done";
ToolStripControlHost Ch4 = new ToolStripControlHost(btnDone);
btnDone.Click += new EventHandler(btnDone_Click);
contextMenuStrip1.Items.Clear();
contextMenuStrip1.Items.Add(Ch1);
contextMenuStrip1.Items.Add(Ch2);
contextMenuStrip1.Items.Add(Ch3);
contextMenuStrip1.Items.Add(Ch4);
contextMenuStrip1.Enabled = true;
filterToolStripMenuItem.Enabled = true;
}
else
{
filterToolStripMenuItem.Enabled = false;
}
This may not be the best user interface design, but it seems to work.

The original solution will work with the use of mouse events.
On mouse enter event:
parent.dropdown.autoclose = false;
on mouse leave event:
parent.dropdown.autoclose = true;
The only catch is if the user access the menu items by other means than a mouse.

I used a combination of Neolisk's and Chimera's answers to allow deletion of multiple leaf items from a treeview. My solution is below
Note: the following Items created at design time are used:
TreePromotions (TreeView)
menuVendorSection (Context Menu Strip)
removeMultipleItemsToolStripMenuItem (DropDown of menuVendorSection)
private void removeMultipleItemsToolStripMenuItem_MouseHover(object sender, EventArgs e)
{
removeMultipleItemsToolStripMenuItem.DropDownItems.Clear();
ToolStripMenuItem detailMenuItem;
TreeNode vendorSectionNode = treePromotions.SelectedNode;
for (int vsn = 0; vsn < vendorSectionNode.Nodes.Count; vsn++)
{
//add checkbox item
detailMenuItem = new ToolStripMenuItem(vendorSectionNode.Nodes[vsn].Text);
detailMenuItem.Tag = vendorSectionNode.Nodes[vsn].Tag;
detailMenuItem.CheckOnClick = true;
removeMultipleItemsToolStripMenuItem.DropDownItems.Add(detailMenuItem);
}
//add action buttons
Button buttonDeleteMultiple = new Button();
buttonDeleteMultiple.Text = "Remove Checked Items";
ToolStripControlHost buttonHost = new ToolStripControlHost(buttonDeleteMultiple);
buttonDeleteMultiple.Click += new EventHandler(buttonDeleteMultiple_Click);
removeMultipleItemsToolStripMenuItem.DropDownItems.Add(buttonHost);
Button buttonCancelMultipleDelete = new Button();
buttonCancelMultipleDelete.Text = "CANCEL";
buttonHost = new ToolStripControlHost(buttonCancelMultipleDelete);
buttonCancelMultipleDelete.Click += new EventHandler(buttonCancelMultipleDelete_Click);
removeMultipleItemsToolStripMenuItem.DropDownItems.Add(buttonHost);
removeMultipleItemsToolStripMenuItem.DropDown.AutoClose = false;
menuVendorSection.AutoClose = false;
}
private void buttonDeleteMultiple_Click(object sender, EventArgs e)
{
//delete items
for (int dmi = 0; dmi < removeAllItemsToolStripMenuItem.DropDownItems.Count - 2; dmi++) //do not include buttons
{
((Detail)removeAllItemsToolStripMenuItem.DropDownItems[dmi].Tag).Delete(); //deletes item from database
}
//rebuild leaf
treePromotions.SelectedNode.Nodes.Clear();
addItemNodes(treePromotions.SelectedNode); //builds leaf nodes from database
//close menus
removeMultipleItemsToolStripMenuItem.DropDown.Close();
menuVendorSection.AutoClose = true;
menuVendorSection.Close();
}
private void buttonCancelMultipleDelete_Click(object sender, EventArgs e)
{
//just close menus
removeMultipleItemsToolStripMenuItem.DropDown.Close();
menuVendorSection.AutoClose = true;
menuVendorSection.Close();
}

If someone is still interested, here is a vb solution:
1) For the parent tool strip menu item, add the following handler in the form's constructor:
AddHandler ParentTSMI.DropDown.Closing, AddressOf onDropDownClosing
2) The handler:
Private Sub onDropDownClosing(sender As Object, e As ToolStripDropDownClosingEventArgs)
If e.CloseReason = ToolStripDropDownCloseReason.ItemClicked Then
e.Cancel = True
End If
End Sub
That's it all.
Don't forget to remove the handler (RemoveHandler) when you close the form.

Related

Can I disable a button next to the button I press?

I would like to disable 2 buttons at one click, without specifying these buttons. I have ~150 buttons on my form, and I dont want to write a function for all of them.
I have a function something like this, to disable a button I click on.
public void disableButton(object sender, EventArgs e)
{
if (((Button)sender).Enabled == true)
{
((Button)sender).Enabled = false;
((Button)sender).BackColor = Color.Red;
}
}
So basically, when I call this function, I would like to disable THIS button, and a button next to it. (Eg. button1 and button2)
Considering the button should be added in the control in a specific order, so that when you will get all the buttons based on your requirement you can disable the immediate next button to the current button, Kindly refer to below code:
public void disableButton(object sender, EventArgs e)
{
var selectedButton = ((Button)sender);
var allButtons = Controls.OfType<Button>().ToList();
var currentButtonIndex = allButtons.Select((s, i) => new { button = s, index = i }).Single(si => si.button.Name == selectedButton.Name).index;
if (allButtons[currentButtonIndex + 1].Enabled == true)
{
allButtons[currentButtonIndex + 1].Enabled = false;
allButtons[currentButtonIndex + 1].BackColor = Color.Red;
}
}
To get list of all buttons in your form Controls.OfType<Button>().ToList(), next step is to get the index of current button. Once you get the index the increment it with 1 and you will get the reference of the next button
for safer implementation just check if it is not the last button in that case choose the first button, refer below code:
currentButtonIndex = currentButtonIndex + 1 >= allButtons.Count() ? 0 : currentButtonIndex;
Suppose you are not able to figure out the ordering of the buttons, but the next button should be based on TabStop, based on TabStop you can better find the next button, refer below code
var allButtons = Controls.OfType<Button>().OrderBy(o => o.TabStop).ToList();
var currentButtonIndex = allButtons.Select((s, i) => new { button = s, index = i }).Single(si => si.button.Name == selectedButton.Name).index;
var toDisableButton = allButtons[currentButtonIndex + 1];
Controls.OfType<Button>().Single(si => si.Name == toDisableButton.Name).Enabled = false;
Create a collection of pairs, where pair: (name_of_btn_1, name_of_btn_2).
This can be an IDictionary<string, string>.
Inside the click event handler, get the associated button name using the dictionary. Then, you can do this.Controls.Find() to get the associated button by name and disable it.

hide and unhide context strip menu item c#

if(e.Button == MouseButtons.Right)
{
string signatureDate = dataGridView3.CurrentRow.Cells[8].Value.ToString();
// MessageBox.Show(signatureDate);
if(signatureDate.Length > 5)
{
contextMenuStrip1.Items[0].Visible = false;
contextMenuStrip1.Items[1].Visible = true;
}else
{
contextMenuStrip1.Items[0].Visible = true;
contextMenuStrip1.Items[1].Visible = false;
}
}
I have a context strip menu that is working in my datagridview. And I selected it as Row Context Strip Menu.
What I am trying to do is to get if selected row of datagridview and control signature column is null or not. If it has signature date I want to hide or unhide "Sign" and if it doesn't have signature date hide "Unsign" item on context menu strip.
You can see in picture I enclosed.Context menu Strip
EDIT: Name of the event is MouseDown.
EDIT 2: With editing this code I can get columns data and show them on messageBox. But I can not use those data as a condition. Therefore it is not working. For example, when I select a row that is without "Signature Date" and show it on messageBox, it is working. But when I use Signature Date data as a condition It is not working. I know it is so strange and too easy to overcome but I coundn't because of that I didn't catch anything.
EDIT 3: Event
EDIT 4 (SOLVED) : I created to Context Strip Menu and specify no one of them
as Context strip Menu of Datagridview.
With Datagridview_MouseDown event, I am getting Signature Date column data and check if it is null/empty or not. If it is null/empty I specify first Context Menu strip as Context Strip Menu of Datagridview or not I do revise. I figured out the solution in this way :)
I think your problem is in the instance of context menu strip use this one see if it helps.
if(e.Button == MouseButtons.Right)
{
string signatureDate = dataGridView3.CurrentRow.Cells[8].Value.ToString();
// MessageBox.Show(signatureDate);
if(signatureDate.Length > 5)
{
dataGridView3.ContextMenu.Items[0].Visible = false;
dataGridView3.ContextMenu.Items[1].Visible = true;
}else
{
dataGridView3.ContextMenu.Items[0].Visible = true;
dataGridView3.ContextMenu.Items[1].Visible = false;
}
}
Probably your event is not firing.
Instead of using mouse down you could also use the Opening event of the contextMenuStrip
This should solve your problem
private void Form1_Load(object sender, EventArgs e)
{
dataGridView3.ContextMenu = contextMenuStrip1;
contextMenuStrip1.Opening += contextMenuStrip1_Opening;
}
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
string signatureDate = dataGridView3.CurrentRow.Cells[8].Value.ToString();
// MessageBox.Show(signatureDate);
if (signatureDate.Length > 5)
{
contextMenuStrip1.Items[0].Visible = false;
contextMenuStrip1.Items[1].Visible = true;
}
else
{
contextMenuStrip1.Items[0].Visible = true;
contextMenuStrip1.Items[1].Visible = false;
}
}

How to add a context menu for only one level in a TreeView

I've got a TreeView, containing a number of levels of TreeViewItems.
I would like to add a context menu to only one level of items in the TreeView. However, my code produces a result whereby every single item in the TreeView has a context menu.
This is my code:
//.... foreach item in this level....
{
ContextMenu cmDatabase = new ContextMenu();
MenuItem menuItem = new MenuItem();
menuItem.Header = "Close Connection";
Image imgMenuIcon = new Image();
imgMenuIcon.Source = new BitmapImage(new Uri("icon.png"));
menuItem.Icon = imgMenuIcon;
cmDatabase.Items.Add(menuItem);
treeViewItem.ContextMenu = cmDatabase;
}
I've also tried manually setting all the other TreeViewItems' contextMenu property to null. No luck though. Any ideas?
The TreeView has a ContextMenuOpening event. Set the Handled property of the event handler argument e to true depending on the tree level. This will discard the context menu.
private void treeView1_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
e.Handled = <tree level does not require a context menu>;
}
If you have a TextBlock for your TreeViewItem, you can attach the ContextMenu onto that instead.
Textblock header = "TreeViewItem Text";
header.ContextMenu = cmDataBase;
treeViewItem.Header = header;
Also, for your other question that I answered, but you deleted before I hit Save. :P I'd put the right-click as an event (also on the header).
header.MouseRightButtonDown += new MousebuttonEventHandler(rightClickSelection);
private void rightclickSelection(object sender, MouseButtonEventArgs e) {
TreeViewItem clickedParent = (sender as TextBlock).Parent as TreeViewItem;
clickedParent.IsSelected = true;
clickedParent.UpdateLayout();
}

C# WinForm Datagrid doubleclick event

Is there a doubleclick event for a datagrid? I'm trying to use this code to open a details form when the user doubleclicks on a row.
http://www.codeproject.com/KB/grid/usingdatagrid.aspx
I tried adding it by doubleclicking on the control, but it gives dataGrid1_Navigate instead.
What you get when you double click a control in design mode is the event the designers of the control thought would be the most used, in this case it's Navigate.
But yes, this control has two double click events:
public partial class Form1 : Form
{
DataGrid grid = new DataGrid();
public Form1()
{
InitializeComponent();
grid.DoubleClick += new EventHandler(grid_DoubleClick);
grid.MouseDoubleClick += new MouseEventHandler(grid_MouseDoubleClick);
grid.Dock = DockStyle.Fill;
this.Controls.Add(grid);
}
void grid_MouseDoubleClick(object sender, MouseEventArgs e)
{
}
void grid_DoubleClick(object sender, EventArgs e)
{
}
}
However, both of these events run when you double click anywhere on the control and they don't directly give you information on what row was selected. You might be able to retrieve the row double clicked in the grid_MouseDoubleClick handler by getting it from the control based on the point being clicked (e.Location), that's how it works in the TreeView control for example. At a quick glance I didn't see if the control has such a method. You might want to consider using DataGridView instead, if you don't have a particular reason to use this control.
Sounds like you need a way to get a list of all the events for a given control, rather than finding the default event (which is what VS gives you when you double click a control in the designer)
There are a few ways of doing this:
One way Select the grid.
Then click the events icon to turn the properties window into a list of events, then doubel click the event you want to strart coding the event.
Alternatively, switch to code view, select the grid in the drop down list of objects at the top left of the code window, then select the event you want from the list of all the events for that control in the event list (top right of the code window)
I tried #steve76's code, but had to tweak it slightly to work in a Windows Embedded CE 6.0 system. Here is what worked for me.
private void dataGrid1_DoubleClick(object sender, EventArgs e)
{
Point pt = dataGrid1.PointToClient(Control.MousePosition);
DataGrid.HitTestInfo info = dataGrid1.HitTest(pt.X, pt.Y);
int row;
int col;
if (info.Column < 0)
col = 0;
else
col = info.Column;
if (info.Row < 0)
row = 0;
else
row = info.Row;
object cellData = dataGrid1[row, col];
string cellString = "(null)";
if (cellData != null)
cellString = cellData.ToString();
MessageBox.Show(cellString, "Cell Contents");
}
Perhaps you can use the DataGridView.CellContentDoubleClick event.
Example:
private void DataGridView1_CellContentDoubleClick(Object sender, DataGridViewCellEventArgs e) {
System.Text.StringBuilder messageBoxCS = new System.Text.StringBuilder();
messageBoxCS.AppendFormat("{0} = {1}", "ColumnIndex", e.ColumnIndex );
messageBoxCS.AppendLine();
messageBoxCS.AppendFormat("{0} = {1}", "RowIndex", e.RowIndex );
messageBoxCS.AppendLine();
MessageBox.Show(messageBoxCS.ToString(), "CellContentDoubleClick Event" );
}
If that is not what you are looking for, you can find other events in the reference:
http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview_events.aspx

How to change( and later restore) default context menu in textbox

I wanted to change default textbox context menu, so I created my own menu and them I assigned it like that
texbox.ContextMenu = myContextMenu
However I don't know how to restore default textbox menu (in a runtime). I need myContextMenu to show only when I click textbox with right mouse button (while holding Control button). In other cases I need default textbox contextmenu to show.
Is it possible ??
Here is the example given by Microsoft:
http://msdn.microsoft.com/en-us/library/ms750420.aspx
For the record, here is the way to do this using WinForms:
public partial class TextBoxContextMenuDemo : Form
{
ContextMenu mnuContextDefault;
ContextMenu mnuContextAlt;
MenuItem mnuItmAltMenuTest;
public TextBoxContextMenuDemo()
{
InitializeComponent();
InitializeAltContextMenu();
}
private void InitializeAltContextMenu()
{
mnuContextDefault = new ContextMenu();
mnuContextDefault = this.TextBox1.ContextMenu;
mnuItmAltMenuTest = new MenuItem();
mnuItmAltMenuTest.Index = -1;
mnuItmAltMenuTest.Text = "Test Menu Item";
mnuItmAltMenuTest.Click += new System.EventHandler(this.mnuItmAltMenuTest_Click);
mnuContextAlt = new ContextMenu();
mnuContextAlt.MenuItems.Add(mnuItmAltMenuTest);
}
private void TextBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
if ((Control.ModifierKeys == Keys.Control))
{
this.TextBox1.ContextMenu = mnuContextAlt;
TextBox1.ContextMenu.Show(TextBox1, new Point(e.X, e.Y));
}
else
{
this.TextBox1.ContextMenu = mnuContextDefault;
}
}
}
private void mnuItmAltMenuTest_Click(object sender, System.EventArgs e)
{
MessageBox.Show("You clicked the alternate test menu item!");
}
}
HTH!
It would actually be more difficult to do than it would first seem. I believe that the default context menu is part of the actual template of the control.
The simplest approach, if you only want Cut/Copy/Paste, is to create a second ContextMenu implementing those options. If you do, you can use the built in ApplicationCommands to implement not only the functionality, but also to automatically localize this ContextMenu.
You could just set the ContextMenu-Property to null. Also the OnContextMenuOpening event can help you.

Categories