How to create a ContextMenu when the mouse button is pressed - c#

I made a code for a DataGrid that fire the right mouse button event, in particular:
private void Squadre_DataGrid_MouseClick(object sender, MouseEventArgs e)
{
if (e.RightButton == MouseButtonState.Pressed)
{
//Context menu
}
}
I want create a ContextMenu inside the condition, and associate for each item of the ContextMenu a method that will be executed if the item will be choose.
How to do this?

Perhaps you can achieve that in XAML. Assuming you want to have a context menu for the rows of your DataGrid, you can add the ContextMenu property to your DataGridRow, for example:
<DataGrid>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="ContextMenu" Value="{StaticResource theContextMenu}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
Then add the context menu itself in the resource dictionary:
<Window.Resources>
<ResourceDictionary>
<ContextMenu x:Key="theContextMenu">
<MenuItem Header="Menu Item 1" Click="menuItem1_Click">
</MenuItem>
<MenuItem Header="Menu Item 2" Click="menuItem2_Click">
</MenuItem>
</ContextMenu>
</ResourceDictionary>
</Window.Resources>
Then write a click event handler for each menu item to execute your method:
private void menuItem1_Click(object sender, RoutedEventArgs e)
{
// execute your method..
}
private void menuItem2_Click(object sender, RoutedEventArgs e)
{
// execute your method..
}

You could bind datacontext content to a propertie and than fill It (propertie) in your Button event. Don't forget to set Update condition in Contextmenu binding (xaml)

In my view the best form of work this out is adding an ContextMenu for each row of DataGrid, we can do it in the following way:
In the XAML, place in your DataGrid an listener to event LoadingRow:
<!-- resume version of declaration your DataGrid -->
<DataGrid x:Name="Squadre_DataGrid" LoadingRow="Squadre_DataGrid_LoadingRow" />
In the CodeBehind, come on add the ContextMenu for each row:
private void Squadre_DataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
ContextMenu _contextMenu = new ContextMenu();
MenuItem mia = new MenuItem();//item 1
MenuItem mib = new MenuItem();//item 2
....
_contextMenu.Add(mia);
_contextMenu.Add(mib);
....
e.Row.ContextMenu = _contextMenu;//add context menu to row
}

Related

How to enable menuitems on a ContextMenu - WPF

