Как стать автором
Обновить

Комментарии 40

как показала практика в вендинге программирование интерфейска далеко не самое сложное. потому его хоть как реализуй.
Смотря на что фокусироваться. Может, что как раз наоборот) Модель — простая, VM — автоматом делается, а вот интерфейс попробуй нарисуй, чтобы красивый был, интуитивный, удобный, эргономичный и т.д.
Если VM у вас делается автоматом, то вам она скорее всего не нужна.
Это образно же) Скажем, в полуавтоматическом режиме делаются
Поэтому вендинговые автоматы имеют инструкцию из 10 пунктов на передней панели для программирования покупателей.
А где посмотреть «часть 1»?

Окончательный вариант можно взять отсюда:

Ссылка отсутствует
Часть 1 тут: MVVM Часть 1
С WPF лучше переопределять OnStartup, и создавать главное окно там.
Причина в том, что указание StartupUri не позволяет задать DataContext снаружи окна. А хардкодирование DataContext'а внутри XAML неправильно, так как:

  • Согласно паттерну MVVM, View не должно руководить VM, и знать о нём должно как можно меньше. То есть код, обслуживающий UI, не должен создавать себе VM сам.
  • Чаще всего VM должна получить ещё какие-то аргументы в конструкторе (например, модельные объекты), а это не получится сделать, если VM создаётся в XAML'е (откуда XAML может знать о модели?).
  • Иногда перед открытием главного окна нужна дополнительная логика, требующая показа своего маленького окна (например, предложение обновить программу). Код, выясняющий это и при некоторых условиях показывающий дополнительное окно, проще разместить в OnStartup.
  • Главная VM может понадобиться вам для ещё чего-нибудь. Поэтому если её создаёт класс App, он может сохранить ссылку на неё у себя, чтобы другие (например, побочное окно) могли быть привязаны к тому же экземпляру VM. Иначе вам придётся лезть за главной VM в экземпляр главного окна.


Не мое, но практика показала что это наиболее разумный подход.
Еще лучше взять Caliburn.Micro и забыть о 90% шаблонного геморроя.
Кхм, ну тогда уж лучше брать Stylet и забыть о 95%.
«Stylet is a small but powerful ViewModel-first MVVM framework for WPF, which allows you to write...»

Я использую Model-first — подход, иногда View-first — подход, но ViewModel-first даже затрудняюсь представить:) Надо посмотреть на сие чудо
НЛО прилетело и опубликовало эту надпись здесь
View знает всё про вьюмодель, все свойства, команды и т.д.

Не все. View известен только публичный интерфейс ViewModel. Она не должна знать, откуда брать и как создавать ViewModel. Этим должна заниматься точка сборки.


Если мы поняли, что контекст данных можно получать из контейнера, то остаётся сделать следующий очевидный шаг — декларировать его прямо в XAML через расширение разметки:

Зачем заново придумывать data template?
Плюс в таком виде это не DI, а Service Locator, отягощенный глобальной переменной.

Я вообще пользуюсь Prism. Там (начиная с пятой версии) View и ViewModel можно связывать по настраиваемому Name Convention, т.е. по определенному шаблону имени. А параметры конструктора VM через dependency injection.
Честно говоря, я не знаю, какое понимание смогут вынести из этих статей начинающие.
Видно кучу шаблонного кода, большой оверхед без видимой пользы, и куча ViewModels, которые с тем же успехом мог сделать кодогенератор.
Это не удивительно, так как суть паттерна MVVM не просто в отделении представления от модели, а в отделении поведения представления от поведения модели.
Для модели важны консистентность, производительность, отказоустойчивость.
Для представления важно удобство проведения операций в модели для пользователя -человека с наглядной обратной связью, контролем возможных ошибок, возможностью изменить параметры до принятия необратимого решения.
ViewModel нужна как раз для реализации человеко-удобного поведения отдельно от конкретной реализации ввода-вывода.
В статьях этого нет — и ViewModel превращается в скорее вредную, чем бесполезную прокладку.
ViewModel — это не вредная прокладка, а некоторый прочный каркас, к которому прибивается View. Модель может быть такой, облакообразной, как перьевые облака, грозовая бесформенная куча, к которой не прибиндиться, а во ViewModel может быть такой код:
public IsEditVisible { get; } => (_model.SomeProperty[«SomeIndex»] * _model.GetSomeValue) < _model2.AdjustionCorrelator;
Вьюшка не может собрать по модели — отображать ей кнопку Edit, или нет. А вот к VM одному свойству она прибиндиться может.

