Silverlight: Mvvm, INotifyPropertyChanged и свойства
За последнее время я увидел много постов, расказывающих о том как упростить процес создания свойств которые вызывают событие PropertyChanged. Это и codesnippet'ы и атрибуты для генерации кода... Решил написать о своем варианте который не использует ни то ни другое (намного проще).
И так, стандартный код свойства в MVVM:
1 2 3 4 5 6 7 8 9 10 11 12 13 | private string _name; public string Name { get { return _name; } set { if ( _name != value) { _name = value; RaisePropertyChanged( "Name" ); } } } |
А теперь если представить что таких полей у вас должно быть 10 - это приведет к тому, что в коде будет очень много лишнего, может даже намного больше чем самой логики... да можно отнести эти все свойства в partial класс, но для каждой модели создавать отдельный partial класс - не рационально (по моему мнению).
В своих проектах я использую следующие решение (я не говорю что это самое лутше/оптимальное решение - но для меня оно самое удобное)
1. Для начала создадим класс DataValue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class DataValue : INotifyPropertyChanged { protected object _value; public DataValue( object value) { _value = value; } public object Value { get { return _value; } set { if ( _value != value) { _value = value; RaisePropertyChanged( "Value" ); } } } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged( string propertyName) { var handler = PropertyChanged; if (handler != null ) handler( this , new PropertyChangedEventArgs(propertyName)); } } |
Думаю это код понятен....
2. Создадим безовый класс для модели - ModelBase:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class ModelBase { public ModelBase() { Values = new Dictionary< string , DataValue>(10, StringComparer.OrdinalIgnoreCase); } public Dictionary< string , DataValue> Values { get ; private set ; } protected void SetValue<T>( string key, T value) { DataValue dataValue; if (!Values.TryGetValue(key, out dataValue)) Values.Add(key, new DataValue(value)); else dataValue.Value = value; } protected T GetValue<T>( string key) { return (T) Values[key].Value; } } |
Словарь Values - будет хранить все наши значения в одном месте, доступ к определенному значению будет воспроизводиться по строковому ключу. Так как этот словарь не typesafe, создали два метода SetValue и GetValue для простоты. Также обратите внимание при создании словаря, мы указываем StringComparer.OrdinalIgnoreCase - поэтому нам не придеться задумываться о регистре ключа значения.
3. Создаем модель для нашей главной странице - MainPage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class MainPageModel : ModelBase { internal const string VALUENAME = "name" ; internal const string VALUEAGE = "age" ; public MainPageModel() { Initialize(); } private void Initialize() { SetValue(VALUENAME, "Boris" ); SetValue(VALUEAGE, 19); } } |
Модель MainPageModel наследуеться от ранее созданого класса ModelBase.
Создали две константы - ключи значений. Обязательно нижно проинициализировать значения во время создания класса, так как если это сделать пожже, в UI они не подхватяться, так как словарь Values это не ObservableDictionary (стандартного нету).
4. Xaml - MainPage.xaml
Биндинг осуществляется так:
- Только для чтения - {Binding Values[value_key].Value}
- Для чтения и записи - {Binding Values[value_key].Value, Mode=TwoWay}
Основная часть xaml разметки примера:
1 2 3 4 5 6 7 8 9 10 11 12 | <StackPanel VerticalAlignment= "Top" Orientation= "Horizontal" Margin= "96,125,84,0" Background= "White" > <TextBlock Text= "{Binding Values[name].Value}" Width= "150" Margin= "2" /> <TextBlock Text= ", " Margin= "2" /> <TextBlock Text= "{Binding Values[age].Value}" Width= "50" Margin= "2" /> </StackPanel> <StackPanel Orientation= "Horizontal" VerticalAlignment= "Bottom" Margin= "77,0,61,112" > <TextBlock Text= "name:" /> <TextBox Text= "{Binding Values[name].Value, Mode=TwoWay}" Width= "150" /> <TextBlock Text= "age:" /> <TextBox Text= "{Binding Values[age].Value, Mode=TwoWay}" Width= "50" /> </StackPanel> |
Выглядеть оно будет так:
Плюсы и минусы:
- "+" Чистота кода, все значения храняться в одном месте
- "+" Возможность добавить дополнительные поля для каждого значения. Например, добавить в класс DataValue такое поле как IsValid.
- "-" Невозможность контролировать (легким способом) значение которое меняеться в DataValue, только когда будет вызвана какая-то функция в модели.
Скачать проект можно тут.
Спасибо, критика и пожелания приветствуються.