How to implement commands on MenuItems - c#

In trying to bind a Command to a MenuItem in my program, I've found that Commands don't work with MenuItems like they do with other controls. I've been using this post as a guide, but have had no luck so far. Basically my goal is to run a Command when the MenuItem is clicked.
This is my xaml after looking at the previously mentioned post. My Command is called CreateFiles:
<MenuItem Header="{DynamicResource save}" Command="{Binding Path=PlacementTarget.DataContext.CreateFiles, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
My Command is created in the window's ViewModel and is declared like normal, but I will post it anyway:
private ICommand _createFiles;
public MainWindowViewModel()
{
_createFiles = new Command(createFiles_Operations);
}
public ICommand CreateFiles { get { return _createFiles; } }
private void createFiles_Operations()
{
}
To test whether or not my Command is working I set a break point right at the first bracer. So far the program has not stopped at this break point when the MenuItem is clicked.
Since this method does not seem to work, what can I do to make Commands work with MenuItems?
Update: Command changed to ICommand
Update 2: ContextMenu & Button xaml:
<Button Click="Button_Click_1" Margin="5,4,0,0" Name="Button_1" Height="55" VerticalAlignment="Top" HorizontalAlignment="Left" Width="55" BorderBrush="Black">...
<ContextMenu x:Name="MainContextMenu" MouseLeave="ContextMenuMouseLeave" Background="White" BorderBrush="#FF959595" SnapsToDevicePixels="False">...

You need to set Mode of RelativeSource to FindAncestor to get ContextMenu:
<MenuItem Header="{DynamicResource save}"
Command="{Binding Path=PlacementTarget.DataContext.CreateFiles,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ContextMenu}}" />
You have set PlacementRectangle property to refer to itself i.e.ContextMenu. Don't set that property, ContextMenuService internally set PlacementRectangle to the element on which it is applied (in your case it will be Button).
Remove this PlacementRectangle="{Binding RelativeSource={RelativeSource Self}}" from ContextMenu.
It should be:
<ContextMenu x:Name="MainContextMenu"
MouseLeave="ContextMenuMouseLeave" Background="White"
BorderBrush="#FF959595" SnapsToDevicePixels="False">

Related

Fire a command from a Button inside a ContentControl?

I'm new to WPF and I'm trying to dynamically add a Button inside a ContentControl, which should fire a command when clicked. I'm using MVVMLight to handle the Commands.
Below I have an example with two buttons. The top button is placed directly into the StackPanel. This button fires off the Command as expected.
The second button is placed inside a ContentControl. It displays correctly, but the Command does not fire when the button is clicked.
I assumed this is because the Binding does not transfer down through the DataTemplate, but it seems to work if I use regular Commands instead of MVVMLight RelayCommands.
I don't want to remove the framework, so I'm wondering if anyone knows how to fix it? Thanks
<Window x:Class="ContentControlExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ContentControlExample.ViewModel">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="MyButton" >
<Button Content="SUBMIT" Command="{Binding MyCommand}" Width="200" Height="50"/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<!--When this button is clicked, the Command executes as expected-->
<Button Content="SUBMIT" Command="{Binding MyCommand}" Width="200" Height="50"/>
<!--Nothing happens when this button is clicked-->
<ContentControl ContentTemplate="{StaticResource MyButton}"/>
</StackPanel>
</Window>
Here's the ViewModel with the command:
public class MainViewModel : ViewModelBase
{
public ICommand MyCommand { get; private set; }
public MainViewModel()
{
MyCommand = new RelayCommand(MyCommand_Executed, MyCommand_CanExecute);
}
private bool MyCommand_CanExecute()
{
return true;
}
private void MyCommand_Executed()
{
MessageBox.Show("The command executed");
}
}
The problem here is the implicit DataContext in ContentTemplate is the Content and this has not been set to anything. You need to set Content to some Binding to bridge the DataContext currently in the visual tree, something like this:
<ContentControl ContentTemplate="{StaticResource MyButton}" Content="{Binding}"/>
Another solution is to give your Window a name:
<Window x:Class="ContentControlExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ContentControlExample.ViewModel"
x:Name="_this">
Then bind via its context instead:
<Button Content="SUBMIT" Command="{Binding ElementName=_this, Path=DataContext.MyCommand}" Width="200" Height="50"/>
This is particularly handy for things like ListViews and ItemControls, as their DCs get set to the list elements. Keep in mind though that this will only work on members within the same visual tree, if that's not the case (e.g. popup menus etc) then you need to proxy a binding as described in this article.

