There is a WPF MVVM app. On the main view I have a list of elements, which are defined with ListView.ItemTemplate, in that I want to have a context menu with Delete action.
The Command for that is separated from the view and is kept in ViewModel DreamListingViewModel.
The problem is that on clicking on Delete I can't get it to execute the command on ViewModelk as context there is that of the item, not the items container.
I can make it work somehow by moving the context menu definition outside of the list view elements, but then when I open the context menu, it flickers, as if it's being called "20" times (which what I think does happen, as many times as I have elements in collection), anyways, I need a clean solution for that and I am very bad with XAML.
Here is how my View looks:
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 5 0 5" Background="Transparent" Width="auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding DeleteSelectedDream}"
CommandParameter="{Binding DeleteSelectedDream,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type viewmodels:DreamListingViewModel}}}"
/>
</ContextMenu>
</Grid.ContextMenu>
...
It's the main window and initialized in a generic host in App.cs:
public partial class App : Application
{
private readonly IHost _host;
public App()
{
...
_host = Host.CreateDefaultBuilder().ConfigureServices(services =>
{
...
services.AddTransient<DreamListingViewModel>();
services.AddSingleton((s) => new DreamListingView()
{
DataContext = s.GetRequiredService<DreamListingViewModel>()
});
...
}).Build();
The Command and CommandParameter values are what I've been experimenting with, but it doesn't work
Here is how my ViewModel looks:
internal class DreamListingViewModel : ViewModelBase
{
public ICommand DeleteSelectedDream{ get; }
...
Finally, when the command is fired, I need to pass the current element on which the menu has been shown.
So, here is what I want:
User clicks on a list item with mouse right button - OK
Sees a menu with Delete entry - OK
On Delete click, Command DeleteSelectedDream is fired with current dream (item in the list) as a parameter - ERR
Your example is somewhat lacking necessary information, but I'll try to help.
First you need to verify that you are actually bound to your view model. Are you using Prism or just standard WPF ? In the constructor of your code-behind of your view, set up the DataContext to an instance of your VM.
InitializeComponent();
this.DataContext = new DreamListingViewModel();
Now, you bind to a relative source via Mode 'FindAncestor' and the AncestorType is set to the type of a view model. That usually won't work, as the view model is not naturally a part of the visual tree of your WPF view. Maybe your ItemTemplate somehow wires it up. In a large WPF app of mine I use Telerik UI for WPF and a similar approach to you, however, I set up the DataContext of the Context menu to a RelativeSource set to Self combined with Path set to PlacementTarget.DataContext.
You do not have to use all the XAML in my example, just observe how I do it. Exchange 'RadContextMenu' with 'ContextMenu', Ignore the Norwegian words - here and only use what you need :
<telerik:RadContextMenu x:Key="CanceledOperationsViewContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
<MenuItem Header="{Binding PatientName}" IsEnabled="False" Style="{StaticResource ContextMenuHeading}" />
<MenuItem Header="Gå til aktuell SomeAcme-liste" IsEnabled="{Binding IsValid}" Command="{Binding NavigateToListCommand}" />
<MenuItem Header="Åpne protokoll..." Command="{Binding CommonFirstProtocolCommand, Mode=OneWay}" CommandParameter="{Binding}" />
<MenuItem Header="Åpne Opr.spl.rapport...." Command="{Binding CommonFirstNurseReportCommand, Mode=OneWay}" CommandParameter="{Binding}" />
</telerik:RadContextMenu>
In your example it will be :
<ContextMenu x:Key="SomeContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
<MenuItem Header="Delete" />
Command="{Binding DeleteSelectedDream}"
CommandParameter="{Binding DeleteSelectedDream,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListViewItem}}}"
/>
</telerik:RadContextMenu>
Now I here consider you are using the class ListViewItem
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.listviewitem?view=netframework-4.8
It might be that you need to specify DataContext.DeleteSelectedDream here to be sure you bind up to the DataContext where your implementation of ICommand is.
Accidentally found this answer, that's basically what I needed, just added to it a CommandParameter to send the item and it works like magic!
<ListView Name="lvDreams" ItemsSource="{Binding Dreams}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 5 0 5" Background="Transparent" Width="auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem
Header="Delete"
Command="{Binding DataContext.DeleteSelectedDream, Source={x:Reference lvDreams}}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</Grid.ContextMenu>
...
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I find the following the simplest; perhaps it's because I do not understand WPF, but it's "simple" to remember, and it works with my MVVM pattern.
<ListBox ItemsSource="{Binding MyViewModelItemsCollection, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent" >
<TextBlock Text="{Binding Path=Name, Converter={StaticResource FullPathToFileName}, Mode=OneWay}" Grid.Column="0">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem
Command="{Binding Path=DataContext.MyViewModelAction, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
Header="{Binding Name, Converter={StaticResource resourceFormat}, ConverterParameter={x:Static res:Resources.CONTEXT_MENU_BLOCK_APPLICATION}}">
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</grid>
</DataTemplate>
</ListBox.ItemTemplate
</ListBox>
The MyViewModelXXXXXXX named items are in the view model that is mapped to the data context of the control.
I have ListBox dynamically generated. Each item contains a ProgressBar which I can stop, pause or resume by the means of clicking the relative voice on its ContextMenu.
Here is the XAML code:
<ListBox Grid.Column="1" Name="TransfersList" Margin="30,10,-0.444,34.889" ItemsSource="{Binding DataTx}"
SelectionChanged="TransfersList_SelectionChanged" Grid.Row="1" Grid.ColumnSpan="3"
HorizontalContentAlignment="Stretch">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Ottieni informazioni" Click="GetInfo" />
<MenuItem Header="Metti in pausa" Click="PauseTransfer" />
<MenuItem Header="Riprendi trasferimento" Click="ResumeTransfer" />
<MenuItem Header="Annulla trasferimento" Click="StopTransfer" />
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.ItemTemplate>
<DataTemplate>
<ProgressBar Height="20" Minimum="0" Maximum="{Binding NChunks}" Name="gasparino_il_carbonaro"
Value="{Binding PbStatus}" Foreground="{Binding Color}" ToolTip="{Binding TooltipInfo}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When I click, for example, the ContextMenu "GetInfo" voice, I get always the '-1' SelectedIndex (even if I have many bars).
I suppose that the problem is due to the ProgressBar filling (performing steps and so XAML refreshing), when the ProgressBar is filling/refreshing the "system" becomes unable to understand the selected index (which bar I selected with right click?).
My concise question is:
How can I bind the right-clicked bar with the ContextMenuItem relative
method?
Hello,
I have a ListBox.ItemTemplate (containing obviously a DataTemplate) containing a Grid which itself contains a Label.
The Grid has a Grid.ContextMenu. The issue is that the ContextMenu only shows when I right click on the Label. Not outside.
Here is an image to show the problem if you didn't understand (But I am sure you did ;) !)
Here is the code :
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,1">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem CommandParameter="{Binding}" Command="{Binding Source={x:Reference window}, Path=DataContext.DeleteCommand}" Header="Supprimer">
<MenuItem.Icon>
<Image Source="/SchoolTools.Teacher;component/Images/DeleteIcon.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Grid.ContextMenu>
<Label Content="{Binding ClassYear}" Padding="0"
HorizontalAlignment="Center" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
Thanks !
Change the label's HorizontalAlignment to Stretch so that it fills the entire grid cell.
I have a problem with command binding in WPF. I have the following xaml:
<ItemsControl ItemsSource="{Binding Entity}" Name="Lst">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="qwerty" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" >
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Send2" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As you can see Button and its ContextMenu have the similar command-bindings. But when i click button its command is firing and when i click context menu's item its command isn't firing. Where am i wrong? Thanks in advance!
I had a similar problem before and solved it by passing the datacontext through the tag property of the container as below. I have it working on a grid ContextMenu but dont see any reason why this wont work on a button. Let me know if you have any problem
<Button Content="qwerty" Tag="{Binding DataContext,ElementName=Lst}" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" >
<Button.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Send2" Command="{Binding SaveCommand}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
The ContextMenu being separate from the visual tree, you cannot bind with and element outside of it.
If you check your output window, you should have a message saying that it can't find the object "Lst"
A common and easy workaround would be to manually set the DataContext in code-behind (note: this is not breaking MVVM at all. You are just performing a pure UI operation of linking DataContexts together):
In your Xaml:
<Button.ContextMenu>
<ContextMenu Opened="OnContextMenuOpened">
<MenuItem Header="Send2" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" />
</ContextMenu>
</Button.ContextMenu>
In code-behind:
public void OnContextMenuOpened(object sender, RoutedEventArgs args)
{
(sender as ContextMenu).DataContext = Lst.DataContext;
}
You are therefore linking the DataContext every time the ContextMenu is opened (so if Lst's DataContext changes, your ContextMenu will as well)
Alternatively (cleaner if you are bound to use it a lot of times), get the BindingProxy from this article: http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ and it'll do the trick!
I don't understand why I can't detect right click on my list box when I use an MVVM.
I use the event trigger but some events doesn't work.
<ListBox x:Name="PlaylistsList" ItemsSource="{Binding PlaylistsList}" HorizontalAlignment="Left">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonDown">
<i:InvokeCommandAction Command="{Binding NewPlaylistCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
My command "NewPlaylistCommand" is never call. Could you help me ?
Thank you.
EDIT:
I found solution to my problem, I used the ContextMenu to interact with my ListBoxItem
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="ListBox">
<TextBlock Text="{Binding Name}"/>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" Command="{Binding RenamePlaylistCommand}"/>
<MenuItem Header="Delete" Command="{Binding DeletePlaylistCommand}" CommandParameter="{Binding SelectedValue, ElementName=PlaylistsList}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Now I can right click on my item to Rename or Delete it.
Thank you for your help.
Most likely that something inside has swallowed your right click event and hence it has not bubbled up (I suspect it has something to do with context menu behaviors). You COULD use the Preview- events which bubble the other way.
The other solution you could use is to directly apply the interaction to each and every child in the list box (and their children as well).
However I honestly don't expect from a UI point of view that right clicking on a play list will create a new play list...I expect from my years of windows usage that a context menu should pop up, possibly with an "add new play list" menuitem.