Комментарии 45
Я пишу про базовые вещи, имхо, на фреймворки нужно переходить уже после того, как хотя бы немного потыкал голую платформу
Использовать со старта вспомогательные библиотеки и/или фреймворки или нет сложный вопрос.
Лично я начинал с библиотеки FSharp.ViewModule и успешно ее использовал в своих небольших проектах, даже не подозревая о внутреннем устройстве команд и объектов уведомляющих о своем изменении. И еще долгое время подобные тонкости меня не беспокоили. Я считаю, что главное понимать что механизм делает, а осознание как именно придет с опытом, при условии что вообще возникнет необходимость разбираться во внутренней реализации.
Видимая сложность MVVM, возможно, одна из причин того, что многие продолжают смешивать все в одной куче. Библиотека, с другой стороны, может помочь развернуть процесс обучения — от общего представления о шаблоне к его полному (или хотя-бы достаточному) пониманию, делая его более плавным.
К тому-же, на мой взгляд, интереснее разбираться в том как приложение работает, а не в том как заставить его работать. И, конечно, самостоятельно написанная работающая программа, в отличие от взятой из папки "samples", пусть даже и построенная из готовых блоков (вызовов готовых методов), дает положительный эффект — "я могу, я умею" [хотя-бы что-то!], добавляя уверенности, а не отрицательный — "ничего не работает", "это не для меня".
Реактивные расширения и в частности 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);
}
Ага, так и будем понемногу модифицировать код, чтобы понятно было, зачем это и откуда
Вы забыли создать поле. Да и как показала практика SetField бесполезная штука от слова полностью, слишком часто используются свойства, которые не хранят значения в самой VM, а просто передают их из моделей (т.к. основная задача VM это обеспечить взаимодействие View <--> Model).
public bool IsSomething => something.SomeMethod();
// где-то в конструкторе
something.SomeEvent += (s, e) => OnPropertyChanged(nameof(IsSomething));
Cлишком часто же, в сеттере делается работа, в основном для View, иногда требуется даже контролировать когда именно вызывать PropertyChanged, когда именно менять поле и т.д. — совершенно бесполезно автоматизировать это все.
— По поводу фреймворков. На заре изучения WPF кинулся я по шаблону Catel клепать десктопные «калькуляторы», да так увяз во всём, что не замечал очевидного: нахрен не сдались мне эти фреймворки, ибо всё «из коробки» работало более чем отлично. Такие статьи тому пример — что не надо навешивать кучу библиотек, аспектов ))), nuGet пакетов, достаточно лишь основ для понимания всей логики паттерна и WPF десктопа в частности.
Представьте, что вы отрисовали ваш наисложнейший интерфейс, а потом подтягиваете базу данных на 100 000 записей. Не заморачивайтесь с громоздскостью.
roadmap то довольно простой: структура проекта с папками и правильной обызвалкой классов, Model, notification, ViewModel, View, binding, commands, collections.
И главное: как можно меньше сторонних либ. Это крайне, Крайне упростит понимание что там происходит внутрях.
Странно говорить о Model-View-ViewModel, не используя Model.
Поэтому 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 — я просто не вдаюсь в подробности, чтобы не останавливаться на деталях на таком этапе, когда человеку требуется туториал. Впрочем, комментарии прекрасно дополняют чтение) А так же умение искать материал в интернете.
Пример действительно искусственный, и сделано это намеренно для упрощения и отстранения от деталей.
В примере происходит копирование текста из одного контрола в другой
Ах, вот оказывается в чем дело, как я сразу не догадался =)
И вообще, текст от ссылки до места якоря можно поместить в спойлер, раз вы считаете, что эта ссылка вообще нужна. А так, выглядит, что это ссылка на другую статью, или на репозиторий с текстом примера (который был бы полезен многим). Мне пришлось приглядываться к ссылке, чтобы понять, что вы не отправляете меня куда-то.
Также мне кажется стоило бы указать что привязать DataContext можно сразу в XAML.
Есть несколько замечаний/предложений:
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-контейнер!
Быстрый старт с WPF. Часть 1. Привязка, INotifyPropertyChanged и MVVM