Pull to refresh

Comments 53

Давно читаю хабр, но зарегаться не решался. Пишу первый коммент так что сильно не бейте. Большое спасибо автору за статью. Однако основным из преимуществ использования архитектурного подхода (любого, не только MVVM) является возможность модульного тестирования. Например конструктор VM во втором примере — штука как минимум сложно тестируемая, если вообще тестируемая. Мне кажется стоило бы немого разгрузить этот конструктор. А также просмотреть остальные примеры на возможность тестирования. Если есть какой-то способ тестировать такие штуки, с удовольствием о нём узнаю. Ещё спасибо за интересный материал.
Совершенно верно: разделение ответственности в большой степени способствует повышению тестируемости. Т.е. когда мы выдираем модель из behind code обработчиков событий и собираем в одном месте — тестировать ее значительно легче. Вот, собственно, модель обычно и покрывают юнит-тестами. Например: в первом примере необходимо убедиться, а действительно ли статическая функция складывает два числа корректно…
Касательно покрытия тестами VM — этим обычно… ну, во всяком случае я, — не занимаются. Текст VM совершенно плоский. И вызовы делегатов из VM — тоже вполне планарны. А вот то, что они вызывают в модели — покрывать тестами действительно стоит.
По поводу модели — согласен. Готов согласится с тем, что тестировать делегаты не всегда нужно, но есть одна вещь которая требует дополнительных комментариев. Проверка валидности ввода должна быть покрыта тестами. В вашем случае это вроде как не так очевидно, но если мы сменим числа, например, на email то покрывать — обязательно. Или такая логика уже выводится в уровень модели и считается бизнес логикой?

ЗЫ: Я бы все таки покрыл тестами вызов правильных методов в модели из VM.
Проверку валидности ввода email можно организовать еще на этапе вьюшки, — есть data annotations, ErrorInfo и т.п. — такое проверять не надо. Если проверка комплексная, то выносим ее в модель, в какую-нибудь IsEntityValid, и там проверяем.
Просто есть идеальное видение, с полным покрытием кода, и есть реальный мир, где нередко программисты вообще не пишут юнит-тесты. Растеряв силы или заскучав на проверке VM, они могут и не добраться до модели, а так — пусть хоть модель покроют.

А что будет если Вы пишете веб приложение и юзер отправит в Ваш контроллер данные не через валидирующую форму а напрямую в контроллер, через url get/post запрос. Тогда это может сломать Вашу модель или не дай бог базу данных.

Ну, во-первых в вебе валидировать надо (и) на серверной стороне, конечно. Во-вторых в комментах про тестирование, а у вас мысль уже куда-то в сторону пошла
В моём понимании любой шаблон проектирования предназначен для решения определённой категории задач. Часто, читая статью про какой-либо шаблон проектирования, я вижу пример реализации этого шаблона для решения какой-то элементарнейшей задачи. При этом я вижу кучу непонятных абстракций и усложнений кода, реализация которых, зачастую, сложнее решения этой самой элементарнейшей задачи. Мне говорят, как надо делать, как нельзя делать, но не объясняют зачем вообще все это делать. Вот статья — классический пример. Аргументация такая: посмотрите на серверную стойку с лапшой из проводов, и посмотрите на другую стойку, где красота и порядок. Так вот пример с серверной стойкой мне, очевидно, понятен. И у меня не возникает вопросов, зачем использовать хомутики и жгутики. Ну а далее что? Давайте напишем даже не калькулятор, а просто сумматор 2-х чисел и будем MVVM-ить. Из статьи я понял как нужно соединять порты, но не понял для чего мне MVVM. Так что, если Вы беретесь объяснять за шаблоны проектирования, приведите пример, на котором применение этого самого шаблона будет аргументировано и будет видна его польза. Ну да ладно, всё-таки это часть 1, возможно, дальше будет больше и понятней.

