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

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

Посмотрите в сторону ReactiveUI фреймворка. Намного интереснее будет повторять.
Спасибо за комментарий)
Я пишу про базовые вещи, имхо, на фреймворки нужно переходить уже после того, как хотя бы немного потыкал голую платформу

Использовать со старта вспомогательные библиотеки и/или фреймворки или нет сложный вопрос.


Лично я начинал с библиотеки FSharp.ViewModule и успешно ее использовал в своих небольших проектах, даже не подозревая о внутреннем устройстве команд и объектов уведомляющих о своем изменении. И еще долгое время подобные тонкости меня не беспокоили. Я считаю, что главное понимать что механизм делает, а осознание как именно придет с опытом, при условии что вообще возникнет необходимость разбираться во внутренней реализации.


Видимая сложность MVVM, возможно, одна из причин того, что многие продолжают смешивать все в одной куче. Библиотека, с другой стороны, может помочь развернуть процесс обучения — от общего представления о шаблоне к его полному (или хотя-бы достаточному) пониманию, делая его более плавным.


К тому-же, на мой взгляд, интереснее разбираться в том как приложение работает, а не в том как заставить его работать. И, конечно, самостоятельно написанная работающая программа, в отличие от взятой из папки "samples", пусть даже и построенная из готовых блоков (вызовов готовых методов), дает положительный эффект — "я могу, я умею" [хотя-бы что-то!], добавляя уверенности, а не отрицательный — "ничего не работает", "это не для меня".

Я как раз и писал эту статью в расчёте на то, что она поможет справиться с тем, как заставить приложение работать.
Мнение интересное, спасибо)

Совершенно другая идиология и если предварительно не переломить мозг с ReactiveX, то лучше даже и не пытаться.

Реактивные расширения и в частности ReactiveUI позволяют решать задачи существенно быстрее. "Переломить мозг" не так уж и сложно, тем более, что потом это быстро окупится. Туда же PropertyChanged.Fody. После того, как попробуешь эти две библиотеки, пути назад уже не будет. Начинаешь понимать, что вот эти ручные реализации INotifyPropertyChanged — нечто из далёкого прошлого. См. https://m.habr.com/post/418007/

Спасибо за статью. Очень вовремя подошла. Буду ждать следующие.

Пожалуйста)

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

private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            NotifyPropertyChanged(propertyName);
            return true;
        }

        public string Id
        {
            get => _id;
            set => SetField(ref _id, value);
        }

Ага, так и будем понемногу модифицировать код, чтобы понятно было, зачем это и откуда

Жду еще статей, самому так и не получилось в MVVM толком разобраться)

Думаю, что раз Вы понимаете суть привязки и PropertyChanged, то вполне себе уже понимаете и MVVM)

Тоже хотел сделать замечание по поводу CallerMemberName в OnPropertyChanged.

Вы забыли создать поле. Да и как показала практика SetField бесполезная штука от слова полностью, слишком часто используются свойства, которые не хранят значения в самой VM, а просто передают их из моделей (т.к. основная задача VM это обеспечить взаимодействие View <--> Model).

public bool IsSomething => something.SomeMethod();
// где-то в конструкторе
something.SomeEvent += (s, e) => OnPropertyChanged(nameof(IsSomething));

