dynamically associate a MenuItem with an item in a List - c#

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

Related

MenuItem Click event handler not called

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.

e.OriginalSource is null

I have a code where i get some text of textblock. I do this in a Hold Event using this code txt = (e.OriginalSource as TextBlock).Text; but sometimes it returns me this error "An exception of type 'System.NullReferenceException' occurred in Barcode_QRCode.WindowsPhone.exe but was not handled in user code" and sometimes works just fine. Does anyone could help me here?
my code:
private void ListViewItem_Holding(object sender, HoldingRoutedEventArgs e)
{
txt = (e.OriginalSource as TextBlock).Text;
MenuFlyout menu = new MenuFlyout();
MenuFlyoutItem item1 = new MenuFlyoutItem();
MenuFlyoutItem item2 = new MenuFlyoutItem();
MenuFlyoutItem item3 = new MenuFlyoutItem();
item1.Text = "Copiar";
item2.Text = "Deletar";
item3.Text = "Compartilhar";
item1.Click += MenuFlyout_Copiar;
item2.Click += MenuFlyout_Deletar;
item3.Click += MenuFlyout_Compartilhar;
menu.Items.Add(item1);
menu.Items.Add(item2);
menu.Items.Add(item3);
menu.ShowAt((FrameworkElement)sender);
}
Check if e.OriginalSource is null before attempting to cast and assign the value:
Private void ListViewItem_Holding(object sender, HoldingRoutedEventArgs e)
{
txt = (e.OriginalSource != null ? (e.OriginalSource as TextBlock).Text : "");
MenuFlyout menu = new MenuFlyout();
MenuFlyoutItem item1 = new MenuFlyoutItem();
MenuFlyoutItem item2 = new MenuFlyoutItem();
MenuFlyoutItem item3 = new MenuFlyoutItem();
item1.Text = "Copiar";
item2.Text = "Deletar";
item3.Text = "Compartilhar";
item1.Click += MenuFlyout_Copiar;
item2.Click += MenuFlyout_Deletar;
item3.Click += MenuFlyout_Compartilhar;
menu.Items.Add(item1);
menu.Items.Add(item2);
menu.Items.Add(item3);
menu.ShowAt((FrameworkElement)sender);
}
Maybe sometimes the OriginalSource you receive is not a TextBlock, which will cause you cast to return null. You should check the type of your OriginalSource before trying to use it like so :
var textBlock = e.OriginalSource as TextBlock;
if(textBlock != null)
{
....
}

Unhide Datagrid column in WPF