Теперь вопрос по существу (возможно на вопрос «зачем» из 1-й части моего комментария Вы ответите здесь). Предположим есть UserControl, он состоит из 2-х частей View (Xaml) и Codebehind (cs). Во View значит я верстаю саму форму, кладу TextBox. В Codebehind я создаю такие же команды и свойства, как это делаете Вы в своей ViewModel, и во View точно также создаю привязки к этим свойствам и командам. Т.е. Codebehind UserControl'а это Ваш ViewModel. Так для чего же мне уже имея View и Codebehind создавать ещё один файл и выносить туда какой-либо код?
Два вопроса вижу:
1. Для чего MVVM? Отвечаю: для того, чтобы у вас была самодостаточная модель, сгруппированная в одном месте. Чтобы была отделена от инфраструктурного кода. Если, условно, инфраструктурный код — это 70%(и больше) от всего кода, а бизнес-логика — 30% — то очень хорошо эту модель иметь в одном месте, а не размазанную по обработчикам событий кучи контролов. Тогда модель поддается тестированию, изменению функционала, она компактнее, понятнее и т.д. Всегда можно взять модель и написать совершенно другой инфраструктурный код, для другой платформы, другой библиотеки и т.д.
2. Почему нельзя использовать кодбехайнд в качестве VM?
Отвечаю: никто не запрещает вам использовать кодбехайнд в качестве DataContext самого себя, но только в случае, если у вас на один View одна VM. Но часто бывает, что одна VM используется для многих View. Это удобно делать, чтобы не городить огромную вьюшку, а разбить ее на ряд вьюшек поменьше — их тогда и перекомпоновать будет проще. В таком случае нелогично использовать один контрол в качестве DataContext других контролов.
  1. В любом приложении есть модель и её представление. Шаблонов проектирования, в которых модель связывается с представлением много: MVC, MVP, MVVM. То, что модель должна быть отделена от инфраструктурного кода — это как раз понятно. Вопрос больше про то, для чего использовать сам шаблон MVVM. Что его использование даёт по сравнению с тем, что я напишу контроллер, через который вид взаимодействует с моделью. Или я буду просто в Codebehind взаимодействовать с моделью. Вопрос не о M, а о VM из этого шаблона. Я на самом деле знаю ответ на этот вопрос. Просто, читая статью о шаблоне проектирования, хотел бы видеть в ней объяснение. Возможно новички прочитают и не поймут для чего всё это нужно.


  2. Самому UserControl'у необязательно устанавливать DataContext'ом ссылку на себя, для того, чтобы использовать привязки внутри. Можно, например, задать имя контролу и в привязках ссылаться на контрол по этому имени:
    <UserControl x:Name="CalcView">   
    <TextBox Text="{Binding Path=Value1, ElementName=CalcView}" />
    </UserControl>

    Использовать один контрол в качестве DataContext другого нелогично, я с Вами согласен. Вот Вы пишете, что часто бывает одна большая VM которая используется для многих View, но городить один большой View не хотите. А почему тогда получается 1 большая VM, котороая во многих View участвует? Почему её не нужно разбивать на более мелкие части, как это делаете с View? Или это что-то вроде медиатора, который устанавливает связь между различными частями модели в 1-м месте, но используется во многих местах? Т.е. другими словами мне пока не понятен смысл выносить код взаимодействия с моделью в отдельный класс, когда есть Codebehind у UserControl. В Вашем примере получается, что Codebehind во всех представлениях чаще всего останется пустым.


