How to bind command into ContextMenu in DataTemplate - c#

I'm a little bit lost with bindings.
I tried so many things in the last hour, I cannot enumerate all of them. I have an issue with a contextMenu inside a DataTemplate.
To explain: I have a UserControl. Its dataContext is itself. Inside this UserControl, I have an ItemsControl to represent a list of Hyperlink. My ItemsControl itemsSource is bound (it is composed of objects elements).
I redefined ItemsControl.ItemTemplate. Inside, I create a HyperLink, with TextBlock as child to make it work, and on this TextBlock, I set a ContextMenu by doing the following.
<TextBlock.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Enregistrer la pièce jointe" Foreground="Black">
<MenuItem Header="Dans le dossier patient" Command="{Binding DataContext.SaveAttachmentIntPatientFolderCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" CommandParameter="{Binding FilePath}" Foreground="Black" />
<MenuItem Header="Enregistrer sous ..." Command="{Binding DataContext.SaveAttachmentAsCommand}" CommandParameter="{Binding FilePath}" Foreground="Black" />
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
So I have
UserControl --> ItemsControl --> ItemTemplate --> HyperLink --> TextBlock --> ContextMenu --> ContextMenuItem
I know that my first relative source doesn't work, I have a binding error. What I want is to bind on my UserContorl datacontext, which have these commands.
How can I proceed?
Thanks

ContextMenu takes the DataContext of the ItemsControl and so it cannot access the ViewModel directly. Also It is not part of the VisualTree and so you cannot do RelativeSource binding. So We need to get the DataContext of the UserControl through TextBlock's Tag property and then bind to ContextMenu.
You refer the below code.
<TextBlock Text="{Binding }" Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=UserControl}}">
<TextBlock.ContextMenu>
<ContextMenu >
<MenuItem Header="Enregistrer la pièce jointe" Foreground="Black">
<MenuItem Header="Dans le dossier patient"
Command="{Binding Path=PlacementTarget.Tag.SaveAttachmentIntPatientFolderCommand,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Foreground="Black" />
<MenuItem Header="Enregistrer sous ..."
Command="{Binding Path=PlacementTarget.Tag.SaveAttachmentAsCommand,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Foreground="Black" />
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>

Related

WPF firing a view model Command from a list view item

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.

Find the view models DataContext within a DataTemplate

I have a ContentControl with a ContentTemplateSelector like this
<Border>
<ContentControl Content="{Binding MyObject}" ContentTemplateSelector="{Binding MyContentTemplateSelector"}/>
</Border>
Within my DataTemplate, I want to access an ICommand defined in the border's DataContext(ViewModel)
I've tried the FindAncestor Mode, but I suspect that this only operates within the DataTemplate and not the whole VisualTree. This is the XAML code which is in the DataTemplate:
<i:InvokeCommandAction Command="{Binding Path=DataContext.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Border,AncestorLevel=2}}"
CommandParameter="{Binding }" />
Any suggestions on how I can bind this command to the DataContext of my ViewModel? Thanks!
In your ContentControl, you coul add this property :
Tag="{Binding DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
Then in your InvokeCommandAction, you bind to the tag property :
Command="{Binding Tag.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentControl}}"

Bind contextMenu to a different viewmodel from treeview