I'm trying to create a ContextMenu on a UserControl derived object. Note: This is NOT a WinForms App, it is pure WPF.
So I create the ContextMenu thus:
<UserControl.Resources>
<ContextMenu x:Key="cmLCD_CopyCutPaste">
<MenuItem Name="CutOption" Header="{x:Static p:Resources.Popup_Cut}" Command="Cut" Click="MenuItem_Cut" IsEnabled="True"/>
<MenuItem Name="CopyOption" Header="{x:Static p:Resources.Popup_Copy}" Command="Copy" Click="MenuItem_Copy" IsEnabled="True"/>
<MenuItem Name="PasteOption" Header="{x:Static p:Resources.Popup_Paste}" Command="Paste" Click="MenuItem_Paste" IsEnabled="True"/>
</ContextMenu>
</UserControl.Resources>
The mousebutton event is setup thus:
d:DesignHeight="66" d:DesignWidth="340" Focusable="True" KeyDown="Grid_KeyDown" MouseRightButtonDown="EMS_UI_LCDscreen_MouseRightButtonDown" >
In the code behind, the constructor starts like this:
public EMS_UI_LCDscreen()
{
InitializeComponent();
// Find the ContextMenu created on this object - it is called cmLCD_CopyCutPaste
ContextMenu cm = FindResource("cmLCD_CopyCutPaste") as ContextMenu;
// If we found the contextMenu, assign it the ContextMenu placeholder for this instance.
if (cm != null)
{
ContextMenu = cm;
}
...
The mouse click(right button) is handled like this:
private void EMS_UI_LCDscreen_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (this.ContextMenu is ContextMenu cm)
{
if (cm != null)
{
if (DataContext is DeviceEditorData ded)
{
cm.DataContext = ded.Device.GetStructuredLocation(0);
cm.IsEnabled = true;
cm.PlacementTarget = sender as Button;
cm.IsOpen = true;
}
}
}
}
There are the three event handlers to actually respond to the user selection on the ContextMenu:
private void MenuItem_Cut(object sender, RoutedEventArgs e)
{
}
private void MenuItem_Copy(object sender, RoutedEventArgs e)
{
}
private void MenuItem_Paste(object sender, RoutedEventArgs e)
{
}
View of the ContextMenu being shown on the LCD Image component
The component I have created appears in a few different contexts, but the one I'm mainly interested in is inside of a DataGrid that shows various items of data, each with one of these components. when right clicked, the component faithfully displays the popup context menu as expected..... BUT all the items on the menu are grayed out and basically not enabled.
So my question is, what is the missing piece of glue that effectively enables the menu items so that they can be clicked to do the required actions. Most of the answers already seen on the net go into detail of how to do it in a WinForms app, but despite hours of searching, I can find no clear solution to what should be a very simple task of enabling the menu items.
Can some kind soul please put me out of my anguish and in a few lines of code show me how to do it! Thank you
UPDATE: This is the XAML implementation for the LCD Component:
<UserControl x:Class="EMS_Config_Tool.UIComponents.WPF.EMS_UI_LCDscreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:p="clr-namespace:EMS_Config_Tool.Properties"
mc:Ignorable="d"
d:DesignHeight="66" d:DesignWidth="340" Focusable="True" KeyDown="Grid_KeyDown" MouseRightButtonDown="EMS_UI_LCDscreen_MouseRightButtonDown" >
<UserControl.Resources>
<ContextMenu x:Key="cmLCD_CopyCutPaste">
<MenuItem Name="CutOption" Header="{x:Static p:Resources.Popup_Cut}" Command="{Binding Cut}"/>
<MenuItem Name="CopyOption" Header="{x:Static p:Resources.Popup_Copy}" Command="{Binding Copy}"/>
<MenuItem Name="PasteOption" Header="{x:Static p:Resources.Popup_Paste}" Command="{Binding Paste}"/>
</ContextMenu>
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="Cut"
CanExecute="CutCommand_CanExecute"
Executed="CutCommand_Executed" />
<CommandBinding Command="Copy"
CanExecute="CopyCommand_CanExecute"
Executed="CopyCommand_Executed" />
<CommandBinding Command="Paste"
CanExecute="PasteCommand_CanExecute"
Executed="PasteCommand_Executed" />
</UserControl.CommandBindings>
</UserControl>
As can be seen, it is very simple - there are no items added to the control - it only serves as a Canvas upon which the code behind draws all the needed items which are purely graphic "draw" items.
This is the implementations of the 6 methods referenced, in the corresponding .cs file for the xaml above:
private void CutCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Verify relevant conditions and set CanExecuteRoutedEventArgs.CanExecute accordingly
e.CanExecute = true;
}
private void CutCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Execute the command action
if (sender is EMS_UI_LCDscreen lcd)
{
if (TheDevice != null)
{
Clipboard.SetDataObject(TheDevice.GetStructuredLocation(0).ToString());
TheDevice.SetLocation(0, "");
lcd.StructuredTextToShow = TheDevice.GetStructuredLocation(0);
}
else
{
Clipboard.SetDataObject(lcd.StructuredTextToShow.ToString());
lcd.StructuredTextToShow.SetString("");
}
}
}
private void CopyCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Verify relevant conditions and set CanExecuteRoutedEventArgs.CanExecute accordingly
e.CanExecute = true;
}
private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Execute the command action
if (sender is EMS_UI_LCDscreen lcd)
{
if (TheDevice!=null)
Clipboard.SetDataObject(TheDevice.GetStructuredLocation(0).ToString());
else
Clipboard.SetDataObject(lcd.StructuredTextToShow.ToString());
}
}
private void PasteCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Verify relevant conditions and set CanExecuteRoutedEventArgs.CanExecute accordingly
IDataObject iData = Clipboard.GetDataObject();
// Is the Data Text?
if (iData.GetDataPresent(DataFormats.Text))
e.CanExecute = true;
else
e.CanExecute = false;
}
private void PasteCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Execute the command action
if (sender is EMS_UI_LCDscreen lcd)
{
// Retrieves data
IDataObject iData = Clipboard.GetDataObject();
// Is the Data Text?
if (iData.GetDataPresent(DataFormats.Text))
{
if (TheDevice != null)
{
TheDevice.SetLocation(0, (string)iData.GetData(DataFormats.Text));
lcd.StructuredTextToShow = TheDevice.GetStructuredLocation(0);
}
else
{
lcd.StructuredTextToShow.SetString((string)iData.GetData(DataFormats.Text));
}
}
}
}
Finally, the ContextMenu is assigned to the object in the constructor:
public EMS_UI_LCDscreen()
{
InitializeComponent();
// Find the ContextMenu created on this object - it is called cmLCD_CopyCutPaste
ContextMenu cm = FindResource("cmLCD_CopyCutPaste") as ContextMenu;
// If we found the contextMenu, assign it the ContextMenu placeholder for this instance.
if (cm != null)
{
ContextMenu = cm;
}
...
The menu is shown on the component in response to right-click, the cut/copy/paste methods respond to the key ops, but clicking on the menu items does not fire the corresponding methods.
I can't see why that should be so, it all looks correct to me, but perhaps there is something missing that is preventing it working. One suggestion was to use a "relay command" type of thingy, but what and how that may be, is unclear.
You should remove all code that sets IsEnabled to true. It's redundant as the value is true by default.
The buttons are disabled because you have (accidentally?) attached a command to the MenuItem.Command property but no corresponding command handler.
The framework will try to invoke a CanExecute handler. Since there is no one defined, a default handler is returned that sets CanExecuteRoutedEventArgs.CanExecute to false, which will disable the button (ICommandSource).
Not sure if your intention was to use a command here or you wrongfully guessed the Command property is like a name property (since you have also registered Click event handlers). See Commanding Overview to learn more.
Anyway, this is how you can register a command handler:
You have assigned predefined application commands (cut, copy and paste - the MenuItem.Command string values in your XAML implicitly references the static ApplicationCommands commands). These commands are routed commands (behavior is identical to routed events - in fact routed commands are routed events). Therefore, you must define the command bindings on a parent element of the command source (the element that invokes the command) as the command will bubble up the tree.
A UIElement.CommandBinding consists of the specified CommandBinding.Executed handler and the optional CommandBinding.CanExecute handler.
Use the CanExecute handler to control the disabled states of the command source e.g., a Button. If the command source should be always enabled, simply omit the CanExecute handler.
XAML
<Window>
<Window.CommandBindings>
<CommandBinding Command="Cut"
CanExecute="CutCommand_CanExecute"
Executed="CutCommand_Executed" />
</Window.CommandBindings>
</Window>
C#
partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var cutCommandBinding =
new CommandBinding(ApplicationCommands.Cut, CutCommand_Executed, CutCommand_CanExecute)
this.CommandBindings.Add(cutCommandBinding);
}
}
Then create the corresponding command handlers in the code-behind.
private void CutCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Verify relevant conditions and set CanExecuteRoutedEventArgs.CanExecute accordingly
e.CanExecute = true;
}
private void CutCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Execute the command action
}
Update
To force command handling on the UserControl, you can explicitly set the MenuItem.CommandTarget property to reference the UserControl.
You can also assign the UserControl.Contextmenu from XAML:
<UserControl>
<UserControl.Resources>
<Style TargetType="MenuItem">
<Setter Property="CommandTarget"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}" />
</Style>
</UserControl.Resources>
<UserControl.Contextmenu>
<ContextMenu>
<MenuItem Name="CutOption" Header="{x:Static p:Resources.Popup_Cut}" Command="{Binding Cut}"/>
<MenuItem Name="CopyOption" Header="{x:Static p:Resources.Popup_Copy}" Command="{Binding Copy}"/>
<MenuItem Name="PasteOption" Header="{x:Static p:Resources.Popup_Paste}" Command="{Binding Paste}"/>
</ContextMenu>
</UserControl.Contextmenu>
<UserControl.CommandBindings>
<CommandBinding Command="Cut"
CanExecute="CutCommand_CanExecute"
Executed="CutCommand_Executed" />
<CommandBinding Command="Copy"
CanExecute="CopyCommand_CanExecute"
Executed="CopyCommand_Executed" />
<CommandBinding Command="Paste"
CanExecute="PasteCommand_CanExecute"
Executed="PasteCommand_Executed" />
</UserControl.CommandBindings>
</UserControl>
This was the solution that finally worked for me. The elixir was assigning the commands, not with the "Binding" term, but directly:
<UserControl x:Class="EMS_Config_Tool.UIComponents.WPF.EMS_UI_LCDscreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:p="clr-namespace:EMS_Config_Tool.Properties"
mc:Ignorable="d"
d:DesignHeight="66" d:DesignWidth="340" Focusable="True" KeyDown="Grid_KeyDown" MouseRightButtonDown="EMS_UI_LCDscreen_MouseRightButtonDown" >
<UserControl.Resources>
<Style TargetType="MenuItem">
<Setter Property="CommandTarget"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}" />
</Style>
</UserControl.Resources>
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem Name="CutOption" Header="{x:Static p:Resources.Popup_Cut}" Command="Cut" />
<MenuItem Name="CopyOption" Header="{x:Static p:Resources.Popup_Copy}" Command="Copy" />
<MenuItem Name="PasteOption" Header="{x:Static p:Resources.Popup_Paste}" Command="Paste" />
</ContextMenu>
</UserControl.ContextMenu>
<UserControl.CommandBindings>
<CommandBinding Command="Cut"
CanExecute="CutCommand_CanExecute"
Executed="CutCommand_Executed" />
<CommandBinding Command="Copy"
CanExecute="CopyCommand_CanExecute"
Executed="CopyCommand_Executed" />
<CommandBinding Command="Paste"
CanExecute="PasteCommand_CanExecute"
Executed="PasteCommand_Executed" />
</UserControl.CommandBindings>
</UserControl>
The constructor and assignment of images looks like this:
public EMS_UI_LCDscreen()
{
InitializeComponent();
Image ObjImage1 = new Image();
Image ObjImage2 = new Image();
Image ObjImage3 = new Image();
ObjImage1.Source = new BitmapImage(new Uri(#"pack://application:,,,/Graphics\Misc Icons\Cut.png"));
CutOption.Icon = ObjImage1;
ObjImage2.Source = new BitmapImage(new Uri(#"pack://application:,,,/Graphics\Misc Icons\Copy.png"));
CopyOption.Icon = ObjImage2;
ObjImage3.Source = new BitmapImage(new Uri(#"pack://application:,,,/Graphics\Misc Icons\Paste.png"));
PasteOption.Icon = ObjImage3;
...
... and displaying the context menu on the component in response to the mouse right click:
private void EMS_UI_LCDscreen_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (ContextMenu is ContextMenu cm)
{
if (DataContext is DeviceEditorData ded)
{
cm.DataContext = ded.Device.GetStructuredLocation((uint)LocationIndex);
cm.IsEnabled = true;
cm.PlacementTarget = sender as EMS_UI_LCDscreen;
cm.IsOpen = true;
}
if (DataContext is EMSBasicDevice dev)
{
cm.DataContext = dev.GetStructuredLocation((uint)LocationIndex);
cm.IsEnabled = true;
cm.PlacementTarget = sender as EMS_UI_LCDscreen;
cm.IsOpen = true;
}
}
}
For brevity, I have not shown the ..._CanExecute / ...Executed methods, they are as shown previously in the question.
Thanks to all who have helped in resolving this issue.
View of the finished context Menu in situ
So, if you are using DataContext and Binding's, you can do the following
remove the direct control with event handlers, i.e. Click, and direct setting of Enabled.
use Binding to bind menu action to the handler inside the view model: Command={Binding CutCommand} (or any other handler).
use ICommand.CanExecute of the command handler (CutCommand in the sample above). to manage Enabled state of menu item.
So, let's put all together.
In the XAML
<MenuItem Name="CutOption" Header="{x:Static p:Resources.Popup_Cut}"
Command="{Binding CutCommand}"/>
In the ViewModel
class ViewModel: INotifyPropertyChanged
{
...
public ICommand CutCommand { get; }
public ViewModel()
{
//you can just omit second arg if you always can call Cut.
CutCommand = new RelayCommand(CutHandler, ()=>CanCut()));
}
...
}
Here RelayCommand is a https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/relaycommand or any other implementation of simple command.
If you need to update the Enabled state on-the-fly, you can call CommandManager.InvalidateRequerySuggested(); to reevaluate CanExecute of all commands.

Deselect on single click in a ListBox with Extended SelectionMode

I have a ListBox with SelectionMode="Extended". You can only deselect the last item by holding down ctrl while clicking on it. I would like to be able to deselect the item by just clicking on it while not changing the behavior of the Extended selection mode other than that.
I only found one question about this topic and it actually has a different goal (being able to deselect all items by clicking outside of the ListBox).
If I understand your requirement correctly you could handle the PreviewMouseLeftButtonDown event for the ListBoxItem container and de-select it if it's already selected:
<ListBox SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseLeftButtonDown"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem>1</ListBoxItem>
<ListBoxItem>2</ListBoxItem>
<ListBoxItem>3</ListBoxItem>
</ListBox>
private void OnMouseLeftButtonDown(object sender, MouseEventArgs e)
{
ListBoxItem lbi = sender as ListBoxItem;
if (lbi != null)
{
if (lbi.IsSelected)
{
lbi.IsSelected = false;
e.Handled = true;
}
}
}
This should allow you to be able to de-select an item without using the CTRL key.

Why right click event not worked in WPF?

I want to delete my listbox item on right click. But, right click event not worked in my case.
Below is the code which I tried.
In constructor:
listBox1.MouseDown += new MouseButtonEventHandler(listBox1_MouseRightClick);
Right Click:
private void listBox1_MouseRightClick(object sender, MouseButtonEventArgs e)
{
if (sender is ListBoxItem)
{
ListBoxItem item = (ListBoxItem)sender;
Harvest_TimeSheetEntry entryToDelete = (Harvest_TimeSheetEntry)item.DataContext;
MessageBoxResult Result = System.Windows.MessageBox.Show("Are you sure?", "Delete Confirmation", System.Windows.MessageBoxButton.YesNo);
if (Result == MessageBoxResult.Yes)
{
Globals._globalController.harvestManager.deleteHarvestEntry(entryToDelete);
}
else
{
System.Windows.MessageBox.Show("Delete operation Terminated");
}
}
}
In xaml:
<ListBox x:Name="listBox1" ItemsSource="{Binding}" Margin="0,131,0,59" ItemTemplateSelector="{StaticResource templateSelector}" SelectionMode="Single" MouseRightButtonDown="listBox1_MouseRightClick">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseDown" Handler="listBox1_MouseRightClick"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Please suggest, how should I use right click event so it could work?
Your original code seems redundant and verbose. MouseRightButtonDown isn't working because there is already an event handling listbox item selection and the ListBoxItem datacontext is simply the SelectedItem of listBox1.
Get rid of overriding the style and just declare the listbox with the preview event. This will tunnel MouseRightButtonDown instead of bubble it.
<ListBox x:Name="listBox1"
ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource templateSelector}"
Margin="0,131,0,59"
SelectionMode="Single"
PreviewMouseRightButtonDown="listBox1_MouseRightClick" />
In the constructor, get rid of this
listBox1.MouseDown += new MouseButtonEventHandler(listBox1_MouseRightClick);
Now in the event handler, sender is your listbox1 but if you're not tying this event to other listboxes, simply get the selectedItem from listbox1 and cast it to the appropriate object. Otherwise if you decide you want the functionality on multiple listboxes cast sender to ListBox
private void listBox1_MouseRightClick(object sender, MouseButtonEventArgs e)
{
Harvest_TimeSheetEntry entryToDelete = (Harvest_TimeSheetEntry)listBox1.SelectedItem;
if(entryToDelete != null)
{
//Do work
}
}
Deleting records on right click is not a good design and it leads users make more confuse the functionality. Still if you want to do something, then you can go for the PreviewMouseRightButtonDown event. Please see the below snippet
ListBox1.PreviewMouseRightButtonDown += new MouseButtonEventHandler(ListBox1_MouseRightButtonDown);
Change your XAML as follows
<ListBox x:Name="listBox1"
ItemsSource="{Binding}"
Margin="0,131,0,59"
ItemTemplateSelector="{StaticResource templateSelector}"
SelectionMode="Single">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="ListBox1_PreviewMouseRightButtonDown" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

Getting WPF Data Grid Context Menu Click Row

I have a WPF DataGrid
<DataGrid AutoGenerateColumns="False" Name="dataGrid1" IsReadOnly="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="Site" Binding="{Binding Site}" Width="150" />
<DataGridTextColumn Header="Subject" Binding="{Binding Subject}" Width="310" />
</DataGrid.Columns>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete" Click="Context_Delete">
<MenuItem.Icon>
<Image Width="12" Height="12" Source="Images/Delete.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
I have the click event handler as:
private void Context_Delete(object sender, System.EventArgs e) { }
How do I get the row on which the Context Menu was before the click? The sender object is System.Windows.Controls.MenuItem, not the DataGridRow. How do I get the DataGridRow where the Context Menu was clicked. (I set the DataGrid.ItemSource in the code behind file.)
So based on your example code, I presume you bind your DataGrid to an ObservableCollection of objects of which you bind the properties Site and Subject to the DataGridColumns.
Essentially, all you need to do is figure out what the item bound to the clicked DataGridRow is and remove that from your ObservableCollection. Here is some example code to get you started:
private void Context_Delete(object sender, RoutedEventArgs e)
{
//Get the clicked MenuItem
var menuItem = (MenuItem)sender;
//Get the ContextMenu to which the menuItem belongs
var contextMenu = (ContextMenu)menuItem.Parent;
//Find the placementTarget
var item = (DataGrid)contextMenu.PlacementTarget;
//Get the underlying item, that you cast to your object that is bound
//to the DataGrid (and has subject and state as property)
var toDeleteFromBindedList = (YourObject)item.SelectedCells[0].Item;
//Remove the toDeleteFromBindedList object from your ObservableCollection
yourObservableCollection.Remove(toDeleteFromBindedList);
}
Typically, you do not deal with rows (if you do - think again about the reasons) - instead you work with view model. When you open context menu, you get your item selected, so it can be accessed via the DataGrid.SelectedItem property. However, if you really need DataGridRow - you have your DataGrid.SelectedIndex and there is a lot of answers here on SO on how to get the row. like Get row in datagrid
To expand morincer's point above with an example, I ended up with a simpler approach...
private void MenuItem_OnClickRemoveSource(object sender, RoutedEventArgs e)
{
if (SourceDataGrid.SelectedItem == null) return; //safety first
_importViewModel.SourceList.Remove((SourceFileInfo)SourceDataGrid.SelectedItem);
}
In my case, the
_importViewModel.SourceList
is the ObservableCollection the rows are bound to. So per best practices, I simple remove the selected item from the collection and the binding takes care of the UI.
dsfgsho's answer worked for me, but right clicking on a grid row does not automatically select it. This means that if your focus is elsewhere and you right-click and select a context menu item, you can get an out of range exception on item.SelectedCells[0], or if you have a row selected and right-click on a different row, you may get unexpected results.
I dealt with this by handling "PreviewMouseRightButtonDown" on the Datagrid. Here I am explicitly selecting a row when it is right-clicked. I forget where my UIHelpers class came from (probably elsewhere on this site - I was using it to resolve drag & drop items), but this should point you in the right direction if you are running into this problem. This is an extension of the accepted answer:
// handle right mouse click to select the correct item for context menu usage
private void myDataGrid_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
//find the clicked row
DataGridRow row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement) sender, e.GetPosition(myDataGrid));
if (row == null)
{
Debug.WriteLine("Row is null");
return;
}
else
{
Debug.WriteLine("Grid Row Index is " + row.GetIndex().ToString());
(sender as DataGrid).SelectedIndex = row.GetIndex();
}
}
Elemental Pete's UIHelper probably stemmed from:
http://www.hardcodet.net/2009/03/moving-data-grid-rows-using-drag-and-drop
This Article lists a Zip that contains UIHelper.cs.
It's not my Code so no copy/paste here.
The accepted answer from dsfgsho makes sense but when using CommandBinding for the standard ApplicationCommands rather than an explicit Click event it is a little different as the sender is not the MenuItem but the DataGrid itself.
XAML:
<DataGrid.CommandBindings>
<CommandBinding Command="Cut" CanExecute="DataGrid_CanCut" Executed="DataGrid_Cut" />
<CommandBinding Command="Copy" CanExecute="DataGrid_CanCopy" Executed="DataGrid_Copy" />
<CommandBinding Command="Paste" CanExecute="DataGrid_CanPaste" Executed="DataGrid_Paste" />
<CommandBinding Command="New" CanExecute="DataGrid_CanAddNew" Executed="DataGrid_AddNew" />
<CommandBinding Command="Delete" CanExecute="DataGrid_CanDelete" Executed="DataGrid_Delete" />
</DataGrid.CommandBindings>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="Cut" />
<MenuItem Command="Copy" />
<MenuItem Command="Paste" />
<MenuItem Command="New" />
<MenuItem Command="Delete" />
<Separator />
<MenuItem Header="Test" Command="{Binding CustomContextCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
Code Behind:
private void DataGrid_Delete(object sender, ExecutedRoutedEventArgs e)
{
// Test whether cleared, resolved, etc., and confirm deletion
var datagrid = (DataGrid)sender;
var trans = (DataClasses.BankTransaction)datagrid.SelectedCells[0].Item;
// Take action here; e.g., remove it from the underlying collection, remove it
// from the DB, etc.
e.Handled = true;
}
private void DataGrid_CanDelete(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}

Determining which ListViewItem was clicked on in a ListView when executing a ContextMenu MenuItem

I'm trying to use the context menu in a listview to run some code that requires data from which item it originated from.
I initially just did this:
XAML:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.Resources>
<ContextMenu x:Key="resourceContextMenu">
<MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click" />
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource resourceContextMenu}" />
</Style>
</ListView.ItemContainerStyle>
...
C#:
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
// code that needs item data here
}
But I found that the originating listview item was not accessible that way.
I've read some tactics about how to get around this, like intercepting the MouseDown event and setting a private field to the listviewitem that was clicked, but that doesn't sit well with me as it seems a bit hacky to pass data around that way. And WPF is supposed to be easy, right? :) I've read this SO question and this MSDN forum question, but I'm still not sure how to really do this, as neither of those articles seem to work in my case. Is there a better way to pass the item that was clicked on through to the context menu?
Thanks!
Similar to Charlie's answer, but shouldn't require XAML changes.
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
MenuItem menu = sender as MenuItem;
ListViewItem lvi = lvResources.ItemContainerGenerator.ContainerFromItem(menu.DataContext) as ListViewItem;
}
Well in the cmMetadata_Click handler, you can just query the lvResources.SelectedItem property, since lvResources will be accessible from the code-behind file that the click handler is located in. It's not elegant, but it will work.
If you want to be a little more elegant, you could change where you set up your ContextMenu. For example, you could try something like this:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<TextBlock Text="{TemplateBinding Content}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</ListView.Style>
<ListViewItem>One Item</ListViewItem>
<ListViewItem>Another item</ListViewItem>
</ListView>
What this does is plug in a template for your ListViewItem, and then you can use the handy TemplatedParent shortcut to assign the ListViewItem to the DataContext of your menu item.
Now your code-behind looks like this:
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
MenuItem menu = sender as MenuItem;
ListViewItem item = menu.DataContext as ListViewItem;
}
Obviously the downside is you will now need to complete the template for a ListViewItem, but I'm sure you can find one that will suit your needs pretty quickly.
So I decided to try and implement a command solution. I'm pretty pleased with how it's working now.
First, created my command:
public static class CustomCommands
{
public static RoutedCommand DisplayMetadata = new RoutedCommand();
}
Next in my custom listview control, I added a new command binding to the constructor:
public SortableListView()
{
CommandBindings.Add(new CommandBinding(CustomCommands.DisplayMetadata, DisplayMetadataExecuted, DisplayMetadataCanExecute));
}
And also there, added the event handlers:
public void DisplayMetadataExecuted(object sender, ExecutedRoutedEventArgs e)
{
var nbSelectedItem = (MyItem)e.Parameter;
// do stuff with selected item
}
public void DisplayMetadataCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
I was already using a style selector to dynamically assign styles to the listview items, so instead of doing this in the xaml, I have to set the binding in the codebehind. You could do it in the xaml as well though:
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
MyItem selectedItem = (MyItem)item;
Style s = new Style();
var listMenuItems = new List<MenuItem>();
var mi = new MenuItem();
mi.Header= "Get Metadata";
mi.Name= "cmMetadata";
mi.Command = CustomCommands.DisplayMetadata;
mi.CommandParameter = selectedItem;
listMenuItems.Add(mi);
ContextMenu cm = new ContextMenu();
cm.ItemsSource = listMenuItems;
// Global styles
s.Setters.Add(new Setter(Control.ContextMenuProperty, cm));
// other style selection code
return s;
}
I like the feel of this solution much better than attempting to set a field on mouse click and try to access what was clicked that way.

Categories

Resources