1. Так вопрос в чем преимущество MVVM над MVP или MVC?
Ну, вот конкретно в WPF в том, что MVVM «аппаратно» поддерживается WPF. View понимает и INotifyPropertyChange, и Observable и др. — ее не надо руками обновлять через презентер. Интерфейс вьюшки городить не надо. Биндинг в конце-концов. Мне MVP в WinForms'ах не нравился тем, что уж очень там много руками делать приходилось.
2. Не чаще всего code behind останется пустым, а всегда будет оставаться пустым, если используется чистый MVVM.
Одна VM для нескольких вьюшек используется, не столько для того, чтобы расшарить контекст, а скорее для того, чтобы разбить вью на физические части, а потом скомпоновать в общей вьюшке. Просто View в XAMLе могут быть достаточно… громоздкими, аляповатыми, знаете, со всеми этими <Grid.RowDefinitions>… да и вообще, как любой XMLобразный код.
В вашем примере с привязками вы биндитесь к DependencyProperty, в случае же использования DataContext подойдут регулярные свойства. Они по объему кода гораздо скромнее
Никто не заставляет Вас биндиться к Dependency свойствам. Можно и в UserControl создавать обычные CLR свойства и реализовывать INotifyPropertyChanged (хотя это было бы странно, учитывая что наследуемся от DependencyObject). Вы не подумайте, что я придраться хочу и доказать, что MVVM плохо. Я задаю Вам простые вопросы с целью понять, для чего мне MVVM, почему я не могу считать Codebehind UserControl'а VM'ом. Название Вашей статьи предполагает, что Вы в полной мере объясните цель и причины использования MVVM в WPF. Вот я и хочу укрепить свои знания и получить еще одно видение.
Хм, интересно) И при невыставленном DataContext работает такое использование регулярных свойств в качестве соурса биндинга.
На мой взгляд то, где будет VM физически — это незначимая деталь реализации. Но можно дать такой ответ:
В вашей конструкции при биндинге надо всякий раз указывать имя элемента управления, и второе: неясно, что делать, в случае нескольких View на один VM.
Не понял первую фразу. Что значит “чистый mvvm”? И где же размещать логику вьюхи, не зависящую от модели, как не в code behind?
Логика вьюхи? Если имеется ввиду всякие IsVisible, IsEnabled — то в VM.
Если всякие украшательства, типа анимации — то в XAMLe. Если в XAML они не лезут, то тогда в код бехайнд.
Чистый MVVM значит, что можно миксовать MVVM и MVP. Хотя можно с помощью windows.interactivity попытаться оставаться в рамках MVVM, но если программа сильно «вьюхоцентричная», то можно создать интерфейс вьюшки и вызывать его.
Меня вот эта фраза смутила:
code behind… всегда будет оставаться пустым, если используется чистый MVVM

Использование code behind никак не противоречит MVVM, если он используется для вьюшной логики, т.е. не зависящей от модели. Например, если видимость зависит от состояния модели (наличие заказов заказов у клиента), то да, оно будет во вью-модели как IsVisible (или вообще через конвертер вычисляться). А если видимость зависит от размера окна, то во вью-модели ей делать нечего.
А из вашей же фразы можно сделать вывод, что если code behind используется, то MVVM «грязный». И да, некоторые «умельцы», рассуждая так, доходят до того, что прокидывают ссылку на вью во вью-модель, пилят логику с ней там и хвастаются, что у них code-behind пустой :).
Так вот как раз у этих «умельцев», несмотря на пустой код-бехайнд, — MVVM не чистый, а разбавленный )
А так — вполне согласен, что код-бехайнд может содержать код, если его наличие/отсутствие никак не сказывается на модели.
Да, вопрос именно в чем преимущество. MVVM позволяет нам писать код в декларативном виде (Xaml), используя событийную модель. Т.е. INotifyPropertyChanged, Binding, Multibinding, ICommand, Trigger и вся прочая ерунда, поддерживаемая самой платформой напрямую. Когда на собеседовании я спрашиваю у кандидата для чего MVVM, мне бы хотелось услышать объяснение, увидеть понимание сущности самого шаблона, способности объяснить его отличие от MVC, к примеру, а не просто потому что нам так сказали. Какой смысл пользоваться чем-то, когда не понимаешь смысла, не видишь отличий от других подходов. Рассказывая кому-либо о шаблоне проектирования, я бы начинал от классификации (структурный, поведенческий, порождающий), дальше цель шаблона и пример решаемых с его помощью задач, причина, по которой следует использовать тот или иной подход. Голословного «в WPF так принято» не достаточно.

