DataContext binding on ContextMenu - c#

I have a DataGrid with a ContextMenu, I'm trying to figure out how to properly bind the context.
So far I have read that the context menu its ouside the visual tree, and therefore the DataContext is different. With that in mind the provided solution is to use the property Tag, but I still cannot make it work:
<UserControl>
<!--#region DataGrid-->
<DataGrid ItemsSource="{Binding Model.Collection}"
Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}, Path=DataContext}">
<!--#region Resources-->
<DataGrid.Resources>
<!--#region DataGridCell-->
<Style TargetType="DataGridCell">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Open Details"
Command="{Binding DataContext.OpenRowDetailsCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding DataContext.SelectedIndex, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
<!--#endregion DataGridCell-->
</DataGrid.Resources>
<!--#endregion Resources-->
<!--#region DataGridColumns-->
<DataGrid.Columns>
<DataGridTextColumn Header="Filename"
Binding="{Binding FileInfo.Name}"
Width="Auto" />
</DataGrid.Columns>
<!--#endregion DataGridColumns-->
</DataGrid>
<!--#endregion DataGrid-->
The DataContext of the UserControl is working fine as I have other commands which uses the DataContext that way.
Anyone sees any error or has any other approach?
Thanks in advance.

This should work provided that the OpenRowDetailsCommand and SelectedIndex properties are defined in the view model class of the parent UserControl:
<Style TargetType="DataGridCell">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Open Details"
Command="{Binding PlacementTarget.Tag.DataContext.OpenRowDetailsCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding PlacementTarget.Tag.DataContext.SelectedIndex, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>

I like to use a BindingProxy for this (as described in this SO answer)
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object),typeof(BindingProxy));
}
Use it like this:
<DataGrid ItemsSource="{Binding Model.Collection}">
<DataGrid.Resources>
<local:BindingProxy x:Key="VMProxy" Data="{Binding}" />
<local:BindingProxy x:Key="DataGridProxy" Data="{Binding RelativeSource={RelativeSource Self}}" />
<Style TargetType="DataGridCell">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Open Details"
Command="{Binding Data.OpenRowDetailsCommand, Source={StaticResource VMProxy}}"
CommandParameter="{Binding Data.SelectedIndex, Source={StaticResource DataGridProxy}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>

Related

Datagrid not firing AddingNewItem or InitializingNewItem event

I have a datagrid binded to observablecollection. By clicking button new row is added, and i wana to start editing new row as soon as its added. For this purpose i wana use AddingNewItem or InitializingNewItem events, because i think that i can acces newly added row in this event and call BeginInit on it (or something similar)
But in code like bellow, i cannot make events work. When i add new item, events are not fired. Am i missing something?
<DataGrid Margin="5"
AddingNewItem="enumerationsDataGrid_AddingNewItem"
AutoGenerateColumns="False"
InitializingNewItem="enumerationsDataGrid_InitializingNewItem"
ItemsSource="{Binding TypesAllAvailable}"
RowDetailsVisibilityMode="Collapsed">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Expander x:Name="rowDetailsExpander"
Cursor="Hand"
IsExpanded="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}, Mode=FindAncestor}, Mode=TwoWay, Path=DetailsVisibility, Converter={StaticResource visToBoolConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="0.7*"
Binding="{Binding TypeName}"
Header="Name" />
<DataGridTextColumn Width="0.2*"
Binding="{Binding Enumerations.Count}"
Header="Enums"
IsReadOnly="True" />
<DataGridTextColumn Width="0.7*"
Binding="{Binding Parent.Name}"
Header="Scope"
IsReadOnly="True" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel>
<DataGrid x:Name="enumerationsDataGrid"
Margin="5 0 5 5"
AddingNewItem="enumerationsDataGrid_AddingNewItem"
AutoGenerateColumns="false"
ItemsSource="{Binding Enumerations}"
SizeChanged="enumerationsDataGrid_SizeChanged">
<DataGrid.RowStyle>
<Style BasedOn="{StaticResource MetroDataGridRow}"
TargetType="DataGridRow">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="{Binding PlacementTarget.Tag.RemoveEnumerationValueCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding}"
Header="_Delete enumeration" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"
Header="Enumerations" />
</DataGrid.Columns>
</DataGrid>
<TextBlock Margin="5 0 5 5"
HorizontalAlignment="Center"
Foreground="Black"
Text="No enumerations found."
Visibility="{Binding Items.IsEmpty, Converter={StaticResource bool2VisibilityConverter}, ElementName=enumerationsDataGrid}" />
<Button Width="110"
Margin="5 0 0 5"
HorizontalAlignment="Left"
Command="{Binding AddEnumerationValueCommand}"
CommandParameter="{Binding}"
Content="New enumeration" />
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
<DataGrid.RowStyle>
<Style BasedOn="{StaticResource MetroDataGridRow}"
TargetType="DataGridRow">
<!--<Setter Property="DetailsVisibility" Value="{Binding ElementName=rowDetailsExpander}, Path=IsExpanded}" /> -->
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="{Binding Parent.RemoveStateTypeCommand}"
CommandParameter="{Binding}"
Header="_Delete state" />
<!-- Command="{Binding PlacementTarget.Tag.DeleteObjectiveStateCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" -->
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
</DataGrid>
Events are in same UserControl and breakpoint not hitting...
private void enumerationsDataGrid_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
}
private void enumerationsDataGrid_InitializingNewItem(object sender, InitializingNewItemEventArgs e)
{
}
EDIT
Im using DataGrid component which should have event AddingNewItem and InitializingNewItem
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.datagrid.addingnewitem?view=netframework-4.7.2
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.datagrid.initializingnewitem?view=netframework-4.7.2
When i initialize datagrid as in example above, bind it on Observable collection and add item to bound collection, none of this events are fired. And im curious why those events are not fired?
Binding seems okay, because item is correctly added into datagrid.
EDIT2
I know about notify events and observable collections, but watching this i can access only ViewModel class. But what i wana is, that when user add new row, that row will switch into Edit mode and user can write into its cells. Instead of double clicking on it.
So thats why im trying to use those two events. BEcause when i have reference on cell, i can call BeginEdit https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.datagrid.beginedit?view=netframework-4.7.2