Cлишком часто же, в сеттере делается работа, в основном для View, иногда требуется даже контролировать когда именно вызывать PropertyChanged, когда именно менять поле и т.д. — совершенно бесполезно автоматизировать это все.
Хорошая статья. Но только… где Model от «первой буковки» паттерна? )))
Спасибо) в этом примере обошлось без модели, т.к. нет никакой логики и сущностей, которые нужно было бы хранить. Дальше всё будет)
А зря. Простейший класс со стринговым автосвойством подошёл бы отлично. А заодно проброс уведомлений был бы нагляден.
— По поводу фреймворков. На заре изучения WPF кинулся я по шаблону Catel клепать десктопные «калькуляторы», да так увяз во всём, что не замечал очевидного: нахрен не сдались мне эти фреймворки, ибо всё «из коробки» работало более чем отлично. Такие статьи тому пример — что не надо навешивать кучу библиотек, аспектов ))), nuGet пакетов, достаточно лишь основ для понимания всей логики паттерна и WPF десктопа в частности.
Я решил понемногу наращивать сложность, поэтому в этот раз без модели) Потому что инфы немало и без этого получилось
По поводу фреймворков и паттерна полностью согласен. Хотя часть рутинной работы, пожалуй, можно и на фреймворки сбросить)
Конечно можно, и даже нужно! Чего один Fody будет стоить. Но когда у свежеиспечённого прогера WPF вложенный класс в модели не будет нотифить VM а та V, тогда уже свежеиспечённый прогер полезет на StackOverFlow за очередной порцией быдлокода фреймворка, вместо того, чтобы явно указать штатную подписку на событие изменения.
Связь между V и VM проста и банальна, на ней даже особо заострять внимание не стоит, просто как данность (С тем же caliburn привязка еще удобнее делается). Организация работы с моделью, вот где самое интересное и камень преткновения).
Чтоб уж полностью соответстовать паттерну, то View и задание DataContext лучше производить в App.xaml.cs, а то в данном случае получается, что View создает VM. Ну или использовать какой нибудь фреймворк (caliburn.micro/MVVMLight)
Ага, думал сделать так, но потом решил убрать в конструктор окна. Далее вообще перенесём присваивание датаконтекста в обработчик события ContentRendered, т.к. датаконтекст может быть громоздким и может затормозить отрисовку.
Хорошо. Интересно почитать будет. Не затягивайте с новыми статьями :) А то помню как несколько лет назад знакомился с MVVM, везде HelloWorld'ы, а чтоб что нибудь сложнее — нету.
.
За всё время только один попался проект с применением MVVM и без использования паттернов, на который приятно посмотреть.
Насчёт времени обещать конкретного плана, к сожалению, нет, но постараюсь не затягивать)
За ссылку спасибо, очень круто сделано) Прям на самом деле приятно посмотреть
Так это тоже затормозит отрисовку))
Представьте, что вы отрисовали ваш наисложнейший интерфейс, а потом подтягиваете базу данных на 100 000 записей. Не заморачивайтесь с громоздскостью.
roadmap то довольно простой: структура проекта с папками и правильной обызвалкой классов, Model, notification, ViewModel, View, binding, commands, collections.
И главное: как можно меньше сторонних либ. Это крайне, Крайне упростит понимание что там происходит внутрях.
Ну основной интерфейс будет отрисован, а на время подтягивания базы я могу вывести крутилку или какие-то заглушки) Но в целом да, статья не об этом, поэтому лучше делать максимально наглядно и канонiчно
Если не считать нарушения паттерна MVVM, статья неплоха. Сам смысл MVVM заключается в отделении данных от логики и представления, а у вас получается Model скрещена с ViewModel и уведомляет об изменениях сама себя.

Странно говорить о Model-View-ViewModel, не используя Model.
Спасибо) Уже несколько раз отвечал на это, но повторюсь: сейчас пока всё «на пальцах», да и в текущем примере не нужна модель как таковая, потому что нечего хранить.
Ну это достаточно распространенная практика, когда VM и M скрещивают, если все очень тревиально.
Согласен, распространённая. Но почему то считают, что это надо прям навязывать при объяснении паттерна «для новичков» где прям в названии слово Model стоит. Речь обо всех возможных статьях в интернетах.
Вы не поверите, в книге от MS по WPF тоже как-то забито на MVVM по полной.
О какой книге идёт речь?
Сори, перепутал. Имел ввиду издательства Apress. WPF: Windows Presentation Foundation в .NET 4.5. Уж как-то про многое рассказано, а про MVVM забыто.
Хотел тут написать про MVVMLight и забивании на велосипеды. Но авторы вооще ребята, к успеху идут, выпилили документацию и впаривают курсы.

Поэтому ReactiveUI и PropertyChanged.Fody — наши друзья и товарищи! Удивительная эффективность, опенсорс, никакой коммерции, кроссплатформенность и хорошая документация.

Grid — позволяет организовать элементы по столбцам и строкам, ширина каждого столбца или строки настраивается индивидуально.

Кажется, вы пропустили "в виде сетки или таблицы" и не только ширина, но и высота тоже ;)


MVVM и интерфейс INotifyPropertyChanged. Копия текста.

Копия текста?


Привязка в терминологии WPF — это механизм, позволяющий связывать некоторые свойства контролов с некоторыми свойствами объекта C#-класса и выполнять взаимное обновление этих свойств при изменении одной из частей связки (это может работать в одну, в другую или в обе стороны сразу).

Не обязательно C# класса. WPF может прекрасно подружиться с F#, не говоря уже о VB.Net.


Ну, почти всё, финишная прямая! Осталось указать вьюхе, что оно должно слушать событие PropertyChanged:
TextBox Text="{Binding Path=SynchronizedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"
TextBlock Text="{Binding Path=SynchronizedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"

Здесь, мне кажется, вы не совсем однозначно выражаетесь.


