Я працюю над програмою WPF із переглядами, які потребують численних перетворень цінності. Спочатку моя філософія (частково натхненна цією бурхливою дискусією щодо XAML Disciples ) полягала в тому, що я повинен скласти модель погляду суворо щодо підтримки вимог до даних щодо поглядів. Це означало, що будь-які перетворення значень, необхідні для перетворення даних у такі види, як видимість, пензлі, розміри тощо, будуть оброблятися перетворювачами значень та багатозначними перетворювачами. Концептуально це здавалося досить елегантним. Модель перегляду і погляд мають однозначну мету і добре підключаться. Буде проведено чітку межу між "даними" та "виглядом".
Ну, після надання цій стратегії "старої спроби коледжу", у мене виникають сумніви, чи хочу я продовжувати розвивати цей шлях. Я насправді настійно розглядаю можливість скидання перетворювачів значень і покладання відповідальності за (майже) всю конверсію значень прямо в руки моделі перегляду.
Реальність використання перетворювачів значень просто не здається вимірювальною до очевидного значення чітко розділених проблем. Моя найбільша проблема з перетворювачами цінності полягає в тому, що вони нудно використовувати. Вам потрібно створити новий клас, реалізувати IValueConverter
або передати IMultiValueConverter
значення або значення з object
правильного типу, протестувати DependencyProperty.Unset
(принаймні для багатозначних перетворювачів), написати логіку перетворення, зареєструвати перетворювач у словнику ресурсів [див. Оновлення нижче ], і нарешті, підключіть перетворювач, використовуючи досить багатослівний XAML (що вимагає використання магічних рядків як для прив'язки (и), так і для назви перетворювача[див. оновлення нижче]). Процес налагодження теж не є пікніком, оскільки повідомлення про помилки часто є виразними, особливо в режимі дизайну Visual Studio / Expression Blend.
Це не означає, що альтернатива - зробити модель перегляду відповідальною за все перетворення цінностей - є вдосконаленням. Це може бути справою того, що трава з іншого боку буде зеленішою. Окрім втрати елегантного роз'єднання проблем, вам доведеться написати купу похідних властивостей та переконатися, що ви сумлінно дзвоните RaisePropertyChanged(() => DerivedProperty)
під час встановлення базових властивостей, що може виявитися неприємним питанням технічного обслуговування.
Нижче наведено початковий перелік, який я зібрав за плюси і мінуси, що дозволяють переглядаючим моделям обробляти логіку перетворення та виконувати перетворення значень:
- Плюси:
- Менше загальних прив’язок, оскільки усуваються багатоперетворювачі
- Менше магічних рядків (шляхи прив'язки
+ назви ресурсів конвертора) Немає більше реєстрації кожного перетворювача (плюс підтримка цього списку)- Менше роботи для запису кожного перетворювача (не потрібно застосовувати інтерфейси чи кастинг)
- Легко вводити залежності, щоб допомогти з перетвореннями (наприклад, кольорові таблиці)
- Розмітка XAML менш докладна і легша для читання
- Повторне використання конвертера все ще можливе (хоча планується певне планування)
- Жодних загадкових проблем із DependencyProperty.Unset (проблема, яку я помітив із багатозначними перетворювачами)
* Прорички вказують на переваги, які зникають, якщо ви використовуєте розширення розмітки (див. Оновлення нижче)
- Мінуси:
- Більш міцне з'єднання між моделлю перегляду та видом (наприклад, властивості повинні відповідати таким поняттям, як видимість та щітки)
- Більше загальних властивостей, щоб дозволити пряме відображення для кожного перегляду прив'язки
(див. Оновлення 2 нижче)RaisePropertyChanged
потрібно викликати кожне похідне властивість- Потрібно все ж покластися на перетворювачі, якщо перетворення базується на властивості елемента інтерфейсу
Отже, як ви, напевно, можете сказати, у мене є пекота в цьому питанні. Я дуже вагаюся, щоб піти по шляху рефакторингу лише, щоб зрозуміти, що процес кодування настільки ж неефективний і стомлюючи, чи використовую я перетворювачі значень чи піддаю численні властивості перетворення значення у моїй моделі перегляду.
Я пропускаю якісь плюси / мінуси? Для тих, хто спробував обидва засоби перетворення цінності, які ви вважали, що працюють краще для вас і чому? Чи є інші альтернативи? (Учні щось згадували про постачальників дескрипторів типів, але я не міг зрозуміти, про що вони говорять. Будь-яке розуміння щодо цього було б вдячно.)
Оновлення
Сьогодні я з'ясував, що можна використовувати щось, що називається "розширення розмітки", щоб усунути необхідність реєструвати перетворювачі значень. Насправді це не тільки позбавляє від необхідності їх реєстрації, але фактично забезпечує інтелісенс для вибору перетворювача під час введення Converter=
. Ось стаття, яка мене розпочала: http://www.wpftutorial.net/ValueConverters.html .
Можливість використовувати розширення розмітки дещо змінює баланс у моїх переліках та переліках та обговоренні вище (див. Підкреслення).
В результаті цього відкриття я експериментую з гібридною системою, де я використовую перетворювачі BoolToVisibility
і те, що я називаю, MatchToVisibility
і модель перегляду для всіх інших перетворень. MatchToVisibility - це в основному перетворювач, який дозволяє мені перевірити, чи пов'язане значення (як правило, перерахунок) відповідає одному або більше значень, зазначених у XAML.
Приклад:
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"
В основному, це робиться, щоб перевірити, чи статус закінчено чи скасовано. Якщо це так, то видимість отримує значення "Видиме". В іншому випадку він отримує набори на "Прихований". Це виявилося дуже поширеним сценарієм, і цей конвертер врятував мені близько 15 властивостей на моїй моделі перегляду (плюс пов'язані з ним заяви RaisePropertyChanged). Зауважте, що під час введення Converter={vc:
"MatchToVisibility" відображається в неперервному меню. Це помітно знижує ймовірність помилок і робить перетворювачі значень менш стомлюючими (вам не потрібно пам’ятати і не шукати ім'я потрібного конвертора значень).
Якщо вам цікаво, я вставлю код нижче. Однією з важливих особливостей даної реалізації MatchToVisibility
є те , що він перевіряє, якщо оцінка вартості є enum
, і , якщо він є, він перевіряє , щоб переконатися Value1
, Value2
і т.д., також перерахувань одного і того ж типу. Це забезпечує перевірку під час проектування та час виконання того, чи будь-яке значення значення перерахувань введено неправильно. Щоб покращити це до перевірки часу компіляції, ви можете скористатися наступним (я ввів це вручну, тому, будь ласка, пробачте мене, якщо я допустив помилки):
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue={x:Type {win:Visibility.Visible}},
IfFalse={x:Type {win:Visibility.Hidden}},
Value1={x:Type {enum:Status.Finished}},
Value2={x:Type {enum:Status.Canceled}}"
Хоча це безпечніше, це просто занадто багатослівно, щоб воно того вартувало. Я б також міг просто використовувати властивість моделі перегляду, якщо я збираюся це зробити. У будь-якому разі я вважаю, що перевірка часу проектування є цілком адекватною сценаріям, які я намагався до цього часу.
Ось код для MatchToVisibility
[ValueConversion(typeof(object), typeof(Visibility))]
public class MatchToVisibility : BaseValueConverter
{
[ConstructorArgument("ifTrue")]
public object IfTrue { get; set; }
[ConstructorArgument("ifFalse")]
public object IfFalse { get; set; }
[ConstructorArgument("value1")]
public object Value1 { get; set; }
[ConstructorArgument("value2")]
public object Value2 { get; set; }
[ConstructorArgument("value3")]
public object Value3 { get; set; }
[ConstructorArgument("value4")]
public object Value4 { get; set; }
[ConstructorArgument("value5")]
public object Value5 { get; set; }
public MatchToVisibility() { }
public MatchToVisibility(
object ifTrue, object ifFalse,
object value1, object value2 = null, object value3 = null,
object value4 = null, object value5 = null)
{
IfTrue = ifTrue;
IfFalse = ifFalse;
Value1 = value1;
Value2 = value2;
Value3 = value3;
Value4 = value4;
Value5 = value5;
}
public override object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var ifTrue = IfTrue.ToString().ToEnum<Visibility>();
var ifFalse = IfFalse.ToString().ToEnum<Visibility>();
var values = new[] { Value1, Value2, Value3, Value4, Value5 };
var valueStrings = values.Cast<string>();
bool isMatch;
if (Enum.IsDefined(value.GetType(), value))
{
var valueEnums = valueStrings.Select(vs => vs == null ? null : Enum.Parse(value.GetType(), vs));
isMatch = valueEnums.ToList().Contains(value);
}
else
isMatch = valueStrings.Contains(value.ToString());
return isMatch ? ifTrue : ifFalse;
}
}
Ось код для BaseValueConverter
// this is how the markup extension capability gets wired up
public abstract class BaseValueConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public abstract object Convert(
object value, Type targetType, object parameter, CultureInfo culture);
public virtual object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Ось метод розширення ToEnum
public static TEnum ToEnum<TEnum>(this string text)
{
return (TEnum)Enum.Parse(typeof(TEnum), text);
}
Оновлення 2
Оскільки я опублікував це запитання, я натрапив на проект із відкритим кодом, який використовує "Іллінг" для введення коду NotifyPropertyChanged для властивостей та залежних властивостей. Це робить реалізацію бачення Джоша Сміта про модель зору як "перетворювача цін на стероїди" абсолютним вітерцем. Ви можете просто скористатися "Властивості, що реалізуються автоматично", а ткацьке зробить все інше.
Приклад:
Якщо я введіть цей код:
public string GivenName { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
... ось що складається:
string givenNames;
public string GivenNames
{
get { return givenName; }
set
{
if (value != givenName)
{
givenNames = value;
OnPropertyChanged("GivenName");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
Це величезна економія кількості коду, який потрібно вводити, читати, прокручувати минуле тощо. Що ще важливіше, це позбавляє вас від необхідності з'ясувати, які ваші залежності. Ви можете додати нові "властивості отримує" на кшталт, FullName
не вимагаючи кропіткого просування по ланцюгу залежностей, щоб додавати RaisePropertyChanged()
дзвінки.
Як називається цей проект з відкритим кодом? Оригінальна версія називається "NotifyPropertyWeaver", але власник (Simon Potter) з тих пір створив платформу під назвою "Fody" для розміщення цілого ряду ткачів IL. Еквівалент NotifyPropertyWeaver на цій новій платформі називається PropertyChanged.Fody.
- Інструкції з налаштування Fody: http://code.google.com/p/fody/wiki/SampleUsage (замініть "Virtuosity" на " PropertiesChanged ")
- Сайт проекту PropertyChanged.Fody: http://code.google.com/p/propertychanged/
Якщо ви хочете скористатись NotifyPropertyWeaver (який трохи простіше встановити, але не обов’язково оновлюватиметься в майбутньому за виправленнями помилок), ось веб-сайт проекту: http://code.google.com/p/ notifypropertyweaver /
Так чи інакше, ці рішення ІЛ-ткача повністю змінюють обчислення в дискусії між моделлю перегляду стероїдів проти перетворювачів цінності.
MatchToVisibility
Здавалося, зручним способом включити деякі прості перемикачі режимів (у мене є один перегляд, зокрема, з тоною деталей, які можна включати та вимикати. У більшості випадків розділи представлення навіть позначені (з x:Name
), щоб відповідати режиму вони відповідають.) Мені насправді не прийшло в голову, що це "ділова логіка", але я дам вам твій коментар.
BooleanToVisibility
бере одне значення, пов’язане з видимістю (true / false) і переводить його в інше. Це виглядає як ідеальне використанняValueConverter
. З іншого боку,MatchToVisibility
це кодування ділової логіки вView
(які типи елементів повинні бути видимими). На мій погляд, цю логіку слід відсунути внизViewModel
, а то й далі, до того, що я називаюEditModel
. Те, що може бачити користувач, має бути тестуваним.