Один из плюсов использования связки mvvm и wpf это использование шаблонов. Такой подход позволяет заменить почти всех наследников UserControl на DataTemplate.
Дополнительно упрощается поддержка вложенных vm.

Согласен с предыдущим автором — вы смогли понятно расписать как использовать шаблон MVVM, но зачем его использовать непонятно, и тем более, чем он лучше?

Мое мнение, что лучше его вообще не использовать, кроме двух случаев.
1 Это кросплатформенная программа тогда в идеале у каждой платформы меняется только View.
2 У вас есть офигенный дизайнер который может ваять формы в Blend, а программеры уже просто подключают свои поля куда надо. (но для большинства это наверно из области фантастики)

Больше я не вижу, чем MVVM лучше. Куча геморроя, с текстовыми значениями, без перехода по ссылкам, с диким пробросом данных в кривые контролы и попапы.
А уж если кто-то «мудрый» решил подключить Caliburn…
Предположим есть UserControl, он состоит из 2-х частей View (Xaml) и Codebehind (cs). Во View значит я верстаю саму форму, кладу TextBox. В Codebehind я создаю такие же команды и свойства, как это делаете Вы в своей ViewModel, и во View точно также создаю привязки к этим свойствам и командам. Т.е. Codebehind UserControl'а это Ваш ViewModel. Так для чего же мне уже имея View и Codebehind создавать ещё один файл и выносить туда какой-либо код?

