Binding command - Ancestor - c#

Okay, guys. I've been trying this for about 3 days now, and no amount of Googling is helping. Below is a snippet of my XAML (should be enough to follow along).
My problem is the command for the "ContextMenu".
As you can see I have DeleteTagCommand. Now that command works if I throw it in the position of CheckBoxCommand, which is great.. But it just will be called in it's current location, and it's driving me insane.
<ScrollViewer Grid.Column="0">
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Tags, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Value}" Margin="10,5,10,5" Command="{Binding DataContext.CheckBoxCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Grid}}}"
CommandParameter="{Binding }">
<CheckBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding DataContext.DeleteTagCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Grid}}}"
CommandParameter="{Binding}" />
</ContextMenu>
</CheckBox.ContextMenu>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
I've tried:
Calling 'ElementName' from all over the show, but it's never being picked up
Changing the 'AncestorLevel' to obscene numbers, hoping that was the problem
And more..
Not sure what would be useful for you guys, but below is the output message I get
Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.Grid', AncestorLevel='1''. BindingExpression:Path=DataContext.DeleteTagCommand; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
Thanks

ContextMenus aren't actually part of the same visual tree as their parents so they can't directly bind to any elements within it. They can, however, still bind to StaticResources. The trick is to thus use an intermediate proxy such as the BindingProxy class shown on this page. Start by adding an instance to your ItemsControl resource block:
<ItemsControl.Resources>
<local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</ItemsControl.Resources>
Then use it to bind your ContextMenu command:
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding Data.DeleteTagCommand, Source={StaticResource Proxy}}" CommandParameter="{Binding}" />
</ContextMenu>

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.

Problems when binding The Viewmodel to the contextMenu of a control WPF

I am currently having a problem with my project where I pass the binding of the main viewmodel to the contextmenu.
opening the context menu (using the right mouse button obviously) for the first time gives me this error
System.Windows.Data Error: 40 : BindingExpression path error: '(attached:DependencyObjectAttached.DataContextEx)' property not found on 'object' ''TextBlock' (Name='')'. BindingExpression:Path=PlacementTarget.(attached:DependencyObjectAttached.DataContextEx).QuotationCommandProcessor.ConvertProductCommand; DataItem='ContextMenu' (Name=''); target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
It may not be super big of a deal since the binding succeeds the second time the context menu opens, but as OCD as I was, I wish to fix this issue.
So here's what I have
I have a Page, inside the page is a Datagrid, the Datagrid has a Column whose cell template is the PlacementTarget of the ContextMenu
The context menu Command binds to a command of the view model of the page
The implementation i used was through attached property like this
<DataGrid ItemSource="{Binding MyItemSources}">
<DataGrid.Columns>
<DataGridTemplateColumn CellTemplate="{StaticResource MyCellStyle}"/>
<DataGrid.Columns>
</DataGrid>
The style looks like this
<DataTemplate x:Key="MyCellStyle">
<TextBlock Text="{Binding}" attached:DependencyObjectAttached.DataContextEx="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type local:MyPage}}}">
<TextBlock.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Click Here To Run Command" Command="{Binding PlacementTarget.(attached:DependencyObjectAttached.DataContextEx).CommandFromTheViewModel, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding}"/>
</ContextMenu>
<TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
the DependencyObjectAttached.DataContextEx
attached:DependencyObjectAttached.DataContextEx
is an attached property used to pass the viewmodel to the contextmenu
I have already tried using the Tag of the placementtarget (the Textblock) and it is working fine, however, I am using the Tag for some other purpose so attached property was the only option i can think of. Any suggestion?
Please try below code, this achieves the datacontext access of your main window or Page in your case.
Trick is not to create a DataTemplate and instead create ContextMenu as resource directly and then use this context menu for your DataGridCell as shown below.
<Window.Resources>
<ContextMenu x:Key="ContextMenu1">
<ContextMenu.Items>
<MenuItem Header="{Binding DataContext.Title,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window} }"/>
<MenuItem Header="{Binding DataContext.Title,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" />
<MenuItem Header="{Binding DataContext.Title,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" />
</ContextMenu.Items>
</ContextMenu>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding VentingTypesCollection}">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu1}" />
</Style>
</DataGrid.CellStyle>
</DataGrid>
</Grid>
Please remember, DataContext.Title property is a simple string property from my viewmodel.
I think once you achieve access to datacontext, binding anything from viewmodel will be very straight forward.
Hope this helps you.

