I have a ContextMenuStrip setup with two ToolStripItems. The second ToolStripItem has two additional nested ToolStripItems. I define this as:
ContextMenuStrip cms = new ContextMenuStrip();
ToolStripMenuItem contextJumpTo = new ToolStripMenuItem();
ToolStripMenuItem contextJumpToHeatmap = new ToolStripMenuItem();
ToolStripMenuItem contextJumpToHeatmapStart = new ToolStripMenuItem();
ToolStripMenuItem contextJumpToHeatmapLast = new ToolStripMenuItem();
cms.Items.AddRange(new ToolStripItem[] { contextJumpTo,
contextJumpToHeatmap});
cms.Size = new System.Drawing.Size(176, 148);
contextJumpTo.Size = new System.Drawing.Size(175, 22);
contextJumpTo.Text = "Jump To (No Heatmapping)";
contextJumpToHeatmap.Size = new System.Drawing.Size(175, 22);
contextJumpToHeatmap.Text = "Jump To (With Heatmapping)";
contextJumpToHeatmap.DropDownItems.AddRange(new ToolStripItem[] { contextJumpToHeatmapStart,
contextJumpToHeatmapLast });
contextJumpToHeatmapStart.Size = new System.Drawing.Size(165, 22);
contextJumpToHeatmapStart.Text = "From Start of File";
contextJumpToHeatmapLast.Size = new System.Drawing.Size(165, 22);
contextJumpToHeatmapLast.Text = "From Last Data Point";
I then setup an event listener for the click events of the three ToolStripMenuItems that I want to respond to. Here are the methods (I only listed two of the three methods):
void contextJumpTo_Click(object sender, EventArgs e)
{
// Try to cast the sender to a ToolStripItem
ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
if (menuItem != null)
{
// Retrieve the ContextMenuStrip that owns this ToolStripItem
ContextMenuStrip owner = menuItem.Owner as ContextMenuStrip;
if (owner != null)
{
// Get the control that is displaying this context menu
DataGridView dgv = owner.SourceControl as DataGridView;
if (dgv != null)
// DO WORK
}
}
}
void contextJumpToHeatmapStart_Click(object sender, EventArgs e)
{
// Try to cast the sender to a ToolStripItem
ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
if (menuItem != null)
{
// Retrieve the ToolStripItem that owns this ToolStripItem
ToolStripMenuItem ownerItem = menuItem.OwnerItem as ToolStripMenuItem;
if (ownerItem != null)
{
// Retrieve the ContextMenuStrip that owns this ToolStripItem
ContextMenuStrip owner = ownerItem.Owner as ContextMenuStrip;
if (owner != null)
{
// Get the control that is displaying this context menu
DataGridView dgv = owner.SourceControl as DataGridView;
if (dgv != null)
// DO WORK
}
}
}
}
Here is the issue I have:
My contextJumpTo_Click method works perfectly fine. We get all the way down to where I determine which DataGridView the click came from and I can proceed. The contextJumpTo ToolStripMenuItem is, however, NOT a nested menu item on the ContextMenuStrip.
But my method for contextJumpToHeatmapStart_Click does not work right. When I get down to the line where I determine owner.SourceControl, the SourceControl is null and I cannot proceed. Now I know that this ToolStripMenuItem is nested under another one in my ContextMenuStrip, but why is the SourceControl property suddently null on my ContextMenuStrip?
How do I obtain the SourceControl for a nested ToolStripMenuItem for a ContextMenuStrip?
I believe that's a bug.
I tried to crawl up the list of toolstrip parents to get to the ContextStripMenu owner, which worked, but the SourceControl property was always null.
It looks like the common work around is to set the control on the opening of the context menu:
private Control menuSource;
cms.Opening += cms_Opening;
void cms_Opening(object sender, CancelEventArgs e) {
menuSource = ((ContextMenuStrip)sender).SourceControl;
}
Then your code basically turns into this:
DataGridView dgv = menuSource as DataGridView;
if (dgv != null) {
// do work
}
Related
I´m building a ContextMenu on the fly, like this
readinstance = null;
ContextMenu cMenu = new ContextMenu();
for (int i = 0; i < instances.Length; i++) {
string text = String.Format("{0} - {1}", instances[i].Id, instances[i].FormName);
MenuItem item = new MenuItem(text, new EventHandler(cMenuitem_Click));
item.Tag = instances[i];
cMenu.MenuItems.Add(item);
}
cMenu.Show((Button)sender, new Point(0, 0));
cMenu.Dispose();
if (readinstance == null)
throw new Exception("Must select some instance");
and the handler is
void cMenuitem_Click(object sender, EventArgs e)
{
MenuItem item = (MenuItem)sender;
readinstance = (FormPrintingStorage)item.Tag;
}
The menu displays correctly, but when I click some of the options, the handler is not called, so readinstance remains null, and the exception throws. As a side note, when I click any of the options, the menu disappears.
I cannot see what is wrong with my code. Any help will be appreciated.
I´m answering my own question, because I tried more ways.
The first one was to replace the ContextMenu with a ListView and an "Ok" button, at no luck, because the wait loop needed a Thread.Sleep. No comments.
The solution was to implement a new dialog with an empty list view an the Ok button. Some of the relevant code follows. Note that only TreeViewItem/s are moved between the main form and the dialog.
ListViewItem _result = null;
public ListViewItem Result { get { return _result; } }
public List<ListViewItem> Source
{
set
{
listView1.Items.Clear();
foreach (ListViewItem item in value)
listView1.Items.Add(item);
listView1.View = View.List;
}
}
private void button1_Click(object sender, EventArgs e)
{
if (_result == null)
return;
DialogResult = DialogResult.OK;
Close();
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
ListView list = (ListView)sender;
ListView.SelectedIndexCollection indices = list.SelectedIndices;
if (indices.Count == 0)
return;
_result = list.Items[indices[0]];
}
Getting the Result, the main form may do anything it wants with the Tag member. In fact, I´m using the same dialog for two different purposes in the same form.
In my main view I have a Listbox for which I set (among others) the PreviewMouseLeftButtonDownEvent that I use to support drag and drop re-ordering.
var style = ListBox.ItemContainerStyle;
style.Setters.Add(new Setter(AllowDropProperty, true));
style.Setters.Add(new EventSetter(PreviewMouseLeftButtonDownEvent,
new MouseButtonEventHandler(Input_Down)));
private void Input_Down(object sender, EventArgs e)
{
if (!(sender is ListBoxItem))
return;
var draggedItem = sender as ListBoxItem;
isDragging = true;
StartDrag(draggedItem);
}
private void StartDrag(ListBoxItem draggedItem)
{
draggedItem.IsSelected = true;
DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move);
}
In the ListBox.ItemTemplate there is a button with an Update command:
<Button Command="{Binding Path=UpdateCommand}" Content="Button"/>
However, the command is never triggered when I set the PreviewMouseLeftButtonDownEvent. If I remove the PreviewMouseLeftButtonDownEvent setter, the command works fine. Any ideas on why this is and how I can use both?
The DragDrop.doDragDrop() operation seems to block all underlying events, meaning that the commands from the buttons that are clicked are not fired.
Since I did not find any clean way of doing this, I decided to go for a hack. In the event handler of the ListBox.ItemContainerStyle I check if the original source in the RoutedEventArgs is a Button with an ICommand attached to it. If so, I abort the DragDrop.doDragDrop() procedure:
style.Setters.Add(new EventSetter(PreviewMouseLeftButtonDownEvent,
new MouseButtonEventHandler(Input_Down)));
private void Input_Down(object sender, RoutedEventArgs e)
{
if (EventTriggeredByButtonWithCommand(e))
return;
var draggedItem = sender as FrameworkElement;
if(draggedItem !=null)
StartDrag(draggedItem);
}
bool EventTriggeredByButtonWithCommand(RoutedEventArgs e)
{
var frameWorkElement = e.OriginalSource as FrameworkElement;
if (frameWorkElement == null)
return false;
var button = frameWorkElement.TemplatedParent as Button;
if (button == null)
return false;
return button.Command != null;
}
I have a database row with a primary key index and strUsername and I'm using these to dynamically generate dropdown items in a menu strip. The code gives the dropdown item the username string as it's text value and creates an event handler. I want to pass the primary key integer to the click event method but the code I've got gives all the click event methods the database row.count value. Can anyone help please?
// Generate drop down items from database
daUsers.Fill(dtUsers);
// Update mnuFileNew drop down list
if (dtUsers.Rows.Count != 0)
{
mnuFileNew.DropDownItems.Clear();
foreach (dbAutoBloggerDataSet.dbTblUsersRow row in dtUsers.Rows)
{
ToolStripMenuItem newItem = new ToolStripMenuItem(row.strUsername);
newItem.Click += new EventHandler((sender, e) => mnuFileNewUser_Click(sender, e, row.nId));
mnuFileNew.DropDownItems.Add(newItem);
}
}
// Drop down item event handler
private void mnuFileNewUser_Click(object sender, EventArgs e, int nId)
{
frmLogin frmLogin = new frmLogin(nId);
DialogResult result = frmLogin.ShowDialog();
if (result == DialogResult.OK)
{
frmProfile frmProfile = new frmProfile(frmLogin.nId);
frmProfile.ShowDialog();
screenRefresh();
}
}
I think this sample code is useful
void AddMenuItem(string text, string action)
{
ToolStripMenuItem item = new ToolStripMenuItem();
item.Text = text;
item.Click += new EventHandler(item_Click);
item.Tag = action;
//first option, inserts at the top
//historyMenu.Items.Add(item);
//second option, should insert at the end
historyMenuItem.DropDownItems.Insert(historyMenuItem.DropDownItems.Count, item);
}
private void someHistoryMenuItem_Click(object sender, EventArgs e)
{
ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
string args = menuItem.Tag.ToString();
YourSpecialAction(args);
}
I'm using C# and I have a list of objects, and I want to show all the objects in a context menu, each object is shown as a MenuItem.
When one MenuItem is clicked, I want to refer to the associated object in the MenuItem_Click() function.
List<MyObject> MyList = new List<MyObject>();
ContextMenu menu = new ContextMenu();
foreach(MyObject o in MyList)
{
MenuItem item = new MenuItem();
item.Header = o.Name;
item.Click += MenuItem_Click;
menu.Items.add(item);
}
menu.IsOpen=true;
void MenuItem_Click(object sender, RoutedEventArgs e){
// Assume the 5th MenuItem is clicked, I need to refer to the 5th object in MyList
// how to do it?
}
A quick dirty hack that fits the code you currently have: use the Tag property.
foreach(MyObject o in MyList)
{
MenuItem item = new MenuItem();
item.Header = o.Name;
item.Tag = o;
item.Click += MenuItem_Click;
menu.Items.add(item);
}
then you can refer to it this way:
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var myObj = (MyObject)((MenuItem)sender).Tag;
}
You could access it by
MenuItem nItem = sender as MenuItem;
var clickedItem = e.OriginalSource as MenuItem;
MyObject myObj = MyList.Find(o => o.Name == clickedItem.Header);
Suppose I need to change the status of an item from active = true to active = false and vise versa and at the same time persist my change in the database table.
I tested ItemChecked event like the following:
private void listView1_ItemChecked(object sender, ItemCheckedEventArgs e)
{
ListViewItem item = (ListViewItem)sender;
if (item != null)
{
Book b = (Book) item.Tag;
b.MakeActive(item.Checked);
}
}
I failed.
Can anyone help me?
in this case object sender is ListView and not ListViewItem your code should be this
private void listView1_ItemChecked(object sender, ItemCheckedEventArgs e)
{
ListViewItem item = e.Item as ListViewItem;
if (item != null)
{
Book b = (Book) item.Tag;
b.MakeActive(item.Checked);
}
}