Binding a WPF Button CommandParameter to the Button itself in DataTemplate - c#

I have a DataTemplate that represents AppBar buttons that I declare through a collection of custom AppBarCommand objects.
public AppBarCommand(RelayCommand command, string buttonstyle)
{
Command = command;
ButtonStyle = buttonstyle;
}
<DataTemplate>
<Button Command="{Binding Command}"
Style="{Binding ButtonStyle, Converter={StaticResource StringNameToStyleConverter}}"/>
</DataTemplate>
I would like to add a CommandParameter binding, but the parameter has to be the Button itself. This is so I can set the PlacementTarget of a Callisto flyout. Is this possible?

<Button Command="{Binding Command}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}" />
Your Command property should be the generic version of the RelayCommand: RelayCommand<object> for instance.

Answer like Miklós Balogh said, or you can:
<Button x:Name="MyButton" Command="{Binding Command}" CommandParameter={Binding ElementName=MyButton ... />

I had the same problem but I used it in a bit different context:
<MenuItem ItemsSource="{Binding MyList}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding RelativeSource={ RelativeSource FindAncestor, AncestorType={ x:Type Window}}, Path= DataContext.MyListItemCommand}"/>
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
so I assume that even if you write it like this
<Button Command="{Binding Command}" CommandParameter="{Binding}" />
it should work.
I also recommend reading this post to understand it better.

Related

Command from Resources ContextMenu does not trigger

I need to use a context menu to restart services based on a datagrid (the context menu should send the name of the service as a CommandParameter)
The ContextMenu shows properly but clicking the MenuItems does not trigger the command (breakpoint not reached) but setting the context menu directly to a checkbox for example triggers the commands perfectly.
Ultimately, what I want is to get the context menu on right click on any item of the datagrid, clicking the menuitem should send either the Name column value or the whole selected item to the command in ViewModel (and then restart the process from there).
Really hurting my head for a few days, if you have any hints...
My Datagrid :
<DataGrid x:Name="dgServices" HorizontalAlignment="Left" VerticalAlignment="Top" DockPanel.Dock="Top" Height="auto" HeadersVisibility="Row"
AutoGenerateColumns="False" HorizontalGridLinesBrush="#FFC7E0EE" VerticalGridLinesBrush="#FFC7E0EE" SelectionMode="Single" BorderThickness="0"
ItemContainerStyle="{StaticResource DefaultRowStyle}" ItemsSource="{Binding Source={StaticResource cvsServ}}">
<DataGrid.GroupStyle>
//GROUPSTYLE
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Width="30"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="373">
</DataGridTextColumn>
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="356"/>
</DataGrid.Columns>
</DataGrid>
ContainerStyle and ContextMenu in Window.Resources :
<Window.Resources>
<ContextMenu x:Key="RowMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="NULL" Command="{Binding restartService}"/>
<MenuItem Header="NAME" CommandParameter="{Binding PlacementTarget.Name, RelativeSource={RelativeSource FindAncestor,AncestorType=ContextMenu}}" Command="{Binding restartService}"/>
<MenuItem Header="ITEM" CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource FindAncestor,AncestorType=ContextMenu}}" Command="{Binding restartService}"/>
<MenuItem Header="ITEMS" CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource FindAncestor,AncestorType=ContextMenu}}" Command="{Binding restartService}"/>
</ContextMenu>
<Style x:Key="DefaultRowStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
</Style>
<CollectionViewSource x:Key="cvsServ" Source="{Binding services, UpdateSourceTrigger=PropertyChanged}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Key" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
The Checkbox used for testing :
<CheckBox IsChecked="{Binding autoStart, Mode=TwoWay}">
<Label Content="AutoStart"/>
<CheckBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit" CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor,AncestorType=ContextMenu}}" Command="{Binding restartService}"/>
<!--<MenuItem Header="Edit" CommandParameter="{Binding ElementName=dgServices, Path=SelectedItem}" Command="{Binding restartService}"/>-->
</ContextMenu>
</CheckBox.ContextMenu>
</CheckBox>
ViewModel :
private DelegateCommand _restartService;
public DelegateCommand restartService
{
get
{
return _restartService ?? (_restartService = new DelegateCommand(o => TestCommand(o), o => true));
//return _restartService ?? (_restartService = new DelegateCommand(o => MessageBox.Show($"{o.ToString()}\n{o.GetType()}"), o => true));
//return _restartService ?? (_restartService = new DelegateCommand(o => Process.Start(new ProcessStartInfo("C:\\Windows\\system32\\cmd.exe", "/c net start "+o.ToString()) { Verb = "runas" }), o => true));
}
private void TestCommand(object o)
{
MessageBox.Show(o?.GetType()?.ToString() ?? "NULL");
}
If the command property is defined in the view model of the DataGrid, you could bind the Tag property of the row to the DataGrid:
<Style x:Key="DefaultRowStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}" />
</Style>
...and then try this:
<MenuItem Header="NAME"
Command="{Binding PlacementTarget.Tag.DataContext.restartService, RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}}"
CommandParameter="{Binding PlacementTarget.Tag.Name, RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}}"/>

WPF MenuItem not filling available ContextMenu space

I'm having a weird problem with a simple ContextMenu using MahApps.Metro without any additional styling. When moving the cursor on top of the text or slightly around it, there is no problem. But when moving it further away, still inside the ContextMenu bounds, the Cursor is no longer on top of the MenuItem. Clicking now also doesn't result in any action at all besides closing the ContextMenu.
<ContextMenu ItemsSource="{Binding ContextItems}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Text}" Command="{Binding Command}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
What am I doing wrong? Why doesn't the MenuItem use the available space?
If your ContextItems holds a collection with viewmodels then I think this could help you (not tested):
<ContextMenu ItemsSource="{Binding ContextItems}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Text}" />
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Command and Text should be the properties on the viewmodel object.
I havent used MahApps.Metro . Though you can override the template like this
<ContextMenu.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Text}" Command="{Binding Command}"/>
<MenuItem.Template>
<ControlTemplate>
<ContentPresenter Content="{Binding Header,RelativeSource={RelativeSource TemplatedParent}}">
</ContentPresenter>
</ControlTemplate>
</MenuItem.Template>
</MenuItem>
</DataTemplate>
</ContextMenu.ItemTemplate>
I hope this will help.

Adding commands for Contextmenu inside ListViewItem Style

I am searching for a solution for the ContextMenu's commands defined under ListViewItem style. Binding was successful when I defined and bind the commands inside ContextMenu within ListView.
I used viewModel class file to define the commands ex: ExecuteClone, CanExecuteClone which i am trying to bind it with ContextMenu menu items using Command Binding.
<ListView.ContextMenu>
<ContextMenu >
<MenuItem Header="New" Command="{Binding AddCommand}" />
</ContextMenu>
</ListView.ContextMenu>
But when i change the ContextMenu from ListView to ListViewItem, it doesn't hit the ViewModel command.
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="New" Command="{Binding AddCommand}"/>
<MenuItem Header="Clone" Command="{Binding CloneCommand}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
It needs to hit the ViewModel AddCommand, CloneCommand which are DelegateCommand actions defined under ViewModel class.
I found the answer with some trials, anyways thanks for the help.
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=DataContext}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="New" Command="{Binding AddCommand}"/>
<MenuItem Header="Clone" Command="{Binding CloneCommand}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
The ListViewItems have a different DataContext than the ListView. Every ListViewItem has its DataContext set to one of the items in your ItemsSource collection, so this is why the binding doesn't work. If you want to bind the commands to the ListView's DataContext, you can do it like that:
Command="{Binding DataContext.AddCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ListView}}"

Binding a DataGridCell ContextMenu MenuItem to a Command on a ViewModel