How to pass MenuItem as command parameter for its child control

I have something like below. For MenuItem, here I am passing an object of that MenuItem as a CommandParameter. This works fine for me. My MenuItem holds a RadioButton and I want to use the MenuItem CommandParameter value for this RadioButton. Could anyone please help me how to do this. Thanks in Advance.
<MenuItem Header="Name"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding SortCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}">
<MenuItem.Icon>
<RadioButton VerticalAlignment="Center"
Margin="3"
IsChecked="True"
GroupName="sort"
Command="{Binding SortCommand}"
CommandParameter="..." />
</MenuItem.Icon>
</MenuItem>
Now Command is executing only when I select the MenuItem. I want to do the same when user selects the RadioButton also. Below is the code which I am using for this.
public void OnSortCommandExecuted(object menuItem)
{
MenuItem menu = menuItem as MenuItem;
if (menu != null)
{
((RadioButton)menu.Icon).IsChecked = !((RadioButton)menu.Icon).IsChecked;
this.eAggregator.GetEvent<ImagesSortedEvent>().Publish(menu.Header.ToString());
}
}
Like I said in the comments as well, it's not a good practise to pass on UI component as CommandParameter to ViewModel since ViewModel shouldn't know about View.
I would suggest you to have proper binding in ViewModel. Create a bool property in ViewModel and bind with IsChecked DP of radioButton. That ways you don't have to pass any CommandParameter from View, simply check the status of bool property from command execute method.
Now, that why MenuItem can't be accessed from RadioButton?
RadioButton doesn't lie in same Visual tree as that of MenuItem.
So, you can't use RelativeSource to travel upto MenuItem. Also ElementName binding won't work here since this to work both elements should lie in same Visual Tree.
You might find over net to use x:Reference in such cases where two elements doesn't lie in same Visual tree but that won't work here since it will create cyclic dependency.
Last thing, you have to resort with it to use Freezable class object to hold an instance of MenuItem and use that resource in your bindings.
First of all you need to define class deriving from Freezable:
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); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object),
typeof(BindingProxy));
}
and you can use it from XAML like this to pass MenuItem:
<MenuItem Header="Name"
x:Name="menuItem"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding SortCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}">
<MenuItem.Resources>
<local:BindingProxy x:Key="proxy"
Data="{Binding Source={x:Reference menuItem}}"/>
</MenuItem.Resources>
<MenuItem.Icon>
<RadioButton VerticalAlignment="Center"
Margin="3"
IsChecked="True"
GroupName="sort"
Command="{Binding SortCommand}"
CommandParameter="{Binding Data.CommandParameter,
Source={StaticResource proxy}}"/>
</MenuItem.Icon>
</MenuItem>
Ofcourse you need to declare local namespace in XAML.
PS - I would still insist to use first approach to define proper bindings in ViewModel.
UPDATE
If MenuItem is placed under ContextMenu, then RelativeSource binding won't be possible. Approach described above will work in that case.
But in case you are placing MenuItem directly as child of some control (like Menu), RelativeSource binding will work:
CommandParameter="{Binding CommandParameter,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MenuItem}}"

Button Command Binding

