How to pass MenuItem as command parameter for its child control - c#

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}}"

Related

Datacontext not available in DataGridTemplateColumn

I have the following grid:
<DataGrid
x:Name="CandiesDataGrid"
ItemsSource="{Binding Candies}"
SelectedItem="{Binding SelectedCandy}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding CandySelectedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
<DataGridTextColumn KeyboardNavigation.IsTabStop="False" IsReadOnly="True" Width="100" Header="{l:LocText Candy_Prop1}" Binding="{Binding CandyInfo.Name}"/>
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="IsConfirmed" Grid.Column="0"
Style="{StaticResource CandyCheckBox}"
IsChecked="{Binding IsConfirmed, Mode=TwoWay}"
Margin="-75 0 0 0"
Command="{Binding IsConfirmedCommand}">
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
My property uses the OnPropertyChanged. Not only it does not change the value of IsConfirmed but also does not executes the ICommand IsConfirmedCommand.
I searched on the internet and it seems DataGridTemplateColumn loses the ItemSource of the datagrid.
I did try to put RelativeSource in after the mode=TwoWay on my checkbox but it does not work.
Is there any way to have access to the ItemSource in my TemplateColumn?
EDIT:
//Properties
public ObservableCollection<Candy> Candies{ get; } = new ObservableCollection<Candy>();
public Candy SelectedCandy { get { return _selectedCandy; } set { SetProperty(ref _selectedCandy, value); } } //SetProperty acts as OnPropertyChanged
private Candy _selectedCandy;
//Constructor:
public CandyClass()
{
IsConfirmedCommand = new DelegateCommand(IsConfirmedCommand_Execute);
}
//Method
private void IsConfirmedCommand_Execute()
{
//Doing something
}
Inside your CellTemplate, the DataContext is the DataGrid row, whatever that may be (Candy in this case). So by default, that Candy instance will be the Source property of any Binding in that DataTemplate. That's where the binding will look for the property named in the Path (IsConfirmed and IsConfirmedCommand, in this case).
That's what you want: You've got more than one row in the grid, and the row is what you care about in a cell, usually. That or the field: But very often a cell template will want to look at more than one field, so they give you the whole row.
But in this case, you want to go back up and grab something off the parent viewmodel. Viewmodels have no natural parent/child hierarchy, though you could give them one if you wanted: Candy could have a Parent property that had reference to the viewmodel that owns the Candies collection. If you did, you could bind like this:
Command="{Binding Parent.IsConfirmed}"
But that's not common practice. I don't know if it's a particularly great idea or not.
One reason we don't need to do that is we can tell the binding to use a different source instead. UI elements do have a natural parent/child hierarchy, and bindings can navigate it. If you’re doing things right, your parent viewmodel will be the DataContext of something up there somewhere.
{Binding Path=DataContext.IsConfirmed,
RelativeSource={RelativeSource AncestorType=DataGrid}}
"Walk the UI tree upwards until you find a DataGrid. That's your source. Now, once you have a source, find the source object's DataContext property, if any. If it's got a DataContext, take the value of DataContext and look on that object for some property called IsConfirmed."
DataGrid has a DataContext property. Since your binding to Candies worked, we know that DataContext must be your class that has a Candies property. You assure me that class has IsConfirmed as well.
Hence:
<DataTemplate>
<CheckBox
Style="{StaticResource CandyCheckBox}"
IsChecked="{Binding DataContext.IsConfirmed,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
Margin="-75 0 0 0"
Command="{Binding DataContext.IsConfirmedCommand,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
/>
</DataTemplate>

How to implement commands on MenuItems

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">

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