Допустим, я сделел UserControl для отрезка дат (с даты — до даты, два DatePicker`а). И для своей модели сделал им привязку к, например, датам поиска вылета самолёта. Тут следует начать с того, что это нарушение принципа единственной обязанности, что скоро меня приведёт к печальным последствиям.

Тут меня попросили сделать такой же контрол, но для дат выезда поездов. Что мне делать? Наследоваться? Копипастить? Я бы, всё-таки, хотел иметь возможность просто указать, откуда мне брать данные для начала и конца отрезка дат. Вот тут мне может помочь VM и не может помочь CodeBehind.
Я тут не очень понял суть проблемы. В моём понимании Вы создаете элемент управления DateRangeSelector, наследуясь от UserControl, в нём создаёте 2 DependencyProperty StartDate, EndDate. В Xaml кладёте 2 DatePicker и связываете их с этими свойствами. Во всех местах, где Вам нужно выбирать интервал из дат Вы кладёте этот элемент управления и связываете его свойства StartDate и EndDate со свойствами VM.
Проблема в том, что вы хотите делать это в Codebehind. Codebehind — это часть самого элемента и вы будете тащить её всюду с ним.
Я всё ещё не вижу проблемы. Вы говорите очевидные вещи, Codebehind — часть самого элемента, кто же спорит. Что означает, я буду её тащить всюду? Вы привели пример задачи, выбор интервала даты. Я Вам объяснил как её решить, и MVVM к этой задаче вообще не имеет отношения. Вот даже в этой задаче, скажем конечная дата не может быть меньше начальной. Вы это условие обеспечивать где собираетесь, Codebehind контрола выбора дат, или где?
В смысле не имеет отношения? Привязка контрола к данным не имеет отношения? Выше вы писали
В Codebehind я создаю такие же команды и свойства, как это делаете Вы в своей ViewModel, и во View точно также создаю привязки к этим свойствам и командам.
Если вы привяжетесь к одной какой-то модели, то эта привязка станет частью контрола, и использвоать в другом проекте вы его не сможете, не потащив за собой модель этого. Это очевидные вещи, да. Но вы почему-то о них спрашиваете.
Я задавал вопрос автору статьи про Codebehind и хотел выяснить разницу между VM и Codebehind. Вы же привели в пример конкретную задачу. Так вот элемент выбора интервала дат — это самостоятельный элемент управления, точно также как и DatePicker — элемент управления для выбора 1-й даты и они к MVVM вот вообще никакого отношения не имеют чуть более чем на 100%.
Если вы привяжетесь к одной какой-то модели, то эта привязка станет частью контрола, и использвоать в другом проекте вы его не сможете, не потащив за собой модель этого. Это очевидные вещи, да. Но вы почему-то о них спрашиваете.

Здесь я Вас уже перестал понимать, в контексте предложенной Вами задачи.
Я задавал вопрос автору статьи про Codebehind и хотел выяснить разницу между VM и Codebehind. Вы же привели в пример конкретную задачу.
Я привёл конкретный пример вашей же задачи для простоты восприятия.

Всё началось с:
Предположим есть UserControl, он состоит из 2-х частей View (Xaml) и Codebehind (cs). Во View значит я верстаю саму форму, кладу TextBox.
Любой UserControl должен быть самостоятельным элементом управления.

Затем
В Codebehind я создаю такие же команды и свойства, как это делаете Вы в своей ViewModel, и во View точно также создаю привязки к этим свойствам и командам. Т.е. Codebehind UserControl'а это Ваш ViewModel.

Некорректность утверждения о том, что «Codebehind UserControl'а это Ваш ViewModel» я вам в этой ветке и освещаю. Разница в том, что Codebehind — это часть самого элемента View, а VM же подсказывает View, откуда ему брать данные и какие команды использовать. VM может содержать ссылки на модель, Codebehind — нет. VM могут быть разные, предоставляющие разные данные и разные команды, Codebehind для контрола всегда один и тот же.

В итоге, могу предположить, что вы что-то своё понимаете под UserControl.
Вы привели задачу для контрола выбора интервала дат в качестве примера как использовать MVVM и сейчас за уши притягиваете простоту восприятия. Для озвученной задачи, как я уже сказал выше, нужен Control, это не View модели, представляющей интервал дат. Так зачем на изначально некорректном примере доказывать правильность использования MVVM.
Вобщем, вступать в дальнейший спор (или продолжать) я не хочу.

Я по Вашим ответам понял, какие преимущества дает MVVM. Большое спасибо!

MVVM — это прекрасный и очень естественный (хотя на первый взгляд, для привыкших к другому, это и не очевидно) подход. Но проблема в прибитости WPF гвоздями к Windows: если хочешь, чтобы приложение было кроссплатформенным, приходится использовать WinForms, а там с MVVM туго.
Думаю, появиться что-нибудь, типа UWP. Только будет не Universal Windows Platform, a просто — Universal Platform
Что же в WinForms кросплатформенного? Наличие кое-какого порта под Mono? Xaml, благодаря Xamarin и разным проектам, вроде Avalonia, выглядят более кросплатформенно.
есть такая вещь, как Xamarin (forms, android, ios). Там не привязано к винде, кроссплатформенно и MVVM

Автор, а вы ничего не путаете? Логика в модели? И логика работы с DAL тоже в модели? По-моему модель это именно модель данных, набор свойств с минимумом логики, необходимой для этих свойств. В вашем примере со сложением логике сложения, конечно, самое место в модели, но если логика чуть сложнее, то в модели ей не место.
Пример выбран неудачно, мввм тут из пушки по воробьям .

Где же быть логике, как не в модели? О_0 Бизнес-логика она в модели. DAL я трактую, как бедные репозитории, просто доступ к данным, но логика- дожна быть отдельно.
Конечно сложение чисел с МВВМ — это оверкил, но позволяет сосредоточится на теории, не загромождая все реализацией. К тому же это не последний пример
Модель, в принципе, может не хранить никакого состояния. Т.е. она может вполне быть реализована статическим методом статического класса.
В широком понимании модели, да. Но, как правило, под моделью понимается доменная модель.
если у нас во View есть три текстовых поля, или три места, которые должны вводить/выводить данные — следовательно в VM (своего рода подложке) должны быть минимум три свойства, которые эти данные принимают/предоставляют.
Это не так. В VM у нас может быть один объект и это могут быть его свойства.
Синее — это VM, к которой эти три зеленых точки железно прибиты (прибиндены)
Биндинги в WPF — это нечто прямо противоположное «железному прибитию». По умолчанию, они прописываются простыми строками и им плевать на то, есть ли свойство или нет — просто всё перестанет работать.
public int Number1
Пожалуйста, не называйте так свойства. И поля. И вообще ничего. В принципе, рекомендую уделить внимание качеству кода.
Затем — операция добавление некоторого числа в коллекцию — это обязанность модели. VM не может залезать во внутренность модели и самостоятельно добавлять в коллекцию модели число, она обязана просить сделать это саму модель. В противном случае это будет нарушение принципа инкапсуляции.
Тут немножко странные вещи описаны, начиная с того, что инкапсуляция — это не принцип, а механизм и нарушить её хоть и можно, но не таким способом.
Для того, чтобы создать связь кнопки и VM, необходимо использовать DelegateCommand.
Я надеюсь, что имелся в виду ICommand.
//таким нехитрым способом мы пробрасываем изменившиеся свойства модели во View
_model.PropertyChanged += (s, e) => { RaisePropertyChanged(e.PropertyName); };
Увы, нет. Мы не «пробрасываем свойства», мы используем один из плохих приёмов программирования — программирование по совпадению. Мы вызываем событие у одного объекта, используя название изменившегося свойства другого. Да, в этом примере всегда будет передаваться Sum, и по совпадению (!) такое свойство есть у VM и оно даже (по совпадению!) отображает те данные что нам нужны. Изменить название свойства или его логику — всё поломается. Так делать нельзя.
//проверка на валидность ввода — обязанность VM
Смелое утверждение. Я вот считаю, что проверка на валидность того, что вводит пользователь — обязанность View. Если же проверка на валидность — обязанность VM, то как и где он будет уведомлять пользователя в случае ошибки?
Это не так. В VM у нас может быть один объект и это могут быть его свойства

Биндинги в WPF — это нечто прямо противоположное «железному прибитию»
Не придирайтесь. Я имею ввиду, что сколько во вью открыто «слотов» для биндинга, столько должно ему предоставлять VM. А под словом «железно» имеется ввиду, что VM это не нечто, как модель — абстрактное в вакууме, а такое нечто, что зависит от вью, имеет открытых «слотов» столько, чтобы удовлетворить свою View
… инкапсуляция — это не принцип, а механизм

Инкапсуляция — это как раз принцип. А уж как именно вы инкапсулируете — это механизм.
Увы, нет. Мы не «пробрасываем свойства», мы используем один из плохих приёмов программирования — программирование по совпадению…
В статье акцент сделан в первую очередь на MVVM, а это удобная конструкция, избавляющая от необходимости прописывать каждое пробрасываемое свойство из модели. В реальных проектах такое не прокатывает и чревато — это понятно.
… считаю, что проверка на валидность того, что вводит пользователь — обязанность View..
Имелось ввиду валидация в конкретном случае. Вообще же — валидация сначала во View (уже даже самим типом контрола — используя RadioButton сложно ввести невалидное значение). Если нельзя во вью, то в VM. Если нельзя в VM, то задействуем Модель.
Не придирайтесь.
Я не придираюсь к вам, я освещаю некоторые моменты, которые, неосведомлённый читатель поймёт неверно и сделает неверные выводы.
модель — абстрактное в вакууме
VM [...] такое нечто, что зависит от вью
Эти две фразы неверны. VM не зависит от View, а наоборот. Это позволяет использовать VM с разными View.

Модель — это вполне конкретная вещь.
Инкапсуляция — это как раз принцип.
Ну я не знаю, хотя бы в википедии посмотрите. Возможно, вы путаете её с принципом сокрытия данных.
В статье акцент сделан в первую очередь на MVVM, а это удобная конструкция, избавляющая от необходимости прописывать каждое пробрасываемое свойство из модели.
Ну так в примере вы именно тем и занимаетесь, что прописываете свойство модели в свойстве VM (это я про сумму). Если будет больше полей — будете больше пропихивать свойств.
Если нельзя во вью, то в VM. Если нельзя в VM, то задействуем Модель.
Вот такие «где получится — там и будет» рассуждения и приводят к СКС в стиле картинки №1.
Эти две фразы неверны. VM не зависит от View, а наоборот. Это позволяет использовать VM с разными View.

Я использую две методики написания программ в MVVM: 1.Model first 2. View first
В обоих VM разрабатывается после View.
Про модель в вакууме — значит, что она не зависит от VM и View, и ничего про них не знает. У нею своя атмосфера.
Я использую две методики написания программ в MVVM: 1.Model first 2. View first
В обоих VM разрабатывается после View.
Можно ссылки на описание этих методик?
Смелое утверждение. Я вот считаю, что проверка на валидность того, что вводит пользователь — обязанность View. Если же проверка на валидность — обязанность VM, то как и где он будет уведомлять пользователя в случае ошибки?

А использование интерфейса IDataErrorInfo для VM и указание соответствующих свойств в биндинге во View куда можно отнести? В общем-то получается для валидации задействовано и то, и другое. Возможно я не прав, но разве View должна знать какие данные верны, а какие нет? Она должна лишь уведомлять о том, что данные не верны (красное поле ввода, подсказка что не так).
Я для себя делал так (уверен что найдется масса ошибок, но я пока ещё учусь):
Есть правило валидации:


public class ValidationRule
{
    public ValidationRule(string columnName, Func<string> rule)
    {
        _columnName = columnName;
        _rule = rule;
    }
    private string _columnName;
    private readonly Func<string> _rule;
    public string ColumnName {
        get {
            return _columnName;
        }
    }
    public Func<string> Rule {
        get {
            return _rule;
        }
    }
}

И соответственно есть список правил. Их мы задаем в VM. И там же указываем что делать при изменении корректности всего списка правил (например блокируем/разблокируем кнопку сохранения). Через интерфейс IDataErrorInfo поля ввода во View получают информацию о корректности данных и в случае ошибки отображают заданную подсказку.


public class ValidationObject : IDataErrorInfo
{
    #region IDataErrorInfo
    private List<ValidationRule> _validationRules = new List<ValidationRule>();
    private Dictionary<string, bool> _propertiesCorrectly = new Dictionary<string, bool>();
    private Action<bool> _changingCorrect;
    public void AddValidationRule(ValidationRule rule)
    {
        if (_validationRules.Where(r => r.ColumnName == rule.ColumnName).Count() != 0)
            return;
        _validationRules.Add(rule);
        _propertiesCorrectly.Add(rule.ColumnName, false);
    }
    public void SetActionOnChangingCorrect(Action<bool> act)
    {
        _changingCorrect = act;
    }
    string IDataErrorInfo.this[string columnName]
    {
        get
        {
            ValidationRule rule = _validationRules.FirstOrDefault(x => x.ColumnName == columnName);
            string result = null;
            if (rule != null)
            {
                result = rule.Rule();
                _propertiesCorrectly[rule.ColumnName] = (result == null) ? true : false;
                _changingCorrect?.Invoke(!_propertiesCorrectly.Values.Contains(false));
            }
            return result;
        }
    }
    string IDataErrorInfo.Error
    {
        get
        {
            throw new NotImplementedException();
        }
    }
    #endregion
}
Феникс, не генерируйте такие портянки)
Это относится к коду, комментарию или всему вместе? :)
Возможно я не прав, но разве View должна знать какие данные верны, а какие нет?
А почему, собственно, нет? Уточню, что View должна знать о верности данных в контексте взаимодействия с ней пользователя. То есть, например, у нас может быть поле Price, которое в форме ввода не может быть больше 1000, но при этом сами данные — могут быть, если мы их получили в результате каких-то накруток налогов, например, либо получив автоматически из третьего источника. А в другой форме, по каким-то своим причинам, мы ограничим стоимость уже 100.

При всём при этом, у нас есть возможность ограничить через DataAnnotations сами данные, например, указав, что цена не может быть отрицательной никогда и ни за что.

Как дополнительный бонус, при просмотре XAML наглядно видно, какие поля какими ограничениями обладают.
Есть правило валидации:
Вы, возможно по совпадению, употребляете название библиотечного класса ValidationRule, не наследуясь от него и не используя его во View, где он и должен быть. Почитайте статью, там прописано и как использовать этот класс, и как валидировать во View.

Ещё раз подчеркну, что во View валидировать необязательно, если только нет каких-то специфических требований, связанных только с конкретным View.

Кстати, в целом, система валидации в WPF довольно кривая. Объединять ошибки из ValidationRule`s и DataAnnotations — это то ещё веселье. А если использовать виверы (тот же Validar) — то становится ещё веселее.
Может быть мой вопрос будет освещён во второй части статьи, но задам его сейчас.

Допустим из одного окна (родительского), по нажатию кнопки показывается другое окно (дочернее).
Где, согласно принципам MVVM, должны быть инстранцированы View и ViewModel дочернего окна?
допустимо ли делать это в code behind родительского окна?
Я это делаю в VM родительского окна:
ShowSomeWindowCommand = new DelegateCommand<Window>(own =>
            {               
            //someVar и anotherVar используются в конструкторе SomeWindow, чтобы создать себе VM
                var dlg = new SomeWindow(someVar, anotherVar) {Owner = own};
                var result = dlg.ShowDialog();
                ...
            });

В качестве CommandParameter передаю родительское window.

В Caliburn Micro есть IWindowManager.ShowDialog(object viewModel). А в Prism это решалось event+behavior. Но однозначно не стоит создавать Window во ViewModel.

Если Prism, то там есть InteractionRequest тогда. А что это вы мне запрещаете во ViewModel окошки клепать?)