I have a button inside a listbox.
I want to bind the command to the DataContext of the Main Grid.
I'm not sure who to do this, below is my attempt.
I want to bind to ViewModel.SelectionEditorSelectionSelectedCommand on my view model, which the main grid is bound to, I don't want to bind to the actual filteredSelection.SelectionEditorSelectionSelectedCommand
Here is my XAML
<Grid Name="MainGrid">
.....
<ListBox x:Name="MarketsListBox" Height="Auto" MaxHeight="80" ItemsSource="{Binding Path=FilteredMarkets}" Margin="5" Width="Auto" HorizontalAlignment="Stretch"
>
ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel Orientation="Horizontal">
<Button Height="Auto"
Content="{Binding FinishingPosition,Converter={StaticResource FinishingPositionToShortStringConverter1}}"
Foreground="{Binding Path=FinishingPosition, Converter={StaticResource FinishingPositionToColourConverter1}}"
Margin="2" Width="20"
Command="{Binding ElementName=MainGrid.DataContext, Path=SelectionEditorSelectionSelectedCommand}"
CommandParameter="{Binding}"
/>
.....
Binding to the grid using ElementName should work, but you have made a small error in the binding syntax. ElementName must include the name only, not a property. You simply need to include DataContext in the Path:
Command="{Binding ElementName=MainGrid,
Path=DataContext.SelectionEditorSelectionSelectedCommand}"
So based on this line:
Command="{Binding ElementName=MainGrid.DataContext ... }
I'm assuming you have something like this:
<Grid Name="MainGrid">
<Grid.DataContext>
<lol:GridViewModel /> <!--Some kind of view model of sorts-->
</Grid.DataContext>
... content
</Grid>
Then all you would have to do is on the ViewModel class create a public property that returns some sort of ICommand, such as:
class GridViewModel {
public ICommand SelectionEditorSelectionSelectedCommand {
get { return new TestCommand(); }
}
}
Where TestCommand would be some kind of class implementing ICommand as in:
class TestCommand : ICommand {
public event EventHandler CanExecuteChanged { get; set; }
public bool CanExecute(object parameter)
{
return true; // Expresses whether the command is operable or disabled.
}
public void Execute(object parameter)
{
// The code to execute here when the command fires.
}
}
Basically, for ICommand you just need to define what happens when the command Executes, how to determine whether or not it CanExecute and then supply an event handle for when CanExecuteChanged. Once you have this setup, all you have to do is wire up your button like this:
<Button Command="{Binding SelectionEditorSelectionSelectedCommand}" />
And that's it. Basically the binding will automatically check your ViewModel class for a property called SelectionEditorSelectionSelectedCommand, that implements ICommand. When it reads the property it will instantiate an instance of TestCommand, and WPF will handle it from there. When the button is clicked Execute will be fired like clockwork.
You should try as I did in a similar situation:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid}}, Path=DataContext.YOURCOMMANDHERE}" />
I had a button inside a TabItem Header and it worked Ok!
The thing is, your Command is a Property of the DataContext, so your path should indicate it.
Good Luck!
EDIT: Elementname might work as well.

Binding one control to another's DataContext

I bind my wpf window to app layer class (WindowVM.cs) using DataContext in Window.xaml.cs constructor (DataContext = WindowVM). But, one control (btnAdd) I want to bind to Window.xaml.cs property. So in Window.xaml.cs constructor I add this.btnAdd.DataContext. This is Window.xaml.cs constructor and property to which I want bind Button btnAdd:
public Window()
{
InitializeComponent();
DataContext = WindowVM;
this.btnAdd.DataContext = this;
}
public RelayCommand Add
{
get
{
return _add == null ? _add= new RelayCommand(AddPP, CanAddPP) : _add;
}
set
{
OnPropertyChanged("Add");
}
}
Xaml looks like this (class PP is WindowVM property):
<TextBox Name="txtName" Text="{Binding PP.Name, ValidatesOnDataErrors=true, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Name="txtSurname" Text="{Binding PP.Surname, ValidatesOnDataErrors=true, UpdateSourceTrigger=PropertyChanged}" />
<Button Command="{Binding Add}" Content="Add" ... />
And - everything works, but console output this:
BindingExpression path error: 'Add' property not found on 'object' ''WindowVM'...
In next calls there isn't any console output error for property Add.
Now I am a little bit confused because of this error. Is this error because of first DataContext (to WindowVM), because there isn't property Add, but with line this.btnAdd.DataContext property Add is found and it's the reason that it works?
Simply set the DataContext of the Button in the XAML using a RelativeSource:
<Button Command="{Binding Add}" Content="Add" DataContext="{Binding Add, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
I had this problem and I know this is an oldish post but I think this might help someone who stumbles on this in the future.
what I did was declare the viewmodels as resources
<Page.Resources>
<local:LocationListViewModel x:Key="LocationList" />
<local:LocationNewViewModel x:Key="NewLocation" />
<code:BinaryImageConverter x:Key="imgConverter" />
</Page.Resources>
then which ever control I wanted to be associated with said viewmodel I added this to their datacontext
<TabItem x:Name="tabSettingsLocations" x:Uid="tabSettingsLocations"
Header="Locations"
DataContext="{StaticResource ResourceKey=LocationList}">....
<TabItem x:Name="tbSettingsLocationsAdd" x:Uid="tbSettingsLocationsAdd"
Header="Add New"
DataContext="{StaticResource ResourceKey=NewLocation}">....
<Image x:Name="imgSettingsLocationMapNew" x:Uid="imgSettingsLocationMapNew"
Source="{Binding Map, Converter={StaticResource imgConverter},
Mode=TwoWay}"
DataContext="{StaticResource ResourceKey=NewLocation}" />
So in my example above I have Listview bound to the list viewmodel and I create a new single location for my new entry. You will notice that by creating it as a resource I can bind the tabitem and the image (which is not a child of the tab item) to the new location viewmodel.
My command for the addnew location is in the new location viewmodel.
<TabItem x:Name="tbSettingsLocationsAdd" x:Uid="tbSettingsLocationsAdd"
Header="Add New"
DataContext="{StaticResource ResourceKey=NewLocation}">....
<Button x:Name="btnSettingsLocationSaveAdd" x:Uid="btnSettingsLocationSaveAdd" Content="Submit" Margin="0,80,10,0"
VerticalAlignment="Top" Style="{DynamicResource ButtonStyle}" HorizontalAlignment="Right" Width="75"
Command="{Binding AddCommand}" />.....
Which is the child of the tabitem I bound to the new location viewmodel.
I hope that helps.
When you set the DataContext-Property, your Window resets the Bindings of it's child controls. Even the Binding of your button.
At this Point (before "button.DataContext = this" is evaluated) "Add" is searched in WindowVM. After this you set the Window class as buttons DC, and everything works fine.
To avoid the initial error, swap two lines from this
public Window()
{
InitializeComponent();
DataContext = WindowVM;
this.btnAdd.DataContext = this;
}
to this
public Window()
{
InitializeComponent();
this.btnAdd.DataContext = this;
DataContext = WindowVM;
}

