Как можно применить MVVM подход к обработке клика на один из элементов списка? Есть одно "большое волосатое НО" - в метод-обработчик клика у презентера нужно передавать параметр (допустим, сам элемент списка). Да, при помощи стандартного обработчика клика в code behind это легко сделать - вешай обработчик на Click, а в нем вызывай метод презентера. Но вопрос в том, возможно ли/как это сделать только при помощи xaml-разметки?
MVVM подход к обработке события Click в ItemsControl
Новые ответы
Вот еще неплохой вариант. Создать оболочку над элементами списка. Сделать так, чтобы каждый элемент списка с данными удовлетворял интерфейсу
public interface ICommandElement
{
ICommand ClickCommand { get; }
}
Например:
public delegate void ItemClickHandler(object item);
public class CommandedItem: ICommandElement
{
private ItemClickHandler _handler;
private ICommand _command;
private void DoClick()
{
if (_handler != null) _handler(this);
}
public CommandedItem(object item, ItemClickHandler handler)
{
...
_handler = handler;
_command = new RelayCommand(x => DoClick(), x => true);
}
public ICommand ClickCommand { get { return _command; } }
}
Идея RelayCommand
взята отсюда. В таком случае код разметки преобразится в следующее:
<ItemsControl ItemsSource="{Binding Path=Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="3" Height="3" Fill="Black"/>
<TextBlock Margin="4">
<Hyperlink Command="{Binding ClickCommand}">
<TextBlock Text="Тут мог бы быть Ваш байндинг на объект данных!"/>
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Проиллюстрирую проблему кодом:
<ItemsControl ItemsSource="{Binding Path=MostRecentFiles.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="15 0 0 0">
<Ellipse Width="3" Height="3" Fill="Black"/>
<Button Background="Transparent"
Content="{Binding Mode=OneWay}"
Command="{Binding OpenRecentProjectCommand}"
CommandParameter="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
В данном примере я пытаюсь вызвать команду OpenRecentProjectCommand
у презентера/ViewModel. Но тут проблема - контекстом для элемента списка будет не презентер, а элемент данных, на основе которого и строится этот элемент.
Можно использовать action из библиотеки caliburn, чтобы вызывать методы ViewModel по событию контрола:
<ListView x:Name="listView" Message.Attach="[Event MouseClick] = [Action ItemActivated(listView.SelectedItem)]" >
Чистый MVVM подход обычно подразумевает использование команд вместо событий и генерации code behind. Но проблема WPF в том, что не все события могут вызывать команду. Обычно с командой связано только одно основное событие некоторых контролов, например, клик на кнопке.
Для решения этой проблемы используется паттерн Attached Behavior и его модификация для команд. Суть паттерна Attached Behavior заключается в инкапсуляции поведения внутри некоторой сущности для последующего применения к любым визуальным элементам. В WPF такая инкапсуляция организуется при помощи стилей. Существует реализация этого паттерна, позволяющая мепить произвольные события на команды, задавая привязку для параметров. Одна из реализаций описана в статье AttachedCommandBehavior V2 aka ACB. Автор разработал библиотеку, позволяющую вызывать команду по событию:
<Border Background="Yellow" Width="350" Margin="0,0,10,0" Height="35" CornerRadius="2" x:Name="test">
<local:CommandBehaviorCollection.Behaviors>
<local:BehaviorBinding Event="MouseLeftButtonDown" Action="{Binding DoSomething}" CommandParameter="An Action on MouseLeftButtonDown"/>
<local:BehaviorBinding Event="MouseRightButtonDown" Command="{Binding SomeCommand}" CommandParameter="A Command on MouseRightButtonDown"/>
</local:CommandBehaviorCollection.Behaviors>
<TextBlock Text="MouseDown on this border to execute the command"/>
</Border>