Обновить
Комментарии 41
Я может что то пропустил, но вы считаете что viewPayment.Text = payment.ToString(«N2»); есть MVVM?
Сначала подумал что мне показалось, открыл исходники… нет не показалось…
Если не трудно укажите, где именно находится данный код, похоже на ошибку.
Я извиняюсь, изначально видно ссылка была не на ту ветку (codebehind), не обратил внимания.
Ок, я тоже мог напутать при оформлении статьи.
public class BoolToVisibilityConverter
        : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (value as bool?) == true
                ? Visibility.Visible
                : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

Это не самый лучший подход. Тесты получаются не так выразительны как могли-бы быть. Гораздо лучше делать свойство типа Visibility во View Model
Согласен, что с точки зрения тестирования проверка свойства Visibility может быть более лаконична.

Однако по моему мнению свойство типа Visibility в View Model это перенос логики уровня представления на уровень модели представления.

В данном случае модель представления содержит поле, которое описывает ее состояние, например IsBusy. На уровне представления мы можем отобразить надпись «Работаю...», выключить все элементы управления или отобразить/скрыть элемент управления. Используя подход предложенный вами, нам пришлось бы реализовать 6 полей вместо одного, принимая решение о том, что показывать, а что нет за уровень представления.
нам пришлось бы реализовать 6 полей вместо одного, принимая решение о том, что показывать, а что нет за уровень представления.

И я абсолютно за такой подход. ViewModel это модель View. Они неразрывно связанны. Единственная разница заключается в том что уровень ViewModel очень просто тестировать, а вот View слой тестировать достаточно сложно. Ну и separation of concern -> логика View уходит во ViewModel, а вот стиль отображения остается во View. В моей практике — так получалось проще.
Получается, что каждый раз, когда нам понадобится значение нового типа которое напрямую коррелирует с базовым полем IsBusy мы будем вынуждены расширять ViewModel новыми полями. Но подход интересный, буду иметь его ввиду, пригодиться.
Присоединяюсь к заминусованному выше товарищу. Пару лет назад, когда я программировал на WPF, мы пришли к подходу «ViewModel — конвертер на стероидах». Это позволяет отказаться от уродливых конвертеров типа BoolToVisibilityConverter (которые сильно усложняются по сравнению с синтетическим примером, если они не «чистые» функции, а учитывают внешний контекст и имеют состояние). Косвенным следствием такого подхода является отказ от использования CallerMemberName.

Предположим, у вас в модели есть дата (DateTimeOffset), а в представлении она отражается в лейбле (string, ISO 8601), прогресс-баре (double, время, прошедшее с начала суток в процентах) и цвете фона (SolidColorBrush, тёмно-синий для ночи и светло-бежевый для дня).

У меня в модели представления было бы три разных свойства-геттера: string, double и Brush (типы представления), одно свойство- (или метод-) сеттер типа DateTimeOffset (тип фактических данных). Сеттер не бросал бы событие об изменении CallerMemberName, потому что его имя никак не относится к привязываемым свойствам — он бросает события соответствующих свойств, к которым привязаны label, progress bar, etc. Поля, которые лежат за этими четырьмя свойствами можно делать в зависимости от ситуаций: или одно поле (_dateTime) типа данных (и конвертировать в каждом из геттеров), или иметь все поля (_dateTimeString, _dateTimePercentage) представления и единожды рассчитывать их в сеттере, а в геттерах просто возвращать. Можно варьировать в зависимости от того, как чато будут чтения и записи.

ViewModel идёт в паре со своим представлением, при смене представления меняется и ViewModel. Общим интерфейсом ViewModel'а может служить интерфейс изменения, он оперирует доменными типами, и ничего не знает о способе фактического отображения этих типов.
Описанное выше совсем не ViewModel это тот же старый добрый code behind только вынесенный в внешний класс. Сама парадигма такова, что ViewModel не должен ничего знать о представлении, совсем не чего.
Не могли бы вы немного пояснить это мнение? View Model — это модель представления. Я так понял, что она должна содержать абстрактное описания представления без привязки к конкретному фреймворку.

В википедии написано, что это вариант PresentationModel см у Фаулера ( martinfowler.com/eaaDev/PresentationModel.html ), где в примере можно, например увидеть isComposerEnabled
Еще немножко подумал, скорее согласен с подходом с Converter, но мне не нравится его название BoolToVisibilityConverter — оно не отражает, что он конвертирует bool в пару Collapsed | Visible, а не Hidden | Visible — надо либо как-то параметризовывать, либо запихивать Collapsed в имя конвертера. Как вы на это cмотрите?
Конвертер является платформозависимым компонентом, в контексте платформы Windows Phone тип Visibility содержит два значения Collapsed и Visible. Нет необходимости загромождать название конвертера и вводить параметры.
Спасибо за объяснение, к тому же я увидел что в WPF существует такой же конвертер и тоже именно с Collapsed.
ViewModel это абстракция, минимально необходимый контракт для взаимодействия пользователя с остальным приложением. Модель представления не должна предоставлять избыточных данных. Если например событие имеет Тип, Дату и Описание, модель представления должна представлять потребителям, только эти данные.
А представление должно решать как эти данные использовать, показать как текст в консоли, показать в виде графика или любом другом виде.
ViewModel это абстракция, минимально необходимый контракт для взаимодействия пользователя с остальным приложением.

Нет. ViewModel — это не абстракция. Это просто такая штука для DataBinding'а. Переводится «ViewModel» как модель представления, а не представление модели. Можно, конечно, обмазаться IViewModel, но это всё premature abstraction.

А представление должно решать как эти данные использовать

Что значит «решать»? Иметь какой-то код, который «решает»? То есть иметь, по-сути, code behind?

Идею «конвертер на стероидах» предложил не я, а Джош Смит.
Вовсе они не связаны. ViewModel не должна ничего знать о View, и уж тем более знать о её устройстве и о том, какие элементы с какими свойствами та содержит.

Представьте, что у вас есть элемент во View, который становится Collapsed если состояние IsBusy, и вместо использования конвертера вы создали свойство типа Visibility во ViewModel. Завтра вам сказали, что вместо скрытия этого элемента лучше менять его цвет на красный. В случае использования bool свойства вы просто пишете новый конвертер, никак не меняя ViewModel. В вашем подходе же придется лезть во ViewModel и создавать новое свойство типа Brush, а старое свойство типа Visibility удалять.

И это в случае изменений во View, а если к вашей ViewModel подключаются несколько представлений, которые используют разные подходы к обработке свойства IsBusy? Например, в универсальном Windows 8 / Windows Phone приложении, где в Windows 8 вы скрываете элемент, а в WP — изменяете его цвет.

Ну и в целом, руководствоваться выразительностью тестов при принятии таких решений — сомнительная практика, на мой взгляд.
Можно пойти дальше.

