I have a DataBound treeview, from which the treeviewitems are created/displayed and feature a context menu. This is working correctly. I am then trying to use a custom menuitem class so that the datacontext of the treeviewitem can be retrieved through the commands fired from the contextmenu. Sorry if this is confusing. I may be going the wrong way about doing this.
Here is my xaml.
<UserControl x:Class="Pipeline_General.Custom_Controls.ProjectTree"
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:pm="clr-namespace:Pipeline_General"
mc:Ignorable="d"
DataContext = "{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TreeView Name="StructureTree" Background="{x:Static pm:myBrushes.defaultBG}" ItemsSource="{Binding ProjectList}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="Margin" Value="5,5,5,5" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Background="{x:Static pm:myBrushes.defaultBG}" Foreground="{x:Static pm:myBrushes.gray}">
<pm:ProjectTreeMenuItem Header="Add Episode.."
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.AddEpCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
<pm:ProjectTreeMenuItem Header="Add Sequence.."
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.AddSeqCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
<pm:ProjectTreeMenuItem Header="Add Scene.."
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.AddScCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type pm:ProjectRoot}" ItemsSource="{Binding Episodes}">
<TextBlock Text="{Binding Path=Title}" Foreground="{x:Static pm:myBrushes.pink}"
FontFamily="Calibri" FontSize="18"/>
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="Margin" Value="5,5,5,5" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Background="{x:Static pm:myBrushes.defaultBG}" Foreground="{x:Static pm:myBrushes.gray}">
<pm:ProjectTreeMenuItem Header="Add Sequence.."
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.AddSeqCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
<pm:ProjectTreeMenuItem Header="Add Scene.."
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.AddScCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
<Separator Height="15" Margin="20,0"/>
<pm:ProjectTreeMenuItem Header="Cut"
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.CutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
<pm:ProjectTreeMenuItem Header="Un-Cut"
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.UnCutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type pm:Episode}" ItemsSource="{Binding Sequences}">
<TextBlock Text="{Binding Path=Key}" Foreground="{x:Static pm:myBrushes.yellow}"
FontFamily="Calibri" FontSize="14"/>
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="Margin" Value="5,5,5,5" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Background="{x:Static pm:myBrushes.defaultBG}" Foreground="{x:Static pm:myBrushes.gray}">
<pm:ProjectTreeMenuItem Header="Add Scene.."
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.AddScCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
<Separator Height="15" Margin="20,0"/>
<pm:ProjectTreeMenuItem Header="Cut"
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.CutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
<pm:ProjectTreeMenuItem Header="Un-Cut"
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.UnCutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type pm:Sequence}" ItemsSource="{Binding Scenes}">
<TextBlock Text="{Binding Path=Key}" Foreground="{x:Static pm:myBrushes.yellow}"
FontFamily="Calibri" FontSize="14"/>
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Background="{x:Static pm:myBrushes.defaultBG}" Foreground="{x:Static pm:myBrushes.gray}">
<pm:ProjectTreeMenuItem Header="Cut"
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.CutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
<pm:ProjectTreeMenuItem Header="Un-Cut"
Element="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
Command="{x:Static pm:MyCommands.UnCutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="20,0"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
and my custom menu item class and commands.
public static class MyCommands
{
static MyCommands()
{
AddEpCommand = new SimpleDelegateCommand(p =>
{
var menuItem = p as ProjectTreeMenuItem;
var Element = menuItem.Element as ProjectElement;
Console.WriteLine(Element.Key);
Console.WriteLine("EP");
});
AddSeqCommand = new SimpleDelegateCommand(p =>
{
var menuItem = p as ProjectTreeMenuItem;
var Element = menuItem.Element as ProjectElement;
Console.WriteLine(Element.Key);
Console.WriteLine("SEQ");
});
AddScCommand = new SimpleDelegateCommand(p =>
{
var menuItem = p as ProjectTreeMenuItem;
var Element = menuItem.Element as ProjectElement;
Console.WriteLine(Element.Key);
Console.WriteLine("SC");
});
CutCommand = new SimpleDelegateCommand(p =>
{
var menuItem = p as ProjectTreeMenuItem;
var Element = menuItem.Element as ProjectElement;
Console.WriteLine(Element.Key);
Console.WriteLine("SC");
});
UnCutCommand = new SimpleDelegateCommand(p =>
{
var menuItem = p as ProjectTreeMenuItem;
var Element = menuItem.Element as ProjectElement;
Console.WriteLine(Element.Key);
Console.WriteLine("SC");
});
}
public static ICommand AddEpCommand { get; set; }
public static ICommand AddSeqCommand { get; set; }
public static ICommand AddScCommand { get; set; }
public static ICommand CutCommand { get; set; }
public static ICommand UnCutCommand { get; set; }
}
public class SimpleDelegateCommand : ICommand
{
public SimpleDelegateCommand(Action<object> executeAction)
{
_executeAction = executeAction;
}
private Action<object> _executeAction;
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_executeAction(parameter);
}
}
public class ProjectTreeMenuItem : MenuItem
{
#region Element (DependencyProperty)
public ProjectElement Element
{
get { return (ProjectElement)GetValue(ElementProperty); }
set { SetValue(ElementProperty, value); }
}
public static readonly DependencyProperty ElementProperty =
DependencyProperty.Register("Element", typeof(ProjectElement), typeof(ProjectTreeMenuItem),
new PropertyMetadata { PropertyChangedCallback = ElementChanged });
private static void ElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ProjectTreeMenuItem so = d as ProjectTreeMenuItem;
if (so != null && e.NewValue != null)
{
so.Element = (ProjectElement)e.NewValue;
Console.WriteLine("HERE " + so.Element.Title);
}
}
#endregion
public ProjectTreeMenuItem()
:base()
{
}
}
The projectElement class ( which im using in hundreds of other places in the project ) has a String Key, String Title attribute which is working fine elsewhere. Im pretty sure my issues lies in setting the Element Property through the xaml via the datacontext.
Figured it out eventually.. I used the command property of the menuitem and used it to send back the datacontext directly instead of using a dependency property on a custom menu item class.
The larger issue was that i was trying to use find the ancestor from the visualtree, which in a contextmenu doesnt work, and i think also because i was within a hierachialdatatemplate it wouldnt work anyway.
the following does the trick though.
<ContextMenu Background="{x:Static pm:myBrushes.defaultBG}" Foreground="{x:Static pm:myBrushes.gray}"
DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Add Episode.."
Command="{x:Static pm:MyCommands.AddEpCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.DataContext}"
Margin="20,0"/>
<MenuItem Header="Add Sequence.."
Command="{x:Static pm:MyCommands.AddSeqCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.DataContext}"
Margin="20,0"/>
<MenuItem Header="Add Scene.."
Command="{x:Static pm:MyCommands.AddScCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.DataContext}"
Margin="20,0"/>
</ContextMenu>
Related
I have the following datagrid that contains two comboboxes each contained in a data template.
<DataGrid x:Name="FilterSelection" HorizontalAlignment="Stretch">
<DataGrid.Resources>
<DataTemplate x:Key="EntityComboboxTemp">
<ComboBox x:Name="EntityCombobox"
ItemsSource="{Binding DataContext.FilterVehicleEntities, RelativeSource={RelativeSource AncestorType=local:ExportView}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding DataContext.SelectedEntityCommand, RelativeSource={RelativeSource AncestorType=local:ExportView}}"
CommandParameter="{Binding ElementName=EntityCombobox, Path=SelectedItem}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="AttributeComboboxTemp">
<ComboBox x:Name="AttributeCombobox"
ItemsSource="{Binding DataContext.FilterAttributes, RelativeSource={RelativeSource AncestorType=local:ExportView}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding DataContext.SelectedAttributeCommand, RelativeSource={RelativeSource AncestorType=local:ExportView}}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{conv:FilterMultipleConverter}">
<Binding ElementName="EntityCombobox" Path="SelectedItem"/>
<Binding ElementName="AttributeCombobox" Path="SelectedItem"/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Entity" CellTemplate="{StaticResource EntityComboboxTemp}" />
<DataGridTemplateColumn Header="Entity Attribute" CellTemplate="{StaticResource AttributeComboboxTemp}"/>
</DataGrid.Columns>
</DataGrid>
The problem lies in the multiple binding of the second combobox, namely the line:
Binding ElementName="EntityCombobox" Path="SelectedItem"/>
which should bind the selecteditem of the first combobox to the second combobox as commandparameter. But I always get the Data binding error that EntityCombobox is unknown. How can I set the DataContext for this binding, is this even possible?
My suggestion is to not do so much in your view. I would recommend to do this logic in the ViewModel for the DataGrid.
To start then I would create a view model for holding your filter selections.
Example: (please see comments for where to put logic for selection changes)
public class FilterViewModel
{
private string _vehicleEntity;
public string VehicleEntity
{
get { return _vehicleEntity; }
set
{
_vehicleEntity = value;
//OnPropertyChanged() if you want
}
}
private string _attribute;
public string Attribute
{
get { return _attribute; }
set
{
_attribute = value;
//Add logic here to determine what to do with both Attribute and VehicleEntity
//OnPropertyChanged() if you want
}
}
}
Then setup your overall View's ViewModel to hold a collection of the FilterModel along with the list of Vehicle options and Attribute options.
Example:
public class MainWindowViewModel
{
public ObservableCollection<FilterViewModel> Rows { get; }
public ObservableCollection<string> FilterVehicleEntities { get; }
public ObservableCollection<string> FilterAttributes { get; set; }
public MainWindowViewModel()
{
Rows = new ObservableCollection<FilterViewModel>();
FilterVehicleEntities = new ObservableCollection<string>()
{
"Vehicle 1",
"Vehicle 2",
"Vehicle 3",
};
FilterAttributes = new ObservableCollection<string>()
{
"Attribute 1",
"Attribute 2",
"Attribute 3",
};
}
}
Then make your view less complicated by just directly binding the selection to the properties. You will then get notified of selection changes as soon as the property updates (as marked in the comments for the FilterViewModel).
Example (full xaml):
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow"
Width="600"
Height="500">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding Rows}">
<DataGrid.Columns>
<DataGridComboBoxColumn
Header="Entity"
SelectedItemBinding="{Binding VehicleEntity, UpdateSourceTrigger=PropertyChanged}">
<!-- property changed so we get the change right after we select-->
<DataGridComboBoxColumn.ElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterVehicleEntities}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterVehicleEntities}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridComboBoxColumn
Header="Entity Attribute"
SelectedItemBinding="{Binding Attribute, UpdateSourceTrigger=PropertyChanged}">
<!-- property changed so we get the change right after we select-->
<DataGridComboBoxColumn.ElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterAttributes}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterAttributes}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
A ComboBox in a DataTemplate cannot refer to a ComboBox that is defined somewhere else using an ElementName binding because they are not in the same namescope.
What you should do is to bind the SelectedItem property of the EntityCombobox to a source property, and then bind the CommandParameter property of the other ComboBox to the same source property. For this to work, the class where the source property is defined must implement the INotifyPropertyChanged interface and raise change notifications and both ComboBoxes must also share the same DataContext.
I would like to create dynamically Hierarchical ContextMenu from data in ViewModel.
In ViewMode, I defined ContextMenuAction:
public class ContextMenuAction : ViewModelBase
{
public string Header { get; set; }
public ICommand Action { get; set; }
public Brush Icon { get; set; }
public ObservableCollection<ContextMenuAction> SubActions { get; set; } = new ObservableCollection<ContextMenuAction>();
}
In View:
<ContextMenu ItemsSource="{Binding Path=PlacementTarget.Tag.Actions, RelativeSource={RelativeSource Self}}">
<ContextMenu.ItemTemplate >
<DataTemplate DataType="MenuItem">
<MenuItem/>
</DataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="ItemsSource" Value="{Binding SubActions}"/>
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Command" Value="{Binding Action}"/>
</Style>
</ContextMenu.ItemContainerStyle>
This is result, there no text in context menu.
I already check output window to check binding, all bindings work, there is no exception.
Please help me to find out the reason, thank in advance!
You should define a HierarchicalDataTemplate:
<ContextMenu ItemsSource="{Binding Path=PlacementTarget.Tag.Actions, RelativeSource={RelativeSource Self}}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ContextMenuAction}" ItemsSource="{Binding SubActions}">
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Action}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
I have tried few solutions given in SO, but still i'm unable to trigger the command.
XAML:
<Image Source="{Binding CurrentImage.Source, Mode=OneWay}" Grid.Row="0" Grid.Column="1" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType= Window}}">
<Image.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit Image" Command="{Binding PlacementTarget.Tag.EditImageCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"></MenuItem>
</ContextMenu>
</Image.ContextMenu>
ViewModel:
private ICommand _EditImageCommand;
public ICommand EditImageCommand
{
get
{
return _EditImageCommand ?? (_EditImageCommand = new CommandHandler(() => EditImage(), _canExecute));
}
}
public void EditImage()
{
}
Change:
private ICommand _EditImageCommand;
private ICommand EditImageCommand
{
get
{
return _EditImageCommand ?? (_EditImageCommand = new CommandHandler(() => EditImage(), _canExecute));
}
}
public void EditImage()
{
}
to
private ICommand _EditImageCommand;
public ICommand EditImageCommand // has to be public
{
get
{
return _EditImageCommand ?? (_EditImageCommand = new CommandHandler(() => EditImage(), _canExecute));
}
}
public void EditImage()
{
}
Commands have to be public to be accessed (or internal for the sake of correctness).
Also, change your xaml to:
<Image Source="{Binding CurrentImage.Source, Mode=OneWay}" Grid.Row="0" Grid.Column="1" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType= Window}}">
<Image.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit Image" Command="{Binding EditImageCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"></MenuItem>
</ContextMenu>
</Image.ContextMenu>
Have changed my XAML to,
<Window.Resources>
<local:ImageList x:Key="SliderViewModel"></local:ImageList>
</Window.Resources>
<Image Source="{Binding CurrentImage.Source, Mode=OneWay}" Grid.Row="0" Grid.Column="1">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit Image" Command="{Binding EditImageCommand, Source={StaticResource SliderViewModel}}"></MenuItem>
</ContextMenu>
</Image.ContextMenu>
</Image>
Working fine. Thanks
Another nice workaround is to declare a static instance of your view model in App.xaml: <ViewModelTypeName x:Key="ViewModelName" d:IsDataSource="True" />and bind like this Command="{Binding Source={StaticResource ViewModelName}, Path=MyCommand}" I had the same issue when i needed to bind from Window and menuitem's native DataContext simultaneously. Also this solution looks not that complicated.
I've created ControlTemplates:
<Window.Resources>
<ControlTemplate x:Key="imgNo" TargetType="{x:Type Control}">
<Image Source="pack://application:,,,/Images/up.png"/>
</ControlTemplate>
<ControlTemplate x:Key="imgUp" TargetType="{x:Type Control}">
<!--<TextBlock Text="Up"/>-->
<Image Source="pack://application:,,,/Images/up.png"/>
</ControlTemplate>
<ControlTemplate x:Key="imgDown" TargetType="{x:Type Control}">
<Image Source="pack://application:,,,/Images/downArrow.png"/>
</ControlTemplate>
<DataTemplate x:Key="ButtonOneDataTemplate">
<Control x:Name="theControl" Template="{DynamicResource imgNo}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsImageChanged}" Value="true">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgUp}" />
</DataTrigger>
<DataTrigger Binding="{Binding IsImageChanged}" Value="false">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgDown}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</WindowResources>
and Button in DataGrid which uses above ControlTemplates:
<DataGrid ItemsSource="{Binding Persons}" Grid.Row="1" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding IdPerson}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<Border Background="Violet">
<StackPanel>
<Button ContentTemplate="{StaticResource ButtonOneDataTemplate}"
Command="{Binding DataContext.HelloCommand, RelativeSource=
{RelativeSource AncestorType=Window}}"
CommandParameter="{Binding DataContext.Hello,
RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</StackPanel>
</Border>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
My ViewModel:
public class MainWindowViewModel:ViewModelBase
{
public RelayCommand HelloCommand { get; set; }
public MainWindowViewModel()
{
LoadPersons();
HelloCommand = new RelayCommand(SayHello);
}
int helloCounter = 0;
private void SayHello(object obj)
{
if (helloCounter % 2 == 0)
IsImageChanged = true;
else
IsImageChanged = false;
helloCounter++;
}
private bool isImageChanged=true;
public bool IsImageChanged
{
get { return isImageChanged; }
set { isImageChanged = value;
OnPropertyChanged("IsImageChanged");
}
}
}
What I want is when I click on the button <Button ContentTemplate="{StaticResource ButtonOneDataTemplate}"/>, then Template should be replaced to {DynamicResource imgDown} or {DynamicResource imgUp}. DataTrigger depends on IsImageChanged value.
However, if I click on the Button, then DataTrigger is not fired(Controltemplates such as imgUp, imgDown are not changed). How can I achieve this from my ViewModel?
Problem is that DataGrid column is not a part of a visual tree, and because of that it does not inherit DataContext. To be able to use DataTriggers in your ButtonOneDataTemplate you need that button, you applying this template to, has correct DataContext. There is a trick, how to provide DataContext to elements that are not in VisualTree, described here
Applying that solution to your code we'll have the following:
Proxy
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Window.Resources
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
<DataTemplate x:Key="ButtonOneDataTemplate">
<Control x:Name="theControl" Template="{DynamicResource imgNo}" Foreground="Orange"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.IsImageChanged}" Value="True">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgUp}" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.IsImageChanged}" Value="False">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgDown}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
HeaderTemplate
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<Border Background="Violet">
<StackPanel>
<Button ContentTemplate="{StaticResource ButtonOneDataTemplate}"
DataContext="{Binding Path=Data, Source={StaticResource proxy}}"
Command="{Binding DataContext.ButtonClick, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</StackPanel>
</Border>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
I need to change the 1st cell of 1st row to a blank text box.
right now I have the column as datagridviewcombobox column?
<DataGridComboBoxColumn Header="And/Or" Width="60" ItemsSource="{Binding Source={StaticResource PredicateCombinationOperatorsEnumValues}}" SelectedItemBinding="{Binding PredicateCombinationOperator, Mode=TwoWay}" />
<DataGridComboBoxColumn Header="Field" ItemsSource="{Binding Source={StaticResource FieldTypeEnumValues}}" SelectedItemBinding="{Binding FieldType}"/>
<DataGridComboBoxColumn Header="Operator" MinWidth="70" ItemsSource="{Binding Source={StaticResource OperatorsEnumValues}}" SelectedItemBinding="{Binding Operator}"/>
<DataGridTextColumn Header="Value" MinWidth="100" Width="*" Binding="{Binding Expression}"/>
</DataGrid.Columns>
<DataGrid.InputBindings>
<KeyBinding Command="{Binding Source={StaticResource DeleteContextMenuCommand}}" Key="Delete"/>
</DataGrid.InputBindings>
<DataGrid.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuOptions}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Command" Value="{Binding}" />
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}" />
<Setter Property="AutomationProperties.AutomationId" Value="{Binding Name}" />
<Setter Property="AutomationProperties.Name" Value="{Binding Name}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</DataGrid.ContextMenu>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContextMenuOpening">
<trigger:ContextMenuOpeningTriggerAction/>
</i:EventTrigger>
<i:EventTrigger EventName="SelectionChanged">
<trigger:SelectionChangeTriggerAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
You could specify conditional data template and select the template according to your condition. First we have to inherit class from DataTemplateSelector and define properties of DataTemplate type. Define as much properties as data template you want.Then override the SelectTemplate method to return the datatemplate you want.Check the below sample code
<Window.Resources>
<local:Animals x:Key="animals"/>
<DataTemplate x:Key="TextTemplate">
<TextBox Margin="2" Width="60" />
</DataTemplate>
<DataTemplate x:Key="ComboTemplate" >
<ComboBox Width="60" />
</DataTemplate>
</Window.Resources>
<Grid>
<Controls:DataGrid>
<Controls:DataGrid.Columns>
<Controls:DataGridTemplateColumn Header="And/Or" Width="60">
<Controls:DataGridTemplateColumn.CellTemplateSelector>
<local:CustomTemplateSelector
TextTemplate="{StaticResource TextTemplate}"
ComboTemplate="{StaticResource ComboTemplate}"/>
</Controls:DataGridTemplateColumn.CellTemplateSelector>
</Controls:DataGridTemplateColumn>
<Controls:DataGridComboBoxColumn Header="Field"/>
<Controls:DataGridComboBoxColumn Header="Operator" MinWidth="70" />
<Controls:DataGridTextColumn Header="Value" MinWidth="100" Width="*"/>
</Controls:DataGrid.Columns>
</Controls:DataGrid>
</Grid>
public class CustomTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate
{ get; set; }
public DataTemplate ComboTemplate
{ get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
MyObject obj = item as MyObject;
if (obj != null)
{
// custom logic to select appropriate data template and return
}
else
return base.SelectTemplate(item, container);
}
}
}
for more check here
http://zamjad.wordpress.com/2010/08/23/apply-conditional-data-template-in-data-grid/