Я вам не могу ничего запрещать ) но на мой лично субъективный взгляд это немного не правильно. В таком случае лучше передавать в DelegateCommand просто object и через внешний Service/Manager связывать переданный ViewModel с соответствующей View, например, как это сделано в Caliburn.Micro:


ConfirmCommand = new DelegateCommand<object>(owner =>
{
  var confirmViewModel = new ConfirmViewModel();
  var settings = new Dictionary<string, object>
  {
    { "Owner", owner }
  };

  if (WindowManager.ShowDialog(confirmViewModel, null, settings) == true)
  {
    // do some action
  }
}

В ооочень больших проектах с MVVM влазишь в кашу из зависимостей, логики команд и свойств модели. Отчасти решается выносом команд в отдельные классы и добавлением контроллера. И получается уже гибрид MVVMC.

Спасибо за статью! Сижу по ней паттерн этот изучаю.
У вас в первом примере ошибка в свойстве.
Я в коде поправил на так:
public int Number3 { get => MathFuncs.GetSumOf(Number1, Number2); }
Не лишним будет упомянуть в обсуждении фреймворк ReactiveUI и библиотеку DynamicData, которые решают ряд проблем MVVM реактивным программированием, и позволяют писать кроссплатформенный софт, используя общую модель и общие модели представления. Более того, ReactiveUI предоставляет инструменты для использования биндингов в Windows Forms и Xamarin.iOS/Xamarin.Android, выглядит это следующим образом:

this.WhenActivated(disposable =>
{
    this.Bind(ViewModel, x => x.Title, x => x.TitleBox.Text)
        .DisposeWith(disposable);
});
Sign up to leave a comment.

Articles