How would you know which element of an ItemsControl sends an event in MVVM?

Let's say I currently have an ItemsControl whose DataTemplate is a bunch of buttons. I'm wiring up these buttons' click events, but how am I to know which button was clicked? Should I not use a ItemsControl?
I'm trying to have no code-behind, but being pragmatic may be necessary.
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="10">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding ItemsControlButtonClicked, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you want to know what Item was clicked, then pass {Binding } as the CommandParameter and it will pass the selected object to your Command
If you want to know what Button was clicked, I would do that in the code-behind since ViewModels do not need to know anything about the UI, and that includes buttons.
Also since your control is a Button, you should use the Command property instead of a Click trigger.
<Button Command="{Binding ItemsControlButtonClicked}" />
You can send parameters along with the command and based on these parameters you can find out which button was clicked
In my project I also use the MVVM Light I has an dropdown with collection of items, and a button which user press and action depend on selected item from drop down
you should create a Relay command with parameter look at the example from my code
public RelayCommand<Project> StartTimer { get; private set; }//declare command
StartTimer = new RelayCommand<Project>(OnStartTimer);
private void OnStartTimer(Project project)
{
if (project != null)
{
currentProject = project;
if (!timer.IsTimerStopped)
{
timer.StopTimer();
}
else
{
Caption = "Stop";
timer.StartTimer();
}
}
on the view I bind the drop down with collection of class Project
and for button command parameter I bind the selected item form drop down
look at the code
<ComboBox Name="projectcomboBox" ItemsSource="{Binding Path=Projects}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="FullName"
SelectedValuePath="Name" SelectedIndex="0" >
</ComboBox>
<Button Name="timerButton" Content="{Binding Path=Caption}" Command="{Binding Path=StartTimer}"
CommandParameter="{Binding ElementName=projectcomboBox, Path=SelectedItem}" ></Button>
pay attention to Command and CommandParameter binding
also you can use this approache not only for drop down
Well, you can use the Sender.DataContext which is the actual data.
Create command properties in your view model class (using Josh Smith's RelayCommand pattern is the simplest way to do this) and bind each button's Command to the appropriate one. Not only is this straightforward to do and simple to maintain, it also gives you an easy way of implementing the enable/disable behavior when you need to.

Categories

Resources