I know that there are similar questions on SO.
I have reviewed them, tried them, tried combinations of them, and retried them for the past day an half with very little success. I'm apparently dense or missing something.
The basic approaches covered were:
Placement Target
Using the Tag Attribute on a Parent
Using Ancestor Relative Source
Making use of ElementName root for the DataContext
Nothing seems to work and in only one instance am I even able to hit a break point in a Test Converter to see whats being bound. It will only work in the Tag Attribute of the DataCell. I am exasperated. This is as close as I have gotten:
<xcdg:DataGridControl.Resources>
<Style TargetType="{x:Type xcdg:DataCell}">
<Setter Property="Tag" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType={ x:Type v:RTCA}}, Converter={StaticResource TestConverter}}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding RelativeSource={RelativeSource AncestorType={ x:Type xcdg:DataCell}}, Path=Tag, Converter={StaticResource TestConverter}}" StaysOpen="True">
<MenuItem Header="Acknowledge" Command="{Binding Path=AcknowledgeViolationCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</xcdg:DataGridControl.Resources>
As a variation on this approach I thought that sense the Binding on the Tag Attribute worked and was correct that I could use in the DataContext for the ContextMenu or the even just the MenuItem itself.
<ContextMenu DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType={ x:Type v:RTCA}}, Converter={StaticResource TestConverter}}" StaysOpen="True">
or
<MenuItem Header="Acknowledge" DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType={ x:Type v:RTCA}}, Converter={StaticResource TestConverter}}" Command="{Binding Path=AcknowledgeViolationCommand}" />
With this next one I also tried PlacementTarget.DataContext and PlacementTarget.Tag with no results.
<Setter Property="Tag" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType={ x:Type v:RTCA}}, Converter={StaticResource TestConverter}}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding PlacementTarget, Converter={StaticResource TestConverter}}" StaysOpen="True">
<MenuItem Header="Acknowledge" Command="{Binding Path=AcknowledgeViolationCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
Other attempts:
<Setter Property="Tag" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType={ x:Type v:RTCA}}, Converter={StaticResource TestConverter}}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding PlacementTarget, Converter={StaticResource TestConverter}}" StaysOpen="True">
<MenuItem DataContext="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=DataContext}" Header="Acknowledge" Command="{Binding Path=AcknowledgeViolationCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
Here I tried making use of the ElementName root approach in different ways but it would only work in the Tag for the Cell and not the DataContext or Tag for the ContextMenu
<Setter Property="Tag" Value="{Binding DataContext, ElementName=root}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding PlacementTarget, Converter={StaticResource TestConverter}}" StaysOpen="True">
<MenuItem Header="Acknowledge" Command="{Binding Path=PacementTarget.Tag.AcknowledgeViolationCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}, Converter={StaticResource TestConverter}}" />
</ContextMenu>
</Setter.Value>
</Setter>
<!-- variation of above -->
<Setter Property="Tag" Value="{Binding DataContext, ElementName=root}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, Converter={StaticResource TestConverter}}" StaysOpen="True">
<MenuItem Header="Acknowledge" Command="{Binding Path=AcknowledgeViolationCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource TestConverter}}" StaysOpen="True">
<MenuItem Header="Acknowledge" Command="{Binding Path=AcknowledgeViolationCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
I can list countless failed attempts. PlacementTargets, Tags, Ancestors, RelativeSources, why can't I make this work?. And why can I only seem to step into my TestConverter when its used on the DataCell Tag Binding and no where else?
Heres the Convert Method:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
When it fires for Tag binding I can actually see the ViewModel being passed just like I want and I can even execute my Command from the immediate window. Frankly I'm at my wits end on what should have been a 15 minute thing.
Can anyone tell me what I'm doing or wrong?
Additional Example(s)
From Lee O.s Comment I tried the BindingProxy approach without any luck.
<xcdg:DataGridControl.Resources>
<vl:BindingProxy x:Key="proxy" Data="{Binding }" />
<Style TargetType="{x:Type xcdg:DataCell}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{StaticResource proxy}" StaysOpen="True">
<MenuItem Header="Acknowledge" Command="{Binding Path=AcknowledgeViolationCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
Additional tries. These next attempts did do something new. Now at least when the app is started the breakpoint for the AcknowledgeViolationCommand fires once. However selecting the Item from the Context Menu still does nothing.
<ContextMenu DataContext="{Binding Data, Source={StaticResource proxy}}" StaysOpen="True">
<MenuItem Header="Acknowledge" Command="{Binding Path=AcknowledgeViolationCommand}" />
</ContextMenu>
and
<ContextMenu StaysOpen="True">
<MenuItem Header="Acknowledge" Command="{Binding Path=Data.AcknowledgeViolationCommand, Source={StaticResource proxy}}" />
</ContextMenu>
also tried x:Reference with the following:
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu StaysOpen="True">
<MenuItem Header="Acknowledge" Command="{Binding Path=DataContext.AcknowledgeViolationCommand, Source={x:Reference dummyControl}, Converter={StaticResource TestConverter}}" />
</ContextMenu>
</Setter.Value>
</Setter>
These approaches all have one thing in common in that they all seem to cause a breakpoint I have set in the command to hit ONCE as the grid loads. Further more adding the TestConverter to the reference and setting a breakpoint causes it to hit once for each row in the grid as it loads, but never when the command is clicked.
Here is the code for the Command in the ViewModel
public ICommand AcknowledgeViolationCommand
{
get
{
if (acknowledgeViolationCommand == null) // BreakPoint is here
acknowledgeViolationCommand = new RelayCommand(param => Test());
return acknowledgeViolationCommand;
}
}
private void Test()
{
string a = "a"; //Breakpoint here
}
Made a change to the command in case for some reason it was wanting the canExecute predicate. I confirmed the breakpoint for the canExecute method does fire.
public ICommand AcknowledgeViolationCommand
{
get
{
if (acknowledgeViolationCommand == null) // BP here
acknowledgeViolationCommand = new RelayCommand(param => Test(), param => YesDoIt());
return acknowledgeViolationCommand;
}
}
private bool YesDoIt()
{
return true; //BP here
}

