Еще одно сравнение паттернов MVVM и MVP для Silverlight
- modified:
- reading: 4 minutes
Я уже когда-то поднимал тему сравнения паттернов MVP и MVVM при разработке Silverlight приложений: Выступление на ADD2010: Silverlight/WPF: возврат от паттерна MVVM к MVP. Вопрос, на самом деле сложный, какой из паттернов лучше. Я бы хотел продемонстрировать небольшой пример и подискутировать на эту тему в комментариях. Был бы рад, если кто-нибудь нашел бы хороший ответ на мой вопрос, который будет дальше.
Давайте рассмотрим простой пример. Сделаем ContextMenu в TextBox с одним MenuItem, которое будет просто вызывать MessageBox. Сразу оговорюсь, что сделать я хочу именно так, как сделаю. Вторым примером сделаем ListBox с элементами TextBox, где у каждого будет тоже свое ContextMenu.
Пример 1. Реализация с паттерном MVVM
Реализуем простейшую команду:
public class MyCommand : ICommand
{ public bool CanExecute(object parameter)
{ return true;
}
public void Execute(object parameter)
{ MessageBox.Show("Yep!");
}
public event EventHandler CanExecuteChanged;
}
Делаем простейший ViewModel:
public class ViewModel
{ private MyCommand _command = new MyCommand();
public MyCommand Command { get { return _command; } }
}
Пишем разметку:
<UserControl x:Class="SilverlightApplication3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SilverlightApplication3="clr-namespace:SilverlightApplication3"
xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
x:Name="ParentControl">
<UserControl.DataContext>
<SilverlightApplication3:ViewModel />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Width="300" Height="200" >
<Controls:ContextMenuService.ContextMenu>
<Controls:ContextMenu>
<Controls:MenuItem Header="Test" Command="{Binding Path=DataContext.Command, ElementName=ParentControl}" />
</Controls:ContextMenu>
</Controls:ContextMenuService.ContextMenu>
</TextBox>
</Grid>
</UserControl>
В code behind все по-стандартному. Задача вырвана из контекста, потому у меня тут немного странный байндинг. Ну к нему можно прийти очень даже просто, если Grid, например, будет иметь DataContext={Binding Path=Entity} или что-то типа того.
Пример 1. Реализация при помощи паттерна MVP
Меняем разметку:
<UserControl x:Class="SilverlightApplication3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit" >
<Grid x:Name="LayoutRoot" Background="White">
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Width="300" Height="200" >
<Controls:ContextMenuService.ContextMenu>
<Controls:ContextMenu>
<Controls:MenuItem Header="Test" Click="MenuItem_Click" />
</Controls:ContextMenu>
</Controls:ContextMenuService.ContextMenu>
</TextBox>
</Grid>
</UserControl>
Создаем класс Presenter:
public class Presenter
{ public void DoAction()
{ MessageBox.Show("Yep!");
}
}
И немного кода в code behind самой View:
public partial class MainPage : UserControl
{ private Presenter _presenter = new Presenter();
public MainPage()
{
InitializeComponent();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
_presenter.DoAction();
}
}
Пример 2. Реализация с паттерном MVVM
Здесь у нас уже будет коллекция элементов, на основе которой будет, в принципе, то же самое, что в примере 1. Код:
public class Entity
{ public string Title { get; set; }
}
public class MyCommand : ICommand
{ public bool CanExecute(object parameter)
{ return true;
}
public void Execute(object parameter)
{ MessageBox.Show("Yep!");
}
public event EventHandler CanExecuteChanged;
}
public class ViewModel
{ private List<Entity> _entities = new List<Entity>() {
new Entity() {Title = "Entity 1"},
new Entity() {Title = "Entity 2"},
new Entity() {Title = "Entity 3"}
};
private MyCommand _command = new MyCommand();
public MyCommand Command
{ get { return _command; }
}
public List<Entity> Entities
{ get { return _entities; }
}
}
Я тут сделал класс Entity с одним свойством, а во ViewModel добавил свойство, которое возвращает коллекцию Entities. XAML меняется следующим образом:
<UserControl x:Class="SilverlightApplication3.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SilverlightApplication3="clr-namespace:SilverlightApplication3"
xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
x:Name="ParentControl">
<UserControl.DataContext>
<SilverlightApplication3:ViewModel />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ListBox HorizontalAlignment="Center" VerticalAlignment="Center" ItemsSource="{Binding Path=Entities}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Width="300" Height="100" >
<Controls:ContextMenuService.ContextMenu>
<Controls:ContextMenu>
<Controls:MenuItem Header="Test" Command="{Binding Path=DataContext.Command, ElementName=LayoutRoot}" />
</Controls:ContextMenu>
</Controls:ContextMenuService.ContextMenu>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
Пример 2. Реализация при помощи паттерна MVP
Код:
public class Entity
{ public string Title { get; set; }
}
public class BindingModel
{ private List<Entity> _entities = new List<Entity>() {
new Entity() {Title = "Entity 1"},
new Entity() {Title = "Entity 2"},
new Entity() {Title = "Entity 3"}
};
public List<Entity> Entities
{ get { return _entities; }
}
}
public class Presenter
{ private BindingModel _bindingModel = new BindingModel();
public BindingModel BindingModel
{ get { return _bindingModel; }
}
public void DoAction()
{ MessageBox.Show("Yep!");
}
}
Добавили BindingModel (то же самое, что PresentationModel в определении Мартина Фаулера). Поменяем немного XAML:
<UserControl x:Class="SilverlightApplication3.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit" >
<Grid x:Name="LayoutRoot" Background="White">
<ListBox HorizontalAlignment="Center" VerticalAlignment="Center" ItemsSource="{Binding Path=Entities}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Width="300" Height="100" >
<Controls:ContextMenuService.ContextMenu>
<Controls:ContextMenu>
<Controls:MenuItem Header="Test" Click="MenuItem_Click" />
</Controls:ContextMenu>
</Controls:ContextMenuService.ContextMenu>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
Ну, и подправим Code Behind:
public partial class MainPage : UserControl
{ Presenter _presenter = new Presenter();
public MainPage()
{
InitializeComponent();
DataContext = _presenter.BindingModel;
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
_presenter.DoAction();
}
}
Выводы
А теперь, коллеги, давайте еще раз присмотримся к коду и попробуем ответить на опрос (с подвохом):
Если считаете, что какой-то вариант не рабочий, то прошу рассказать в комментариях почему так. У меня только есть небольшая догадка относительно этого всего, о которой я расскажу чуть позже.