I guess it is a pretty specific question but I am trying to bind get as CommandParameter the Content of a GridViewColumnHeader. As you will see in the code, it works when I do it in the second setter of the style: <Setter Property="CommandParameter" Value="{Binding Content, RelativeSource={RelativeSource Self}}"/>. But it does not work for my Menu Items, how sould I bind them? Here is the code:
<Style BasedOn="{StaticResource {x:Type GridViewColumnHeader}}" TargetType="GridViewColumnHeader">
<Setter Property="Command" Value="{Binding SortBy}" />
<Setter Property="CommandParameter" Value="{Binding Content, RelativeSource={RelativeSource Self}}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Tag="{Binding Content, RelativeSource={RelativeSource AncestorType=GridViewColumnHeader}}">
<MenuItem CommandParameter="{Binding Tag, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Header="{UI:Language #{SortAscending}}"
Command="{Binding SortAscending}" />
Bind the Tag property to the PlacementTarget of the ContextMenu itself:
<ContextMenu Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Self}}">
<MenuItem CommandParameter="{Binding Tag, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Here is what worked for me, a coworker gave me the solution, I was missing a 'DataContext' as My 'GridView' is actualy in a 'ListView':
<ContextMenu Tag="{Binding PlacementTarget.CommandParameter, RelativeSource={RelativeSource Self}}"
DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
All I did is add a DataContext to the ContextMenu. hope this helps anyone that might have similar problems.
Related
I have a TreeView with an hierarchical Data Template (2 levels). I have a context menu created on the first level of the tree view with 3 levels. I want to bind a command of my view model to the second level of the context menu. Unfortunately I can only get it to work when using a command in my Model, which is not what I want to do... If possible, I would like to do it all in XAML.
I have tested the pure xaml solutions given here and here.
In the designer, the "Tag" is underlined blue saying "Cannot resolve Property Tag in data context of type System.Windows.UIElement" or, if I use "Parent.PlacementTarget....", the designer tells me that PlacementTarget cannot be resolved in the data context of type System.Windows.DependencyObject.
The code is compileable, but my command is never reached.
This is what I have:
In a ResourceDictionary:
<DataTemplate DataType="{x:Type viewModel:UpdateToolViewModel}">
<view:UpdateToolView/>
</DataTemplate>
<DataTemplate x:Key="ToolNameDataTemplate" DataType="{x:Type src:Element}">
<Grid>
<TextBlock Text="{Binding Path=NameProperty.Value}" FontSize="12" />
</Grid>
</DataTemplate>
<HierarchicalDataTemplate x:Key="ToolGroupsDataTemplate" ItemsSource="{Binding Elements}" DataType="{x:Type src:ElementGroup}" ItemTemplate="{StaticResource ToolNameDataTemplate}">
<TextBlock Text="{Binding Path=TextProperty.Value}" FontSize="14" FontWeight="Bold" Tag="{Binding ElementName=UpdateToolControl}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Tool" ItemContainerStyle="{StaticResource ToolGroupContextMenuToolsItemStyle}" >
<MenuItem.ItemsSource>
<CompositeCollection>
<MenuItem Header="Add New ..." Command="{Binding PlacementTarget.Tag.DataContext.AddToolCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}}" />
<CollectionContainer Collection="{Binding Source={StaticResource AddToolContextMenuSource}}"/>
</CompositeCollection>
</MenuItem.ItemsSource>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
In the UserControl:
<UserControl x:Class="...UpdateToolView" ... Name="UpdateToolControl">
<TreeView Name="ToolTreeView" ItemsSource="{Binding AllElementsInGroups}"
ItemTemplate="{StaticResource ToolGroupsDataTemplate}"
ItemContainerStyle="{StaticResource ToolTreeViewItemStyle}"/>
</UserControl>
I am already on the verge of using the command in my model, calling a method in my view model. Not nice, but I just don't seem to get it to work differently.
I was just going on, trying to figure out how to add a command to the CollectionContainer Items, when I found this: CollectionContainer Binding (sorry, its german, but the xaml code is the relevant thing here)
Now I have added the command in the ItemContainerStyle of the MenuItem and suddenly the whole thing works (although "Tag" is still underlined blue in the designer):
<converter:ElementToToolTipConverter x:Key="ElementToToolTipConverter"/>
<Style x:Key="ToolGroupContextMenuToolsItemStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="ItemsSource" Value="{Binding Children}"/>
<Setter Property="ToolTip" Value="{Binding Element, Converter={StaticResource ElementToToolTipConverter}}"/>
<Setter Property="Command" Value="{Binding PlacementTarget.Tag.DataContext.AddToolCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
Sometimes, it already helps thinking about something else... :)
On each DataGridColumnHeader I have a button that I use to open a popup. As a parameter it sends the column's bound Property name to the ICommand in my ViewModel.
This works well for any DataGridTextColumn however when it comes to a DataGridComboBoxColumn the structure is different.
How would I solve this?
<Button Command="{Binding DataContext.OpenFilterCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding Column.Binding.Path.Path,
RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
Problem Column Definition
<DataGridComboBoxColumn Header="Company" >
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.CompanyCollection}"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="SelectedValue" Value="{Binding Company}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.CompanyCollection}"/>
<Setter Property="SelectedValue" Value="{Binding Company}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
Like I mentioned in previous question here that how to get value for DataGridTextColumn where i suggested to use Column.Binding.Path.Path to get bound property name.
But that won't work in this case since DataGridComboBoxColumn does not have any binding property. If syntax is like the one you mentioned in question above, you can get like this:
For SelectedValue i.e. Company:
<Button Command="{Binding DataContext.OpenFilterCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding
Column.EditingElementStyle.Setters[1].Value.Path.Path,
RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
EXPLANATION
TemplatedParent (DataGridColumnHeader) --> Column (DataGridComboBoxColumn) --> EditingElementStyle(EditingElementStyle) --> Setters(1) (get first setter from style) --> Value (Setter Value) --> Path (PropertyPath) --> Path (Actual PropertyName)
If you want to get ItemsSource property name, replace Setters[1] with Setters[0].
I am new to wpf world. I have a context menu in the shell as below:
<ContextMenu>
<MenuItem Header="Login"
Command="{Binding WorkSpaceViewSetter}" CommandParameter="DemoApplication.View.LoginView">
<MenuItem.Icon>
<Image Height="16" Width="16" Stretch="Uniform" Source="/Images/login.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Modules" ItemsSource="{Binding AppModules}">
<MenuItem.Icon>
<Image Source="/Images/modules.png"/>
</MenuItem.Icon>
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding ModuleName}"/>
<Setter Property="Command" Value="{Binding ElementName=win, Path=DataContext.WorkSpaceViewFromType}"/>
<Setter Property="CommandParameter" Value="{Binding MainViewType}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
Each element in the itemssource AppModules of the Modules menuitem has a property named MainViewType of type System.Type. I want to change the view of a region when a menuitem gets clicked and am thinking of using a single ICommad in the shellviewmodel and passing the MainViewType as command parameter. However, the above code is not working.
I was wondering why then the Modules menuitem gets populated from the itemssource as expected.
I have noticed that the command binding on the Login menuitem is also not working even though it should have, since the itemssource property of Modules gets properly bounded. Can anybody please suggest how to make it work?
Context menus aren't on the same visual tree as the rest of your window, so using ElementName in the binding won't work. You'll need to using PlacementTarget instead. Without knowing how your viewmodels are structured it's difficult to give a definitive answer but your solution will look something like:
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding ModuleName}"/>
<Setter Property="Command" Value="{Binding PlacementTarget.DataContext.WorkSpaceViewFromType}"/>
<Setter Property="CommandParameter" Value="{Binding MainViewType}"/>
</Style>
</MenuItem.ItemContainerStyle>
MVVM is used. I created separate menu 'Recent files' which gets its items from binding. It looks like that:
<MenuItem Header="_Recent files" ItemsSource="{Binding RecentFiles, Converter={StaticResource RecentFilesToListOfStringsConverter}, Mode=OneWay}" >
</MenuItem>
Now, I would like to add Command to each of the those auto-generated items, which should get the path as command parameter and execute import file action by click.
Could you please suggest how can it be done in MVVM way?
Again, found the solution by myself. I tried to put the command in wrong way like below, and it doesn't work:
<MenuItem Header="_Recent files" ItemsSource="{Binding RecentFiles, Converter={StaticResource RecentFilesToListOfStringsConverter}, Mode=OneWay}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding ImportRecentItemCommand}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
Here is the right approach. Still don't understand how it works, have to learn WPF deeply!
<MenuItem Header="_Recent files" ItemsSource="{Binding RecentFiles, Converter={StaticResource RecentFilesToListOfStringsConverter}, Mode=OneWay}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding DataContext.ImportRecentItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type MenuItem}, AncestorLevel=1}}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
EDIT: The final version
XAML:
<MenuItem Header="_Recent files" ItemsSource="{Binding RecentFiles, Converter={StaticResource RecentFilesToListOfStringsConverter}, Mode=OneWay}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding DataContext.ImportRecentItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type MenuItem}, AncestorLevel=1}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
ViewModel: MVVM Light Toolkit is used, RelayCommand goes from there:
private ICommand _importRecentItemCommand;
public ICommand ImportRecentItemCommand
{
get { return _importRecentItemCommand ?? (_importRecentItemCommand = new RelayCommand<object>(ImportRecentItemCommandExecuted)); }
}
private void ImportRecentItemCommandExecuted(object parameter)
{
MessageBox.Show(parameter.ToString());
}
Enjoy
I have the following xaml code defined for DataGridTextColumn of a DataGrid.
<DataGridTextColumn x:Name="UserIdColumn" Binding="{Binding Path=UserId}" HeaderStyle="{StaticResource DataGridHeaderStyle}"/>
And Here is the HeaderStyle for the DataGridTextColumn
<Style x:Key="DataGridHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridColumnHeader">
<StackPanel>
<TextBlock x:Name="ColumnName" Text="UserId" />
<TextBox x:Name="UserIdFilter" Width="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cal:ActionMessage MethodName="UserIdFilterChanged">
<cal:Parameter Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridTextColumn}}, Path=Name}"/>
<cal:Parameter Value="{Binding ElementName=UserIdFilter, Path=Text}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As one can see I am trying to access the DataGridTextColumn's Name property inside the DataGridColumnHeader's ControlTemplate using this line of code
<cal:Parameter Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridTextColumn}}, Path=Name}"/>
The above line is returning null, why ?
Is there any other way to get hold of DataGridTextColumn's Name property from DataGridColumnHeader's control template ?
You are recovering a null value for 2 different reasons:
The first one is that the DataGridTextColumn is not part of the visual tree and is not a visual instance of the datagrid. This is just an instance that descibes how a column of the datagrid should be displayed. Thus, DataGridTextColumn is not part of the ancestors of you cell.
The only way I see to recover your DataGridTextColumn is to proceed somehow like that:
<TextBox x:Name="UserIdFilter" Width="100" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}, Path=Columns[0]}">
This way, you should set the Tag property of the Textbox with the instance of the DataGridTextColumn (you can check with snoop that it works properly)
Then, the second reason explaining why you have a null value is that the DataGridTextColumn has no Name property.
What you could do is simply create an attached property like this one:
public class XamlHelper
{
public static readonly DependencyProperty NameProperty = DependencyProperty.RegisterAttached("Name", typeof(string), typeof(XamlHelper));
public static string GetName(DependencyObject obj)
{
return (string)obj.GetValue(NameProperty);
}
public static void SetName(DependencyObject obj, string value)
{
obj.SetValue(NameProperty, value);
}
}
... replace the name of the column definition by this one:
<DataGridTextColumn alias:XamlHelper.Name="UserIdColumn" Binding="{Binding Path=UserId}" HeaderStyle="{StaticResource DataGridHeaderStyle}"/>
and retrieve the property this way:
<Style x:Key="DataGridHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridColumnHeader">
<StackPanel>
<TextBlock x:Name="ColumnName" Text="UserId" />
<TextBox x:Name="UserIdFilter" Width="100" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}, Path=Columns[0]}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cal:ActionMessage MethodName="UserIdFilterChanged">
<cal:Parameter Value="{Binding ElementName=UserIdFilter, Path=Tag.(alias:XamlHelper.Name)}"/>
<cal:Parameter Value="{Binding ElementName=UserIdFilter, Path=Text}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
... hope I didn't made too much mistakes in this code but I think something like that should work
Notice that I used the tag property of the textbox has a buffer because I am note sure the findAncestor mode of the binding is able to recover the datagrid properly (I had some issues on elements that are not is the visual tree). Maybe having a unique binding in you parameter also work...
Tag is also great to make tests because it allows to see the value in snoop!
Do not hesitate if some points are not clear enougth
edit:
I checked out the DataGridCOlumnHeaderClass and I discovered the property that seems to contain what you expected...
Thus, it should be possible to do that:
<Style x:Key="DataGridHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridColumnHeader">
<StackPanel>
<TextBlock x:Name="ColumnName" Text="UserId" />
<TextBox x:Name="UserIdFilter" Width="100" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridColumnHeader}}, Path=Column}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cal:ActionMessage MethodName="UserIdFilterChanged">
<cal:Parameter Value="{Binding ElementName=UserIdFilter, Path=Tag.(alias:XamlHelper.Name)}"/>
<cal:Parameter Value="{Binding ElementName=UserIdFilter, Path=Text}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>