Reputation: 16268
I have an ItemsControl
so that I can display multiple instance of the same template. I need to be able to execute code on event handlers so that I can tell controls apart.
For example: I have a list of groceries, so my DataTemplate
contains a "buy" Button
for each food. I want to bind said button to code and tell which button was pressed.
How can I accomplish that, considering I'm using MVVM design pattern
** XAML :**
<ItemsControl ItemsSource="{Binding MyItemList}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<Button Content="Buy" />
</DataTemplate>
</ItemsControl.ItemsTemplate>
</ItemsControl>
So, MyItemList
is a List<MyItem>
instance. The DataTemplate
contains controls that modify values or execute code not present in MyItem
:
I have read a lot of articles on biding templates to commands, but I cant find one that uses a list of items.
Upvotes: 6
Views: 5105
Reputation: 5536
You should never use events in DataTemplates this will make you use casting and then blow a hole in the whole MVVM pattern. A button has the Command property and you should Bind that property to a command inside your MyItem ViewModel.
If you still need to use an event (for instance you cant bind MouseDown to a command) you shoudl use the EventToCommadn Behaviour which allows you to bind an event to a command. You can read about it here: http://msdn.microsoft.com/en-us/magazine/dn237302.aspx
Upvotes: 3
Reputation: 12533
You need to bind the Button to a Command your ItemsControl's DataContext.
Search for Command in WPF : ( A Common implementation ) :
public class RelayCommand<T> : IRelayCommand
{
private Predicate<T> _canExecute;
private Action<T> _execute;
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
private void Execute(T parameter)
{
_execute(parameter);
}
private bool CanExecute(T parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public bool CanExecute(object parameter)
{
return parameter == null ? false : CanExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
var temp = Volatile.Read(ref CanExecuteChanged);
if (temp != null)
temp(this, new EventArgs());
}
}
In your ViewModel ( The ItemsControl's DataContext , I Hope :) )
private RelayCommand<FoodItem> _addToGroceriesCommand;
public ICommand AddToGroceriesCommand
{
get
{
if (_addToGroceriesCommand == null)
{
_addToGroceriesCommand = new RelayCommand<FoodItem>(OnAddToGroceries);
}
return _addToGroceriesCommand;
}
}
public void OnAddToGroceries(FoodItem newItem)
{
}
XAML :
<ItemsControl ItemsSource="{Binding MyItemList}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<Button Content="Buy"
Command="{Binding Path=DataContext.AddToGroceriesCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}" />
</DataTemplate>
</ItemsControl.ItemsTemplate>
</ItemsControl>
Upvotes: 5
Reputation: 1767
There are several things you might do.
<Button Content="Add" Click={Click} Tag="{Binding .}" DataContext="{Binding .}" />
DataContext="{Binding .} - sets the whole VM instance to property. You can do the same thing with the Tag property. Sometimes it is usefull to use Tag for these purposes. You can user either of them. Both will work.
public void Click(...)
{
var control = sender as FrameWorkElement;
if(control!= null)
{
var myVM = control.DataContext as MyViewModel;
myVM.DoSomethingWithMyVM();
}
}
You can create a usercontrol that would contain the grid and in the grid you reference the custom usercontrol. That's very flexible. In it's ButtonEventhandler you can access the datacontext and do what you need with it. this is much easier, but you'll have more work with notifications to parrent objects. This is better if you want to reuse this control.
Another thing you can do is to set the datacontext of the button to the whole ViewModel. A last effort solution would be to set the Tag of the button to the whole ViewModel. Better if you are not planing to reuse it.
You can also use this as a resource from the resourceDictionary.
Upvotes: -1