I want to show a contextMenu item when I am using rightclick on a treeview item.
After that, I want to use a command when I click on my MenuItem, but I need to bind the command with a different viewmodel and the command parameter with the good viewmodel who come from my treeview selected item.
So for the moment, I have something like that :
<TreeView x:Name="TreeViewProtocolsAndEquipments" AllowDrop="True"
ItemsSource="{Binding ModuleParams}">
<TreeView.Resources>
<!-- CONTEXT MENU -->
<!-- Protocol -->
<ContextMenu x:Key="ContextMenuProtocol">
<MenuItem Header="Add new equipment" Command="{Binding AddNewEquipmentCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}">
<MenuItem.Icon>
<Image Source="Images/Add.png" />
</MenuItem.Icon>
</MenuItem>
<Separator />
</ContextMenu>
<!-- MODULE XXX -->
<!-- ModuleParam > xxx -->
<HierarchicalDataTemplate DataType="{x:Type xxx:ModuleParamXXXViewModel}" ItemsSource="{Binding ModuleItems}">
<TextBlock Text="XXX" Foreground="Green" ContextMenu="{StaticResource ContextMenuProtocol}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
For the moment my command is bind to xxx:ModuleParamXXXViewModel if I just let { binding }
Can I bind my Command to my ActivatedProtocolsAndEquipmentsTreeViewModel (the datacontext of this usercontrol) and keep on the CommandParameter my xxx:ModuleParamXXXViewModel (who is the Item from the treeview where we triggered the right click to show the contextMenu) ?
How can I achieve this in an other way with MVVM practice ?
I also tried to use this but it didn't work too :
<MenuItem Header="Add new equipment" Command="{Binding Path=DataContext.AddNewEquipmentCommand, Source={x:Reference TreeViewProtocolsAndEquipments}}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}">
And with this i get Object Reference not set to an instance of an object
The UserControl is not a visual ancestor of the MenuItem since a ContextMenu resides in its own visual tree.
Bind the Tag property of the TextBlock to the UserControl and then bind the Command property to the PlacementTarget of the ContextMenu:
<TreeView x:Name="TreeViewProtocolsAndEquipments" AllowDrop="True"
ItemsSource="{Binding ModuleParams}">
<TreeView.Resources>
<!-- CONTEXT MENU -->
<!-- Protocol -->
<ContextMenu x:Key="ContextMenuProtocol">
<MenuItem Header="Add new equipment"
Command="{Binding PlacementTarget.Tag.DataContext.AddNewEquipmentCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
CommandParameter="{Binding}">
<MenuItem.Icon>
<Image Source="Images/Add.png" />
</MenuItem.Icon>
</MenuItem>
<Separator />
</ContextMenu>
<!-- MODULE XXX -->
<!-- ModuleParam > xxx -->
<HierarchicalDataTemplate DataType="{x:Type xxx:ModuleParamXXXViewModel}" ItemsSource="{Binding ModuleItems}">
<TextBlock Text="XXX" Foreground="Green"
Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"
ContextMenu="{StaticResource ContextMenuProtocol}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>

WPF binding command to ContextMenu

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!

MVVM Light - Listbox of UserControls, how to know on which ItemIndex a button is clicked

I got a Listbox where each item is a Usercontrol MatchPanel.
That UserControl has a button.
I d like to remove an item when I click on the button of that item.
I used the SelectedItem which is bind to my ViewModel and works well. But sometimes I m able to click on a button of one item without moving the SelectedItem value (the Listbox item dont get focused even if I click on the button of that item...).
Hence I m looking for a way to receive in the command CloseSelectedMatchCommand a parameter which would tell me, for the button I have clicked, at which index of the Listbox it is.
Thanks
Here is my View
<UserControl
DataContext="{Binding ListTradingMatches, Source={StaticResource Locator}}" Height="503.175" Width="409">
<ListBox ItemsSource="{Binding Path=ListMatches}" SelectedItem="{Binding Path=SelectedMatch}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:MatchPanel />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is my MatchPanel UserControl
<UserControl x:Class="MatchPanel"
<Label Content="Pts"/>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}},
Path=DataContext.CloseSelectedMatchCommand}" CommandParameter="{Binding}">
</Button>
</Grid>
You can set the DataContext of your UserControl to the item in the DataTemplate:
<DataTemplate>
<StackPanel>
<local:MatchPanel DataContext="{Binding}" />
</StackPanel>
</DataTemplate>
Now the DataContext is set to the item from the collection, you can set the CommandParameter to the relevant property from that object...:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}, Path=DataContext.CloseSelectedMatchCommand}"
CommandParameter="{Binding Id}" />
..., or just the whole object:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}, Path=DataContext.CloseSelectedMatchCommand}"
CommandParameter="{Binding}" />

Categories

Resources