I am developing a WPF application.There is a datagrid in my Application i have created a context menu to hide and unhide column header of the datagrid while assigning the itemsource of datagrid to an IEnumerable collection.
this.dataGrid1.ItemsSource = objref.Result;
grid_data = objref.Result;
cxMenu = new ContextMenu();
foreach (Microsoft.Windows.Controls.DataGridColumn item in dataGrid1.Columns)
{
menuItem = new MenuItem();
menuItem.Header = item.Header;
menuItem.IsChecked = true;
cxMenu.Items.Add(menuItem);
menuItem.Click += new RoutedEventHandler(menuItem_Click);
menuItem.Checked += new RoutedEventHandler(menuItem_Checked);
menuItem.Unchecked += new RoutedEventHandler(menuItem_Unchecked);
}
Everythjing working fine. when i uncheck the columns are successfully remobved but when i again check the MenuItem of my ContextMenu it is not added.
Handler of my check event is as follows.
void menuItem_Checked(object sender, RoutedEventArgs e)
{
MenuItem item = sender as MenuItem;
dataGrid1.ItemsSource = null;
dataGrid1.ItemsSource = objref.Result;// Again assgining the whole set to itemssource
List<string> menuList = new List<string>();
menuList.Clear();
foreach (MenuItem menuItem in cxMenu.Items)
{
if (menuItem.IsChecked == false)
{
menuList.Add(menuItem.Header.ToString());
}
}
Functionsclass objref = new Functionsclass();
foreach (string menuItem in menuList)
{
foreach (Microsoft.Windows.Controls.DataGridColumn column in dataGrid1.Columns)
{
if (column.Header.ToString() == menuItem)
{
dataGrid1.Columns.Remove(column);
break;
}
}
}
}
But my column is not added when i check again. Please help me on this.
Update 2:
void menuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem item = sender as MenuItem;
if (item.IsChecked)
{
item.IsChecked = false;
}
else
{
item.IsChecked = true;
}
}
void menuItem_Unchecked(object sender, RoutedEventArgs e)
{
MenuItem item = sender as MenuItem;
foreach (Microsoft.Windows.Controls.DataGridColumn column in dataGrid1.Columns)
{
if (column.Header.ToString().Contains(item.Header.ToString()))
{
dataGrid1.Columns.Remove(column);
break;
}
}
}
Uncheck handler.
If you just want to hide/show columns, I don't think Removing/Adding columns is the right approach. I suggest you make use of the Visibility property of the column. set it to Visibility.Collapsed to hide it, then Visibility.Visible to make it visible again.
column.Visibility = Visibility.Collapsed; // Column is hidden
column.Visibility = Visibility.Visible; //Column is Visible
I have just changed my checked and Unchecked event handlers like below. Now its working fine..:)
//Unchecked handler
void menuItem_Unchecked(object sender, RoutedEventArgs e)
{
MenuItem item = sender as MenuItem;
foreach (Microsoft.Windows.Controls.DataGridColumn column in dataGrid1.Columns)
{
if (column.Header.ToString().Contains(item.Header.ToString()))
{
column.Visibility = Visibility.Collapsed;
break;
}
}
}
// Checked handler
void menuItem_Checked(object sender, RoutedEventArgs e)
{
MenuItem item = sender as MenuItem;
List<string> menuList = new List<string>();
menuList.Clear();
foreach (Microsoft.Windows.Controls.DataGridColumn column in dataGrid1.Columns)
{
if (column.Header.ToString().Contains(item.Header.ToString()))
{
column.Visibility = Visibility.Visible;
break;
}
}
}

ContextMenuStrip.Owner Property null When Retrieving From Nested ToolStripMenuItem

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
}

Right click on a menu item and show options

I have menu ServerList, I am adding the menuItems dynamically using C# code. It reads the servers list from file and populate the menu items. I have added the right click options for each server. Edit & Delete.
All this is working fine. the problem is how do I read actual server name when Edit/Detele is clicked.
Here is the code
public MainWindow()
{
InitializeComponent();
LoadMenuItems();
}
//Currently static values, but reads from file. later
private void LoadMenuItems()
{
MenuItem item2 = new MenuItem();
item2.Header = "Server1";
AddContextMenu(item2);
MenuItem item3 = new MenuItem();
item3.Header = "Server2";
AddContextMenu(item3);
ActualMenu.Items.Add(item2);
ActualMenu.Items.Add(item3);
}
private void AddContextMenu(MenuItem item)
{
MenuItem item1 = new MenuItem();
item1.Header = "Edit";
item1.Click += item_Click;
MenuItem item2 = new MenuItem();
item2.Header = "Detlete";
item2.Click += item_Click;
ContextMenu menu = new ContextMenu();
menu.Items.Add(item1);
menu.Items.Add(item2);
item.ContextMenu = menu;
}
void item_Click(object sender, RoutedEventArgs e)
{
MenuItem item = sender as MenuItem;
string header = item.Header.ToString();
}
For this use PlacementTarget.
private void AddContextMenu(MenuItem item)
{
MenuItem item1 = new MenuItem();
....
ContextMenu menu = new ContextMenu();
....
menu.PlacementTarget = item; /// 'Connects' context menu to source menu item.
item.ContextMenu = menu;
}
void item_Click(object sender, RoutedEventArgs e)
{
MenuItem item = sender as MenuItem;
string header
= ((MenuItem)((ContextMenu)((MenuItem)sender).Parent).PlacementTarget).Header;
}
Cheers.
By default, the Header of a MenuItem uses a TextBlock to display content. So, in this case you need to convert the Header to a TextBox, then look at the Text property.
For example,
void item_Click(object sender, RoutedEventArgs e){
string servername = ((sender as MenuItem).Header as TextBlock).Text;
}

Categories

Resources