Tip / idea how can I get a generic context menu?

I want to develop a generic contextmenu in wpf with mvvm. I would like to display it in each viewModel that have a listview with the style: ItemContainerStyle="{DynamicResource ListViewItemContainerStyle}".
The context menu will have 2 options that will be enabled or not depending on some constraints in each view Model. The enabled or not will be managed by the canexecute of the command.
I have no problem if I put it in each view inside the listview, but I would like to have it only in one place other than in each listview. I have tried to put it in the ListViewItemContainerStyle but it shows an exception "it is not possible to add a System.Windows.Controls.ContextMenu to a System.Object". Here is my code snippet
<Style x:Key="ListViewItemContainerStyle"
TargetType="ListViewItem">
.
.
.
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="{Binding Path=Command}"
CommandParameter="AddNew">
<MenuItem.Header>
<TextBlock Text="{x:Static p:TextResources.New}" />
</MenuItem.Header>
</MenuItem>
<MenuItem Command="{Binding Path=Command}"
CommandParameter="Delete">
<MenuItem.Header>
<TextBlock Text="{x:Static p:TextResources.Delete}" />
</MenuItem.Header>
</MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
.
.
.
Any idea of how to do this?
Try this:
<ContextMenu x:Shared="False" x:Key="ListViewContextMenu>
<MenuItem Command="{Binding Path=Command}"
CommandParameter="AddNew">
<MenuItem.Header>
<TextBlock Text="{x:Static p:TextResources.New}" />
</MenuItem.Header>
</MenuItem>
<MenuItem Command="{Binding Path=Command}"
CommandParameter="Delete">
<MenuItem.Header>
<TextBlock Text="{x:Static p:TextResources.Delete}" />
</MenuItem.Header>
</MenuItem>
</ContextMenu>
<Style x:Key="ListViewItemContainerStyle"
TargetType="ListViewItem">
<Setter Property="ContextMenu" Value="{StaticResource ListViewContextMenu}"/>

Categories

Resources