UI-related свойства у ViewModel автоматически дают нам следующие «преимущества»:
* ViewModel получается платформозависимая (без хаков не получится шарить её между SL, WP8, W8 и WP7
* Обламываемся с нормальным запуском юнит-тестов.
Я возможно вызову на себя праведный гнев сообщества, но: DelegateCommand это скорее зло чем добро.

Идея использования комманд растет из responsibility segregation, т.е. когда мне надо делать расчет платежей я оформляю это в отдельный класс, который реализует помимо алгоритма еще и определение того, когда это действие применимо, уведомления когда действие стало применимо и т.д. И затем использую его либо глобально в приложении, либо создавая инстансы в конкретных ViewModel. Зачем? Затем что, как правило, команды повторно используются и требуют бОльшего покрытия чем ViewModel и имеют четкие зависимости.
По моему мнению, передача команды из модели на прямую в предсатвление намного большее зло, чем использование комманд в качестве оберток.
Проблема MVVM которая меня больше всего заботит: есть например датамодель на EF. Фактически для скучных enterprise приложений ViewModel на 70% ее брат-близнец, только там свойства немножко другие штуки возвращают и реализуют NotifyPropertyChanged. По мне это адский оверхед, как для написания (кто-то будет сидеть, писать туповатые viewmodel-и, отрабатывать change requests, раздувать кодобазу без надобности и ненавидеть свою жизнь), так и для исполнения (чтобы показать список объектов надо создать их близнецов в памяти). Есть ли какие-то рекомендации в этом направлении? LightSwitch не предлагать :-)
Да проблема более чем актуальна, когда я знаю, что для определенной сущности понадобится «расширенный» близнец, в ход пускаю авто генерацию кода, часть кода которая реализует обертки для общих членов. А всю уникальную функциональность реализую в рядом лежащем partial классе
Не сочтите за критику, статья действительно интересная и полезная… для общего образования.
Я могу понять желание не использовать Prism для WP. Но MVVM Light достаточно легок и прост в освоении, чтобы не заморачиваться написанием своего велосипеда.
Дело наверное в том, что я в общем то люблю велосипедостроительство, с целью достижения понимания происходящего, но обычно к этому моменту свой велосипед уже не хуже готовых, а местами даже и лучше :)
У меня вопрос возник по ходу чтения. В MainPageViewModel метод Calculate делает проверки и выводит сообщение пользователю.
private void Calculate()
		{
			IsCalculated = false;

			if (!decimal.TryParse(Amount, out _calculatedAmount))
			{
				MessageBox.Show("Сумма должна быть числом");
				return;
			}
...............



Было бы не плохо сразу фокусироваться на том элементе, который не прошел проверку. И не выводить сообщение, а подписать нужный элемент. Каким образом Вы бы рекомендовали это сделать?
Это самая простая и наивная реализация валидации. Валидация будет более подробно рассмотрена в слудующих частях статьи. Если в двух словах, то валидация должна осуществляться в момент ввода информации пользователем и сразу отображать ошибку если она есть. Сделать это можно разными способами, я предпочитаю реализацию с помощью присоеденяемых свойств или с помошью создания собственных элементов управления.
Не увидел здесь ничего особенного. Как вы с таким паттерном обработаете события OnNavigatedTo и пр.? Очень похоже на обычный Binding.
Прошу набраться терпения, я решил идти от простого к сложному ну или более простому, это как посмотреть. А обработка событий навигации будет рассмотрена в следующей части.
Хорошо. Тема MVVM для меня крайне болезнена. Я считаю, что использование MVVM в мобильных приложениях не всегда оправдано. Более того, даже в настольных приложениях готов подискутировать на эту тему. То, что показано выше нельзя называть MVVM. Это же только DataContext…
Если честно немного теряюсь от «Очень похоже на обычный Binding» и «Это же только DataContext». Думаю стоит начать с вопроса, а что такое MVVM для вас?
Добрый день! Спасибо за статью!

Хотелось бы также поделиться опытом и, может быть, посоветовать что-то полезное…
На мой взгляд, применение лямбда-выражений всё же даёт больше гибкости, чем только CallerMemberName-аттрибут.
Также их можно использовать совместно для взаимодополнения друг друга.
Ещё ваша реализация команд, насколько я понял, в некоторых сценариях может приводить к утечкам памяти из-за подписки контрола на событие CanExecuteChanged. Касательно же WinPhone вообще можно реализовать что-то похожее на RoutedCommands из WPF.

Я помню, что вы читали статью WinPhone: пути к совершенству, в которой описаны некоторые фишечки для разработчиков. Очень рекомендую скачать пример из неё… Там как раз используется свой собственный MVVM-фреймворк.

И, конечно же, советую прочесть статью с примером MVVM: новый взгляд. Возможно, кому-то покажутся интересными довольно оригинальные апгрейды для MVVM.

Все наработки из статей применены на реальных проектах, например, Easel (WinPhone) или Poet (WPF), поэтому можно убедиться, что всё работает отлично.
Спасибо за комментарий, все описанные статьи я читал. Что касается вопросов, то большая часть их будет раскрыта в следующих частях статьи. Очень надеюсь завершить работу над ними в ближайшее время.
Использование лямбда выражений так же будет описано, хотя я от их использования отошел, по причинам производительности.
Насчёт последнего я бы поспорил :) Мне на практике ещё ни разу не встретился случай, где реально ощущалось бы падение производительности из-за использования лямбд. Конечно, я могу представить гипотетическую ситуацию, например, привязка к значению таймера, где свойство обновляется каждые несколько миллисекунд, но вместе с тем никто не отменяет другие способы для нотификации…
По-моему это обычный MVVM, только со своей RelayCommand (как раз листал книгу), а хочется большего.
Я пока только присматриваюсь к фреймворкам в WP8, до этого на as3 проектах использовал puremvc и robotlegs. После них очень хочется автоматический инжектор, сообщения через ивентбас, медиаторы тоже довольно удобны.

MVVM Light Toolkit шаг в правильную сторону, но мне кажется он слегка неказист.
Самый крутой MVVM — это RxUI (ReactiveUI). GitHub, сайт. Очень рекомендую, особенно для тех, кто уважает Rx. Работает под XAML, Silverlight, WP8, WinRT, iOS, Android (Xamarin).
немного смутил их сайт, проект стабильно развивается?
Сайт, видимо, давно не обновляли. Видюшка старая лежит. Но проект очень даже развивается. Его основные контрибуторы — это Paul Betts и Phil Haack. Эти чуваки работали в MS, а сейчас работают в GitHub. На RxUI в частности сделан GitHub for Windows.
Похоже, 2-я часть не за горами ))

Тут речь заходила о MVVM Light, но, почему-то, никто не сказал о Caliburn.Micro. Очень мощный инструмент.
К сожалению реакция на первую часть отбила всякое желание продолжать
А вот зря вы так.

А какая реакция вас расстроила?
В комментариях вся адекватная дисскусия сводится к тому, что вы какие-то моменты будете раскрывать в следующих статьях. А следующей статьи как раз и нет ))

Или слив кармы тому виной? Так это дело во-первых наживное, во-вторых не самоцель.

По идее тему можно развивать, тем более, что «свой велосипед» уже написан. А при обсуждении, как правило, можно подчерпнуть и узнать что-то новое или переосмыслть старое. Так что пишите ))
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.