Binding double click in nested listview

I've got a ListView nested into another Listview. Now I want to bind an double-click event to the ListViewItems of the inner ListView
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="DefaultTemplate">
<ListView Name="jobsView" ItemsSource="{Binding jobs}" SelectedItem="{Binding Path=SelectedProduction}" >
<ListView.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding Path=DataContext.ItemSelectedCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" CommandParameter="{Binding ElementName=jobsView, Path=SelectedItem}" />
</ListView.InputBindings>
</ListView>
</DataTemplate>
</UserControl.Resources>
<ListView Name="weekView" ItemsSource="{Binding dayList}" ItemTemplate="{StaticResource DefaultTemplate}" >
</ListView>
</UserControl>
I created a RelayCommand called ItemSelectedCommand in my ViewModel.
public RelayCommand ItemSelectedCommand { get; private set; }
The RelayCommand is not getting triggered. I guess I'm setting the wrong RelativeSource. How would it look correct?
Where is your ListView inserted. Is there in a Visual Tree a parent with type of UserControl?
Also, what's quite good to fix Binding errors is to take a look at the Console. There should be Binding errors that might point you wherte is the mistake. Usually is writes down where it is trying to search for object and property :)
Also, I am not sure if private get; is actually allowed when binding to property.
<StackPanel Grid.Column="1">
<StackPanel.Resources>
<DataTemplate x:Key="DefaultTemplate" DataType="{x:Type sys:String}">
<StackPanel>
<TextBlock Text="{Binding .}"/>
<ListView>
<ListView.ItemsSource>
<CompositeCollection>
<sys:String>Sub Item</sys:String>
</CompositeCollection>
</ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding DataContext.RenameCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, PresentationTraceSources.TraceLevel=High}" CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</StackPanel.Resources>
<ListView ItemTemplate="{StaticResource DefaultTemplate}">
<ListView.ItemsSource>
<CompositeCollection>
<sys:String> First Item</sys:String>
</CompositeCollection>
</ListView.ItemsSource>
</ListView>
</StackPanel>
Reason why it wasn't working for you is because the double Click was on an actual ListViewItem and NOT the ListView.

Binding ContextMenu to Datagrid Columns

I am trying to bind DataGrid column header to its own ContextMenu like this:
<DataGrid x:Name="AllLogs">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Show/Hide Columns"
ItemsSource="{Binding ElementName=AllLogs, Path=Columns}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
Its always sends the following error in output:
Cannot find source for binding with reference
'ElementName=AllLogs'. BindingExpression:Path=Columns;
DataItem=null; target element is 'MenuItem' (Name=''); target property
is 'ItemsSource' (type 'IEnumerable')
EDIT: Binding with a ComboBox works as expected
<ComboBox ItemsSource="{Binding ElementName=AllLogs, Path=Columns}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Header}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You should set first the DataContext of ContextMenu so that ItemsSource bind to Menu Item can inherit the same DataContext.
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show/Hide Columns"
ItemsSource="{Binding Columns}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
The reason that ContextMenu didn't work but ComboBox did is that ContextMenu is a Popup, that means it's not part of DataGrid's visual tree, so ElementName would not work as ComboBox did. In fact, #user1672994 was vary close to the answer.
<DataGrid x:Name="AllLogs">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Show/Hide Columns"
ItemsSource="{Binding PlacementTarget.Columns, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
The problem/misunderstanding is that ContextMenu isn't a part of a visual tree.
So ContextMenu.PlacementTarget is your "connection" to the UIElement, which is in the visual tree, therefore you have to go via PlacementTarget to access the elements from visual tree.
MSDN about PlacementTarget:
When the ContextMenu is assigned to the FrameworkElement.ContextMenu
or FrameworkContentElement.ContextMenu property, the
ContextMenuService changes this value of this property to the owning
FrameworkElement or FrameworkContentElement when the ContextMenu opens
In answer below you don't have to traverse searching an ancestor type, but do use an UIElement as DataContext for ContextMenu:
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show/Hide Columns" ItemsSource="{Binding Columns}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
I don't know the RadGrivView control per say, but the error means that it cannot find an IEnumerable property called "Columns" on your element. Are you sure it is a publicly accessible collection for the control?
ElementName uses VisualTree to find out desired element, it its not part of the current visual tree - up or down as it is with context menu you will get exception.
You could use Binding Source={x:Reference AllLogs} that does not use VisualTree, but unfortunately in your use case you would get exception of circular reference if you use it directly without style.
What you need to use is RelativeSource binding.
<Window.Resources>
<ContextMenu x:Key="headerMenu">
<ContextMenu.Items>
<MenuItem
Header="Show/Hide Columns"
ItemsSource="{Binding Columns, RelativeSource={RelativeSource AncestorType=DataGrid}}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu.Items>
</ContextMenu>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="ContextMenu" Value="{StaticResource headerMenu}" />
</Style>
</Window.Resources>
<Grid>
<DataGrid x:Name="AllLogs">
<DataGrid.Columns>
<DataGridTextColumn Header="ID"></DataGridTextColumn>
<DataGridTextColumn Header="Name"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
This would work as well - with Reference:
<Window.Resources>
<ContextMenu x:Key="headerMenu">
<ContextMenu.Items>
<MenuItem
Header="Show/Hide Columns"
ItemsSource="{Binding Source={x:Reference AllLogs}, Path=Columns}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu.Items>
</ContextMenu>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="ContextMenu" Value="{StaticResource headerMenu}" />
</Style>
</Window.Resources>
<Grid>
<DataGrid x:Name="AllLogs">
<DataGrid.Columns>
<DataGridTextColumn Header="ID"></DataGridTextColumn>
<DataGridTextColumn Header="Name"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>

Apply multiple AttachedCommandBehaviors using a style

I am trying to use AttachedCommandBehavior V2 to translate ListBoxItem events such as double click into commands that are execute against the view model.
I want to fire commands for multiple events, this is the example code I am trying to emulate:
<Border Background="Yellow" Width="350" Margin="0,0,10,0" Height="35" CornerRadius="2" x:Name="test">
<local:CommandBehaviorCollection.Behaviors>
<local:BehaviorBinding Event="MouseLeftButtonDown" Action="{Binding DoSomething}" CommandParameter="An Action on MouseLeftButtonDown"/>
<local:BehaviorBinding Event="MouseRightButtonDown" Command="{Binding SomeCommand}" CommandParameter="A Command on MouseRightButtonDown"/>
</local:CommandBehaviorCollection.Behaviors>
<TextBlock Text="MouseDown on this border to execute the command"/>
</Border>
Since I want to apply that to a ListBoxItem, I am trying to do it through a style by doing:
<ListBox.ItemContainerStyle>
<Style>
<Setter Property="acb:CommandBehaviorCollection.Behaviors">
<Setter.Value>
<acb:CommandBehaviorCollection>
<acb:BehaviorBinding Event="MouseDoubleClick" Command="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding}"/>
<acb:BehaviorBinding Event="KeyUp" Command="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding}"/>
</acb:CommandBehaviorCollection>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
But I get a compile error with that code that says error MC3089: The object 'CommandBehaviorCollection' already has a child and cannot add 'BehaviorBinding'. 'CommandBehaviorCollection' can accept only one child. Line 39 Position 11.
Also if I comment out one of the BehaviorBindings then it compiles but I get a runtime xaml load exception saying "Value cannot be null. Parameter name: property", so I'm not sure if I'm even taking the correct approach.
Can anyone provide an example of the correct syntax to set multiple behavior bindings on a ListBoxItem?
My solution uses interaction triggers and the ItemTemplate not the ItemContainerStyle.
This invokes a mouse double click or key up command in the text box, not the whole list box item.
<UserControl.Resources>
<DataTemplate DataType="{x:Type ViewModel:DataItem}" x:Key="ItemTemplate">
<ContentControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DoubleClickCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding KeyUpCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox Text="{Binding Name}">
</TextBox>
</ContentControl>
</DataTemplate>
</UserControl.Resources>
<ListBox x:Name="listBox" ItemTemplate="{StaticResource ItemTemplate}" ItemsSource={Binding Items} />
Where DataItem is something like
class DataItem : INotifyPropertyChanged
{
public string Name{get;set}
.. etc
}
and the view model set on the DataContext has an IList<DataItems> Items{get; private set} property.

Categories

Resources