UpdateSourceTrigger лишь указывает на то, когда будет обновляться привязываемое свойство. PropertyChanged устанавливает режим обновления сразу после изменения свойства в "приемнике" (не самое удачное слово, но ничего лучше на ум так и не пришло). По умолчанию TextBox обновляет привязку после потери своего фокуса. Что касается TextBlock, то там установка UpdateSourceTrigger не требуется, как и явный Mode = OneWay.


В целом, несмотря на то, что хороших материалов по WPF хватает, еще один туториал точно не повредит.


Но в будущем мне бы хотелось, чтобы примеры были не настолько искусственные. В частности: здесь совсем не требуется реализация INotifyPropertyChanged. Если TextBlock должен дублировать текст из TextBox, то можно сразу там и привязываться к свойству Text, а дополнительная прослойка в виде свойства, оповещающего о своих изменениях, не нужна, обычного авто-свойства в данном конкретном случае будет достаточно.

Спасибо за комментарий, отвечу по порядку)
Кажется, вы пропустили «в виде сетки или таблицы» и не только ширина, но и высота тоже ;)

Да, про высоту забыл, действительно
Копия текста?

В примере происходит копирование текста из одного контрола в другой
Не обязательно C# класса. WPF может прекрасно подружиться с F#, не говоря уже о VB.Net.

Согласен, но я нигде не говорил, что всё ограничивается C#. Просто я его использую, поэтому про него и пишу)

По поводу замечания об UpdateSourceTrigger — я просто не вдаюсь в подробности, чтобы не останавливаться на деталях на таком этапе, когда человеку требуется туториал. Впрочем, комментарии прекрасно дополняют чтение) А так же умение искать материал в интернете.

Пример действительно искусственный, и сделано это намеренно для упрощения и отстранения от деталей.
В примере происходит копирование текста из одного контрола в другой

Ах, вот оказывается в чем дело, как я сразу не догадался =)

Тогда напишите не «Копия текста», а «Пример: Копирование текста».
И вообще, текст от ссылки до места якоря можно поместить в спойлер, раз вы считаете, что эта ссылка вообще нужна. А так, выглядит, что это ссылка на другую статью, или на репозиторий с текстом примера (который был бы полезен многим). Мне пришлось приглядываться к ссылке, чтобы понять, что вы не отправляете меня куда-то.
Хорошая статья, будет интересна как для новичков так и для тех кто уже знаком с WPF, и хотели бы узнать о патерне MVVM.
Также мне кажется стоило бы указать что привязать DataContext можно сразу в XAML.
Если вы про XAML самой View говорите, то это так же не верный подход как и тут.
Статья хорошая, хорошо рассказана основа биндинга.

Есть несколько замечаний/предложений:
1) лучше не загромождать xaml необязательными атрибутами. Например, StackPanel легко обойдётся без Orientation=«Vertical», а оба биндинга справятся без указания Mode. К тому же, режим биндингов и так будет разный — у TextBox режим по умолчанию TwoWay, а у TextBlock — OneWay (как указано в настройках свойств зависимости Text этих классов).

2) Всё-таки лучше указывать датаконтекст в xaml-е, а не в конструкторе:
<Window ...
        xmlns:vm="clr-namespace:Ex1.ViewModels">
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    <StackPanel>
        ...
    </StackPanel>
</Window>

Как указали выше, суть не изменится, зато будет два плюса — показать, что в замле можно указывать всё что угодно, а также в редакторе заработает подсказка свойств в биндинге (лучше отметить, что при изменениях во вьюмодели нужно сбилдить проект). А во втором уроке можно будет рассказать про d:DataContext, опять же — для подсказок.

3) Базовый класс BaseViewModel лучше делать не сразу, а потом. В начале показать, что во вьюмодели должен быть INotifyPropertyChanged, а потом (во второй части) сделать рефакторинг и вынести в отдельный класс. Предполагаю, что эту статью будут читать новички, им будет полезно показывать по шагам, объясняя по ходу изменения в коде и показывая необходимость проводить рефакторинг.
Всё-таки лучше указывать датаконтекст в xaml-е, а не в конструкторе

На самом деле, лучше всего использовать шаблон NavigationService или ViewModelLocator, чтобы можно было легко и непринуждённо использовать IoC-контейнер!

Ну, это pro-уровень :) в первом уроке использовать это не нужно (хотя, упомянуть в тексте можно). Автору пришлось указывать на новую сущность в обзорном уроке — объяснять «код позади» xaml. По опыту обучения новичков, чем меньше различных сущностей в одном уроке — тем лучше.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.