IsEditVisible — свойства с таким именем во ViewModel быть не должно, ViewModel ничего не знает о видимости по построению.


Модель может быть такой, облакообразной, как перьевые облака, грозовая бесформенная куча, к которой не прибиндиться

Из этого совершенно не понятно, почему модель именно такая, и что мешает спроектировать ее иначе.


во ViewModel может быть такой код

Если ViewModel можно заменить простым маппером, то она не нужна.

Спроектировать иначе модель для нужд вью и VM? Если модель используется в проекте в другом месте, если она используется в Вебе и т.д. — перепроектировывать ее для нужд WPF нет необходимости, т.к. именно VM возьмет на себя обязанность спрямить для вьюшки все причудливые изгибы модели.
IsEditVisible — свойства с таким именем во ViewModel быть не должно, ViewModel ничего не знает о видимости по построению.

Слушайте, ну у вас какая-то своя философия MVVM, напишите статью о ней и мы там побеседуем о_0
> Спроектировать иначе модель для нужд вью и VM?

Нужды вью впрямую модели не касаются — именно для этого и вводится VM. Нужды VM формируют интерфейс модели вкупе с основной бизнес задачей (продажей товаров покупателям).

> Если модель используется в проекте в другом месте

… то к нашим услугам адаптеры, мосты и прочие медиаторы. Трехслойные паттерны появились совсем не потому, что модели задавались кем-то внешним. Это способ проектирования и реализации непосредственно приложений с UI.

> Слушайте, ну у вас какая-то своя философия MVVM

У меня нет никакой отдельной философии. Это очень простая вещь — краеугольный камень всех многослойных UI-паттернов. Непосредственно за ввод-вывод информации для человека отвечает слой View и никто больше. Видимость конкретного элемента управления — вопрос исключительно для View. Он может решаться с помощью привязки к свойству ViewModel, но само это свойство всегда выражается в терминах поведения и состояния приложения, а не View, которое может быть абсолютно любым (например, текстовым на шрифте Брайля).
Если у вас ViewModel знает про Edit и что он Visible — вы переложили на нее часть ответственности View и нарушили паттерн.
Нужды VM формируют интерфейс модели вкупе с основной бизнес задачей (продажей товаров покупателям).

Когда я разрабатываю сначала вьюшку, потом к ней создаю VM, а потом, основываясь на вызовах модели из VM, я создаю саму модель — этот подход View-first. Но есть и другой подход (Model-first) — когда сначала без всякого интерфейса создается модель, потом рисуется интерфейс и только потом вьюшки и модель связываются как клеем VM.
Видимость конкретного элемента управления — вопрос исключительно для View. Он может решаться с помощью привязки к свойству ViewModel, но само это свойство всегда выражается в терминах поведения и состояния приложения, а не View, которое может быть абсолютно любым

Вот вроде говорим об одном… но выводы делаем различные.
Вот допустим у нас в представленной задаче кнопочки с покупками определенных товаров были бы видимы только тогда, когда кредита хватало бы на их покупку.
Модели все равно — видны кнопочки или не видны — даже если вы нажмете на такую кнопку: если денег в кредите недостаточно — модель все равно не продаст вам товар. Теперь, если мы (чисто из визуальных нужд) захотим скрыть кнопочки тех товаров, для покупки которых кредит недостаточен, тогда я прописываю во ViewModel if(Credit < MyPrice) BuyVisible = false — условно говоря. А View у меня биндится к BuyVisible. Или это я поставил в кондицию DelegateCommand такую конструкцию. Но в любом случае эту видимость я задаю вo ViewModel. Модели же, напоминаю, пофиг. Она не продаст, даже если ткнуть в невидимую кнопку.
> Теперь, если мы (чисто из визуальных нужд) захотим скрыть кнопочки тех товаров, для покупки которых кредит недостаточен, тогда я прописываю во ViewModel if(Credit < MyPrice) BuyVisible = false — условно говоря.

Не BuyVisible, а CanBuy — ViewModel ничего не знает и не хочет знать о видимости.
Но о доступности того или иного действия сообщить может.
… короче однохренственно :)
Данный пример показывает доступность действия «CanBuy» о котором ViewModel может сообщить.
А если речь идёт не о доступности действия, а о доступности колонки в Grid на основе роли пользователя.
Например, нужно скрывать колонку с запрплатой, если у пользователя нет соответствующих прав на её простмотр.