WPF "static" binding in a List

I have a problem with a binding in a List.
I have a List of objects. This List is bound to a ListBox. Of every object in my List I can open a ContextMenu:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="First" IsEnabled="{Binding FirstEnabled}"/>
<MenuItem Header="Second" IsEnabled="{Binding SecondEnable}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
In the code like this my objects in the list having the two booleans to bind. Now I want to bind this two booleans not to the objects. I want to bind it "static" to my DataContext. This is not working like this and I have no idea how to realize it.
I googled a lot but found nothing helpful ...
Thanks for helping!
Since, ContextMenu applies to ListBoxItem it will have its DataContext value and ListBoxItem will be its PlacementTarget. So if you want to bind to property of ListBox.DataContext you need to pass current ListBox.DataContext as, for example, Tag to ListBoxItem and then you need to refer to it from ContextMenu via its PlacementTarget. It's all because ContextMenu uses Popup which creates its own visual tree so simple RelativeSource/ElementName binding won't work
<Style TargetType="ListBoxItem">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}, Path=DataContext}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="First" IsEnabled="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.Tag.FirstEnabled}"/>
<MenuItem Header="Second" IsEnabled="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.Tag.SecondEnable}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
You can reference every datacontext with the ElementName Syntax:
<ListBox x:Name="myListBox">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="First" IsEnabled="{Binding Path=DataContext.FirstEnabled, ElementName=myListBox}"/>
<MenuItem Header="Second" IsEnabled="{Binding Path=DataContext.SecondEnable, ElementName=myListBox}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
With this syntax you use the DataContext of your ListBox and not from the ListItem.

Binding Contextmenu

<ItemsControl ItemsSource="{Binding ViewModelOne.Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate >
<ContentControl>
<StackPanel Orientation="Vertical">
<StackPanel.ContextMenu>
<ContextMenu >
<MenuItem Header="Delete" Command="{Binding ViewModelOne.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock x:Name="Details" Text="{Binding Details}" />
<TextBlock x:Name="Name" Text="{Binding Name}" />
<Rectangle x:Name="Rects" Height="10" Width="10" Stroke="Black" StrokeThickness="1" />
</StackPanel>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have the above part of the code in my project and am trying to bind the Contextmenu command, what is the correct way.
I have also tried
<MenuItem Header="Delete" Command="{Binding PlacementTarget.Tag.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" />
Still i couldnot get the command working
Instead of putting ContextMenu on the your StackPanel, set ContextMenu on your Item in ItemContainerStyle and also set the Tag for the Item to the parents DataContext.
<ItemsControl x:Name="MyItemControl" ItemsSource="{Binding ViewModelOne.Items}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Tag" Value="{Binding DataContext, ElementName=MyItemControl}"></Setter>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu >
<MenuItem Header="Delete" Command="{Binding PlacementTarget.Tag.ViewModelOne.DeleteCommand, RelativeSource={RelativeSource Self}}" />
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>

How to get this binding for item instead of TreeViewItem?

I got the following code :
<HierarchicalDataTemplate x:Key="AssignedRate" ItemsSource="{Binding Children}" DataType="{x:Type local:UnitRateCatElement}">
<ContentControl>
<ContentControl.Template>
<ControlTemplate>
<StackPanel Tag="{Binding DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
<TextBlock Text="{Binding Category.Description}" />
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Unit Rate"
Command="{Binding Path=PlacementTarget.Tag.AddUnitRateCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</ControlTemplate>
</ContentControl.Template>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsDefined, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}}" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="Hello, it works!" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</HierarchicalDataTemplate>
The binding in the line : <DataTrigger Binding="{Binding Path=IsDefined, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}}" Value="True">
is incorrect (VS says so). How can I get this to work? The class local:UnitRateCatElement does have a IsDefined property. But I cannot get the binding right to point to that object. How can I get this binding right?
Try
Binding="{Binding Path=DataContext.IsDefined, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}}"
I think you should be doing this -
<DataTrigger Binding="{Binding Path=DataContext.IsDefined,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type TreeViewItem}}}" Value="True">
as your RelativeSource binding points to the TreeViewItem, which doesn't have IsDefined property, it is present in the DataContext of the TreeViewItem.
Update:
For your second problem (trigger not working), this is happening because you are setting the Template explicitly i.e. Local Value which is having higher precedence then Triggers; this should work -
<ControlTemplate x:Key="DefaultTemplate">
<StackPanel Tag="{Binding DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
<TextBlock Text="{Binding Category.Description}" />
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Unit Rate"
Command="{Binding Path=PlacementTarget.Tag.AddUnitRateCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</ControlTemplate>
<ControlTemplate x:Key="DefinedTemplate">
<TextBlock Text="Hello, it works!" />
</ControlTemplate>
<HierarchicalDataTemplate x:Key="AssignedRate" ItemsSource="{Binding Children}"
DataType="{x:Type local:UnitRateCatElement}">
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Template" Value={StaticResource DefaultTemplate}>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.IsDefined, RelativeSource=
{RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}}"
Value="True">
<Setter Property="Template" Value={StaticResource DefinedTemplate}>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</HierarchicalDataTemplate>

Binding a command to viewModel inside a Context menu sitting inside a ListView

I am trying to bind a context menu item to a command that I have defined in my ViewModel. The context menu sits inside a ListView that I also have bound to a CollectionViewSource, and this I think is the cause of the problem.
I have managed to bind the selected item in the listView collection to my ViewModel, but when I am trying to use the same way to bind the context menu item command to my ViewModel it doesn't work. I hope that anybody have the time to read through all the code below and give me some idea about what I am doing wrong.
Ps. I have had to change some of the names in order to not give away what the application is about.
In my ViewModel I have defined the followning:
public ObservableCollection<ListItemViewModel> ListViewItemViewModels {get; set;}
public MyListItem SelectedListItemViewModel {get; set;}
private RelayCommand _runCommand;
public ICommand RunCommand {
get {
return _runCommand ??
( _runCommand = new RelayCommand( param => RunReport(), param => CanRunReport ) );
}
}
private void RunReport() {
Logger.Debug("Run report");
}
Then in my View I have set upp a ListView as follows:
<ListView DataContext="{StaticResource ListGroups}"
ItemsSource="{Binding}"
ItemContainerStyle="{StaticResource ListItemStyle}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid},AncestorLevel=1 }, Path=DataContext.SelectedListItem, UpdateSourceTrigger=PropertyChanged}"
Margin="10,10,0,10">
<ListView.GroupStyle>
<StaticResourceExtension ResourceKey="AccountGroupStyle"/>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Path=DisplayTitle}"/>
<GridViewColumn Header="Date" DisplayMemberBinding="{Binding Path=DateString}"/>
</GridView>
</ListView.View>
<ListView.ContextMenu>
<ContextMenu Name="ListViewContextMenu">
<MenuItem Header="Run" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid},AncestorLevel=1 }, Path=DataContext.RunCommand}"/>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
The CollectionViewSource is defined as follows:
<DataTemplate x:Key="ListViewListTemplate" DataType="{x:Type ViewModels:ListItemViewModel}">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=DisplayTitle}" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
<CollectionViewSource Source="{Binding Path=ListItemViewModels}" x:Key="ListItemGroups">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ListItemGroupName"/>
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<ComponentModel:SortDescription PropertyName="Index" Direction="Ascending"/>
<ComponentModel:SortDescription PropertyName="DisplayTitle" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<GroupStyle x:Key="ListItemGroupStyle">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<!-- The text binding here is refered to the property name set above in the propertyGroupDescrition -->
<TextBlock x:Name="text" Background="{StaticResource DateGroup_Background}" FontWeight="Bold" Text="{Binding Path=Name}"
Foreground="White"
Margin="1"
Padding="4,2,0,2"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
<Style x:Key="ListItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<!--
Bind the IsSelected property of a ListViewItem to the
IsSelected property of a ReconciliationTaskViewModel object.
-->
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="ItemsControl.AlternationIndex" Value="1" />
<Condition Property="IsSelected" Value="False" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="#EEEEEEEE" />
</MultiTrigger>
</Style.Triggers>
</Style>
The cause of the problem is that the ContextMenu isn't part of either the logical or visual tree of the ListView, so RelativeSource/FindAncestor doesn't work, and the DataContext is not inherited.
I posted a solution to this problem a few months ago, you can use it as follows:
<ListView ...>
<ListView.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</ListView.Resources>
...
<ListView.ContextMenu>
<ContextMenu Name="ListViewContextMenu">
<MenuItem Header="Run" Command="{Binding Source={StaticResource proxy}, Path=Data.RunCommand}"/>
</ContextMenu>
</ListView.ContextMenu>
...
</ListView>

Categories