Если Вы разработчик WPF/MVVM, то, надеюсь, найдете нечто новое в этой статье. Если же Вы не знакомы с WPF, то можете ознакомиться с данным материалом, чтобы посмотреть, как можно подружить WPF с динамическими
типами языка C#.
Поговорим о том, как создавать нужные модели для байндинга на лету. Ранее в своем блоге я часто использовал пример с
электронной библиотекой. Одним из сложных моментов создания такого приложения является необходимость создавать модель представления
и модель для каждого представления. Поэтому зачастую архитектура такого
приложения напоминает вид, показанный на рисунке ниже.
Для проекта, в котором
много маленьких моделей, нужно придумать более сложное решение, а не
реализовывать на каждый "чих" новую модель представления и модель.
Для простеньких проектов на помощь приходит ExpandoObject, который позволяет
избежать рутинного создания модели. Это класс, позволяющий создавать наши
объекты в рантайме. Кроме этого, данный класс наследуется от интерфейса INotifyPropertyChanged, поэтому данные
будут отображены на экране в нормальном виде и есть возможность получать
необходимые уведомления об изменении тех или иных свойств. Чтобы указать на
изменение свойств, нам нужно было явно наследоваться от интерфейса INotifyPropertyChanged
и
для каждого свойства вызывать PropertyChanged.
public class LibraryBook : INotifyPropertyChanged
{
private string _author;
public string Author
{
get { return _author; }
set
{
_author = value;
OnPropertyChanged("Author");
}
}
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged("Title");
}
}
private DateTime _year;
public DateTime Year
{
get { return _year; }
set
{
_year = value;
OnPropertyChanged("Year");
}
}
private string _sn;
public string SN
{
get { return _sn; }
set
{
_sn = value;
OnPropertyChanged("SN");
}
}
private int _count;
public int Count
{
get { return _count; }
set
{
_count = value;
OnPropertyChanged("Count");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Многовато
кода для такой простенькой модели. Если использовать для этих целей PostSharp, то код упростится. Необходимые нам атрибуты пометим атрибутом [NotifyPropertyChanged]. По сути, мы сделаем то же самое, что и в описанном выше коде,
просто вместо нас это сделает PostSharp. PostSharp позволяет вставлять функционал в существующий код путем
переписывания IL-а.
От использования данного класса мы не избавляемся. Зная, что у нас такой простенький класс и нужно только вывести его для отображения,
то что нам мешает максимально упростить процесс. Приступим к реализации данного функционала. Для начала необходимо
реализовать внешний вид того, как мы хотим отобразить данную модель через XAML-код. Ниже
приведена простая реализация данного функционала.
<Window x:Class="WpfApplicationExpandoObject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListView x:Name ="library" Grid.Column ="0" ItemsSource="{Binding Books}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Author: " />
<TextBlock Text="{Binding Author}" FontWeight="Bold" />
<TextBlock Text=", " />
<TextBlock Text="Caption: " />
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
<TextBlock Text="Count: " />
<TextBlock Text="{Binding Count}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" DataContext="{Binding ElementName=library, Path=SelectedItem}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"
/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Author" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Author}" />
<Label Grid.Row="1" Grid.Column="0" Content="Title" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Title}" />
<Label Grid.Row="2" Grid.Column="0" Content="Year" />
<DatePicker Grid.Row="2" Grid.Column="1" SelectedDate="{Binding Year}" />
<Label Grid.Row="3" Grid.Column="0" Content="Serial Number"
/>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding SN}" />
<Label Grid.Row="4" Grid.Column="0" Content="Count" />
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding Count}" />
</Grid>
</Grid>
</Grid>
</Window>
Наше представление (view) будет иметь следующий вид
:
Остался последний
штрих: создание необходимой модели на лету. Для этого в App.xaml удалим
StartupUri,
затем перейдем в App.xaml.cs и
переопределим метод OnStartup.
А далее смотрим на код и наслаждаемся его простотой.
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
dynamic viewModel = new ExpandoObject();
viewModel.Books = GetBooks();
var view = new MainWindow();
view.DataContext = viewModel;
view.Show();
}
private dynamic GetBooks()
{
dynamic books = new ObservableCollection<dynamic>();
dynamic book = new ExpandoObject();
book.Author = "Jon Skeet";
book.Title = "C# in Depth";
book.Count = 3;
book.SN = "ISBN: 9781617291340";
book.Year = new DateTime(2013, 9, 10);
books.Add(book);
book = new ExpandoObject();
book.Author = "Martin Fowler";
book.Title = "Refactoring: Improving the
Design of Existing Code";
book.Count = 2;
book.SN = "ISBN-10: 0201485672";
book.Year = new DateTime(1999, 7, 8);
books.Add(book);
book = new ExpandoObject();
book.Author = "Jeffrey Richter";
book.Title = "CLR via C# (Developer
Reference)";
book.Count = 5;
book.SN = "ISBN-10: 0735667454";
book.Year = new DateTime(2012, 12, 4);
books.Add(book);
return books;
}
}
Сказать, что это
просто, – ничего не сказать. При изменении нужного свойства после запуска
приложения оно сразу отображает измененные данные. Это говорит о том, что наша
модель отлично работает с интерфейсом INotifyPropertyChanged. Преимущество данного подхода
проявляется в гибкости данного подхода. Данные для отображения можно достать с XML, JSON, SOAP и
т.д. Подход с созданием для некоторых целей модели представления в большинстве
случаев будет оправданным хорошим решением, в отличие от подхода с
использованием ExpandoObject.
При подходе, описанном в статье, мы теряем возможности статической типизации, и
это грозит снижением скорости выполнения данной операции, а также потерей
поддержки intellisense.
Мы теряем одни возможности, а вместо них получаем другие. В каждой задаче нужно
искать компромиссное решение. Поэтому сначала думаем, затем делаем. И Ваши
программные решения будут работать стабильно, независимо от подхода к их
реализации.
No comments:
Post a Comment