В этом случае допускается иметь во ViewModel свойство SalaryVisible (или SalaryAllowed)?
По мне, так этот Salary кандидат на то, чтобы оказаться в модели
Если у пользователя нет прав — этих данных вообще не должно быть на клиенте. Соответственно, visible неприменимо.
Это ведь довольно распространенная задача — скрыть/показать некоторые информационные поля во View в зависимости от привилегий пользователя.
Как тогда по вашему должно выглядеть идеалогически правильное MVVM-решение данной задачи?
ViewModel может выставлять список логических колонок как ObservableCollection
К сожалению, в интернете оч туго с грамотными примерами MVVM. Очень часто у них View Обращается к тем или иным часятм Model напрямую. Тут же хоть не до конца все рассмотрено, но грамотнее чем в большинстве примеров что я встречал.
К тому же полезные коментарии :).
куча ViewModels, которые с тем же успехом мог сделать кодогенератор
Какой генератор, подскажите, вообще какой best practice для разработки поз WPF сейчас?
Я использую Prism. Вам советую прочитать Adam Nathan: WPF Unleashed, потом посмотреть видеокурсы Брайана Лагунаса про Prism, а потом, собственно, на него и перейти

В private static void Watch<T, T2>
неправильное условие в фильтре


if (a.OldItems?.Count == 1) collToUpdate.Remove(collToUpdate.First(mv => modelProperty(mv) == a.NewItems[0]));`
Действительно! Спасибо
В статье просто исправил, а в скачиваемом примере — забыл

а почему не использовать ограничения на типы вроде where T: class, new()


это позволило бы избавиться от активатора и писать более понятный и красивый код в виде
new T();

констрейнт new для конструктора по умолчанию, а нам нужно параметры передать
Окей, но можно тогда было бы сделать условный интерфейс IInitializable(ну или как-то ещё назвать) вроде

internal interface IInitializable 
{
    Inject(params object[] p);
}


и таким образом заставить корректно инициализироваться объект.

Ну, это довольно общий случай, тут мог бы подойти вариант попроще, может.

Просто не уверен, что рефлексия — это достаточно хороший в данном случае вариант для создания, хотя я могу быть и не прав :)
Нет, направление вашей мысли верное, только это несколько затеняет предмет статьи — MVVM. Такую функцию (Watch) я конечно бы не потащил в enterprise, но в статье я всячески убираю посторонние детали, чтобы сосредоточиться на основных существенных моментах MVVM

Компилятор C# преобразует new T(); в Activator.CreateInstance(typeof(T));
SO Тесты

Использование такого рода подписок на PropertyChanged, а так же на CollectionChanged чревато утечками памяти. Особенно в тех случаях, когда вью модели создаются динамически. Гораздо лучше использовать мессенжеры для обмена данными между вьюмоделями, или слабо связанные события. Мессенжер чем еще хорош, что позволяет практически полностью устранить связанность между вью моделями.
А использование асинхронного мессенжера позволит отложить перестройку вьюх, если это необходимо, до более подходящего момента.

Кроме того, вместо источников данных в виде ObservableCollection лучше использовать связку List и ICollectionView. Тогда при изменении данных вьюха не будет перестраиваться при каждом чихе, а будет лишь тогда, когда будет вызван Refresh() для ICollectionView.
Но в данном примере это не обязательно, так как динамических данных нет, все статично.
Это не enterprise код, мы тут фокусируемся не на том, как подписываться, — от этого я абстрагируюсь. Фокус на MVVM.
Что касается ObservableCollection, так смысл как раз в том, чтобы она не руками обновлялась Refresh()'ем, а автоматически. Вьшка вся не перестроится — это не веб:) — автоматически произойдет частичное обновление вью, как и задумывалось.
В конкретной программе есть коллекция с динамическим размером — это список покупок пользователя.
Тут опечатка походу:
public class MainViewVM : BindableBase {
  public int UserSumm { get; }
  public ObservableCollection<MoneyVM> UserWallet { get; }
  public ObservableCollection<ProductVM> UserBuyings { get; }
  public DelegateCommand GetChange { get; }
  public int Credit { get; }
  public ReadOnlyObservableCollection<MoneyVM> AutomataBank { get; }
  public ReadOnlyObservableCollection<ProductVM> ProductsInAutomata { get; }
}


Вместо:
public ReadOnlyObservableCollection<MoneyVM> AutomataBank { get; }
public ReadOnlyObservableCollection<ProductVM> ProductsInAutomata { get; }


Должно быть:
public ObservableCollection<MoneyVM> AutomataBank { get; }
public ObservableCollection<ProductVM> ProductsInAutomata { get; }


Вдруг кто-то как я сидит и руками набивает этот код по мере чтения статьи)
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории