Модератор
|
||||||||||||||||||||||||||||||||||||
1 | ||||||||||||||||||||||||||||||||||||
WPF команды и MVVM. Часть 1. [WPF, Элд Хасп]18.01.2019, 19:56. Показов 48858. Ответов 92
Тема из цикла Готовые решения, примеры и рекомендации начинающим на WPF [Элд Хасп]
Для использования и создания WPF команд в Net предусмотрен интерфейс IСommand и классы RoutedCommand и RoutedUICommand. Команда - это View компонента и ей присущи чисто View-ские свойства: можно привязать её, параметр передаваемый ей, целевой элемент; она может всплывать по дереву элементов; можно обработать её по по пути всплытия. Основная проблема применения команд в MVVM, на мой взгляд, это отсутствие встроенной поддержки привязки к методам ViewModel. Дефолтно в Net обработка команд производится аналогично обработке событий элементов в CB окна. И уже в в этом обработчике можно вызвать методы ViewModel. Но тут возникает другая проблема. Как обработчику получить ViewModel? Придётся обращаться к DataContext приводить к нужному типу, походу могут возникнуть ещё другие нюансы. Самым удобным было бы привязка команд в XAML напрямую, сразу к свойствам ViewModel. Для такой привязки в большинстве случаев создают дополнительный класс реализующий интерфейс IСommand. Почти всегда его называют RelayCommand. Реализации его могут быть разные, но они очень похожи. Одна из реализаций из темы Пример реализации WPF+MVVM приложения (с учётом замечания от Lexeq)
VievModel
И окно для PlusMinusViewModel
Изменим немного ViewModel для демонстрации обработки CommandParameter.
Окно для MultiOperatorsViewModel
Изменим ViewModel так чтобы нельзя было делить на ноль.
Использование WPF команд в MVVM для простых случаев, надеюсь, объяснил подробно и понятно. Но есть более сложные применения. Допустим, есть ListBox и в нём в шаблоне Item есть кнопка. Как к ней привязать WPF команду? Для этого надо вспомнить, что WPF команда это View компонента и одним и свойств WPF команды является её всплывание по дереву. Команду надо "поймать" во включающем ListBox контейнере и обработать. Как это сделать напишу в следующей части. Архив проекта с кодами приложен. Ещё хороший пример простой для понимания предложил Lexeq в пост #10 В пост #15 финальный вариант с учётом замечаний от HF, Lexeq, Рядовой, kolorotur. Там же архив со всеми кодами из темы. В пост #64 вариант реализации RelayCommand с исправленным (при помощи proa33 и kolorotur) методом Invalidate для работы в многопоточном приложении.
12
|
18.01.2019, 19:56 | |
Ответы с готовыми решениями:
92
WPF команды и MVVM. Часть 2. Всплытие команд. Реализация команды для списка элементов [WPF, Элд Хасп] Библиотека элементов для реализации WPF MVVM Решений [WPF, Элд Хасп] Обсуждение темы "Библиотека элементов для реализации WPF MVVM Решений" [WPF, Элд Хасп] Создание приложения "Штатное Расписание" в паттерне MVVM [WPF, Элд Хасп] |
Модератор
|
|
13.02.2019, 14:11 [ТС] | 61 |
Может я чё не так понимаю....
У меня одна Модель (из нескольких объектов). К ней подсоединены 2 ViewModel. При подсоединении первой VM она инициирует Model и они обмениваются данными. Через некоторое время (зависит от пользователя) создаётся вторая VM. Понятно, что при изменении данных VM2 изменит значения свойств. Но до изменения данных VM2 тоже надо инициировать свои свойства текущими значениями Model. Я поэтому так и сделал. После подсоединения прослушки, сразу посылается сигнал об изменении всех свойств. Автоматически VM2 свои свойства обновит. Если это убрать - тогда надо в коде VM2 прописать явное чтение всех свойств из Model после соединения. Получается дублирование кода. Обновление свойств и так уже записано в обработчике PropertyChange. Добавлено через 5 минут Даже, для первой VM. Она соpдаёт Model. После создания Model к ней подсоединяется прослушка. Но если при инициализации Model она изменит значения своих свойств, то VM об этом не узнает, так как прослушка подсоединится позже. И какое-то время, пока все свойства ещё раз не обновятся состояния Model и VM будут отличаться. Придётся, опять таки, явно после подсоединения прослушки VM считывать значения всех свойств. Хотя у WPF элементов такого не возникает. Там, интересно, как этот момент реализован?
0
|
17685 / 12871 / 3365
Регистрация: 17.09.2011
Сообщений: 21,136
|
|
13.02.2019, 14:24 | 62 |
Зачем? У вас же уже есть VM для того же самого объекта — зачем плодить лишние?
Ничего не понял. Вы копируете значения свойств из модели в VM? То есть вы не знаете в какой момент вы подписываетесь на PropertyChanged модели внутри кода VM? А если знаете, то почему сразу после подписки в той же VM не вызвать OnAllPropertyChanged? А то как-то странно получается: модель должна знать о том, что VM должна обновить свои свойства?
0
|
Модератор
|
|
13.02.2019, 14:52 [ТС] | 63 |
В Модели несколько объектов и много свойств. Первая VM настраивает Модель: создаёт нужные объекты (не сама, конечно, а через методы Модели) и задаёт значения свойствам. Потом эти объекты и свойства не меняются - эта VM не нужна.
VM2 уже работает с другими свойствами и методами модели. Я думал в начале сделать общую VM..., но код получался какой-то монструозный, огромный. Поэтому разделил на две VM. После создания VM ей же надо как-то отобразить состояние Модели на момент создания.... Или я опять не в те дебри полез....? Опаньки! А вот это мне в голову не пришло! Так действительно, будет разумнее! Добавлено через 4 минуты kolorotur, а с Invalidate какое оптимальный подход будет? И я так и не понял (в потоках токо-токо начинаю разбираться) из-за чего ошибка? Ведь там только код, визуализации нет.... Откуда потребность в одном потоке?
0
|
Модератор
|
||||||
30.04.2019, 22:39 [ТС] | 64 | |||||
Новая реализация RelayCommand с исправлениями от proa33 и koloroturКак выяснилось при многопоточной работе метод Invalidate (в реализации из поста #15) выдаёт ошибку. Обсуждение ошибки в постах #35-#57. proa33 и kolorotur нашли решение, но в полном виде я забыл его опубликовать. Выкладываю полную реализацию RelayCommand с решением указанной проблемы.
4
|
В поиске
103 / 51 / 17
Регистрация: 20.04.2014
Сообщений: 812
|
|||||||||||
07.05.2019, 19:14 | 65 | ||||||||||
Элд Хасп, может есть уже актуальная версия а я попал на устаревшую
Кликните здесь для просмотра всего текста
Но тут явно какое то извращенство. Либо я читаю не правильно? Передаем в OnPropertyChanged текст в виде массива разобрали количество не 0 и не 1 идем в OnPropertyChanged(names). там в цыкле пробегаемся по переданному массиву в котором возвращаемся обратно в OnPropertyChanged но уже с одним значением но опять его пытаемся разбить его повторно на массив Добавлено через 15 минут Почему к примеру не так? Или есть еще какие либо варианты которые могут пробраться и сломать логику? Кликните здесь для просмотра всего текста
1
|
Модератор
|
|||||||||||
07.05.2019, 19:55 [ТС] | 66 | ||||||||||
Я же сам начинающий. Делал в одно время так, в другое по другому. Что казалось важным тогда, потом, по мере опыта, мнение изменилось. Появились новы знания.
Самая простая реализация, которую использую для простых примеров
0
|
В поиске
103 / 51 / 17
Регистрация: 20.04.2014
Сообщений: 812
|
||||||
07.05.2019, 20:09 | 67 | |||||
Да спасибо у меня такая же только без CallerMemberName было пользовался nameof()
Тут по моему еще хуже стало чем то что я спрашивал
зачем? или я не правильно понял?
1
|
Модератор
|
|
07.05.2019, 20:17 [ТС] | 68 |
Использование этого атрибута позволяет в сеттере свойства вызывать
OnPropertyChanged() без параметров. Очень удобно. Если меняешь название свойства - не надо следить за тем где его имя прописано.По принятым соглашениям (в WPF) событие PropertyChanged без значения (то есть когда null или Empty) означает, что надо обновить все свойства.Я сам этого не знал. Когда поправили, то изменил реализацию.
1
|
В поиске
103 / 51 / 17
Регистрация: 20.04.2014
Сообщений: 812
|
||||||
07.05.2019, 20:35 | 69 | |||||
Не знал спасибо.
А вот по поводу того что вы текст склеиваете потом разбиваете на массив может есть смысл сразу передать в виде массива
Добавлено через 8 минут Элд Хасп, а SetProperty и PropertyNewValue для чего нужны можно пример как их вы используете?
1
|
Модератор
|
|||||||||||
07.05.2019, 21:35 [ТС] | 70 | ||||||||||
Посмотрите внимательнее.
Там есть перегрузка принимающая список имён свойств. Но Ваш вариант со списком параметров (params string[] name) тоже добавлю. Он мне просто в голову не пришёл.Свойство определяется подобным образом
В основном два варианта. Первый это обработка всех сеттеров в одном месте, а не в сеттере каждого свойства. Для одного свойства вот так ещё пойдёт
Раскидывать это по всему коду класса очень не удобно. Создаётся partial класс и в отдельном файле через переопределение SetProperty или PropertyNewValue перехватываются установка значений свойствам. оттуда уже идёт, если надо, ветвление по методам. Все методы в одном месте. Удобно, прозрачно, читаемо.Второе применение это способ сделать тоже самое в производном классе. В этом случае по другому просто невозможно. Я очень часто использую базовый класс и потом производные от него. Мне так удобно. Подобный метод есть у всех Net классов реализующих INPC. Попробуйте в CB окна вызвать список переопределяемых методов. Среди ни будет и void OnPropertyChanged(DependencyPropertyChangedEventArgs e) предназначенный для того же самого.
1
|
В поиске
103 / 51 / 17
Регистрация: 20.04.2014
Сообщений: 812
|
||||||
07.05.2019, 22:32 | 71 | |||||
да видел OnPropertyChanged(IEnumerable<string> propList)
Видимо представить себе такую картину не смог и как то проигнорировал OnPropertyChanged(new string[]{"Поле1","Поле2",..}) + то что эти два метода как то показались зацыкленными OnPropertyChanged первый ссылается на второго, а второй на первый Но больше всего мне не нравится то что много раз делаете действие одно и тоже Повторюсь еще может так будет понятнее к примеру (из 66 поста код ниже слов Сам использую такую) Случай 1 выполняем так OnPropertyChanged("") первая проверка положительно оповещаем все контролы чтобы обновились. дальше идем пытаемся "" разбить на массив у нас не получается names.Length=0 мы еще раз оповещаем все контролы чтобы обновились. Если я правильно понимаю мы 2 раза заставляем перерисовать те поля которые при биньженые. случай 2 OnPropertyChanged("Поле1/rПоле2") первая проверка отрицательна разбиваем на массив из 2 полей передаем массив в OnPropertyChanged(IEnumerable<string> propList) там фильтруем и перебираем И ВОЗВРАЩАЕМСЯ НАЗАД где опять но уже над одним словом Поле1 первую проверку проходим на отрицательно опять пытаемся из неё сделать массив и поняв что во второй раз уже не получается только потом говорим что надо оповестить контрол об изменении и с Поле2 та же процедура а если полей 100))) их как минимум по 2 раза одну и туже операцию с ними проводите. Правильно ли я размышляю? Если правильно то моё мнение лучше работать с массивом string и составляя его с помощью nameof() уже сразу, а не играться и разбивать его. Хотя на сколько я знаю если работать с библиотекой mvvm light то они уже реализовали RelayCommand и INotifyPropertyChanged но могу ошибаться. Я бы наверное просто бы добавил к той вашей самой простой реализации и наверно это и будет на все случаи жизни. name проверять на null думаю бессмысленно так как на это OnPropertyChanged(null) компилятор ругается
Добавлено через 3 минуты И если не сложно можете еще раз выложить пример когда в RelayCommand вылетала ошибка на многопоточной ситуации? а то я не понял как её получить при старом коде Добавлено через 19 минут
1
|
Модератор
|
||||||
07.05.2019, 22:59 [ТС] | 72 | |||||
Тут, конечно, return должен быть!
Моя оплошность! Это, вроде уже по другому реализовывал. Уже запутался в реализациях.
0
|
Модератор
|
||||||
07.05.2019, 23:00 [ТС] | 73 | |||||
Новая реализация OnPropertyChangedClassЯ сделал эту реализацию, сравнительно давно (по моим мерка изучение C# и WPF). Сам задумывался, что возможно это излишне. Но иногда была необходимость известить о нескольких свойствах, поэтому использовал такой способ. Но сейчас уже изменил стиль программирования (по мере набора опыта) и уже этим пользуюсь очень редко. А после дополнения перегрузки с Ваши вариантом params string[] propList - это, вообще, потеряло смысл.Сейчас лучше использовать такую реализацию
5
|
Модератор
|
|
07.05.2019, 23:00 [ТС] | 74 |
У меня она возникла при изменении коллекции из другого потока.
В событии обработки изменения стояла проверка некоторых условий и, если условия менялись, вызывался метод Invalidate. Появлялась указанная ошибка. Если же не вызывать этот метод, то кнопка не становилась активной пока что-то на форме не поменяется. А если окно не в фокусе, то вообще состояние кнопки не менялось.
0
|
WPF Разработчик
463 / 167 / 42
Регистрация: 20.02.2018
Сообщений: 285
|
|||||||||||
08.11.2019, 15:17 | 75 | ||||||||||
Элд Хасп,
Объясните пожалуйста. Вы пишите команды вот в таком виде, через проверку на null
Как это вижу в реализации через Конструктор: •оптимизированно (инициализация один раз, объявление поля команды как readonly) •читабельно и эстетично(наглядно видно какие методы переданы во все команды) Свойство •оптимизированно (команда инициализируется только тогда, когда она понадобиться) •громоздко (слегка длинная конструкция, но да, здесь можно по разному обыграть решение, к примеру сделать статический метод которому передаётся поле по ссылке, и в зависимости от ситуации он создаст и вернём, либо просто вернёт значение) •практично(Когда мы переходим из редактора xaml на свойство команды нам надо знать переданный метод, так более практично, в отличии от конструктора, при котором нам надо перейти в конструктор и отыскать в нём инициализацию команды) Подводя итоге свойства оказываются менее производительными для постоянного вызова команды, поскольку постоянно проверяется if, так же свойства менее эстетично выглядят, однако они более практичны для разработки и отладки приложения
0
|
Модератор
|
|
08.11.2019, 17:25 [ТС] | 76 |
sttrox, я использую и так, и этак.
Часто конструктор VM, вообще, не создаю. И создавать его только ради инициализации команды? Второе, часто инициализация команды содержит логику, а порой и полные коды методов команды. Зачем это всё в конструкторе? Это только запутает код. Третье, скорость проверки на null очень высокая. Я сомневаюсь даже что каким-либо тестом удастся выявить задержку между при такой проверке. Тем более что это свойство только для чтения и обращение к нему от WPF элемента будет однократным.
0
|
WPF Разработчик
463 / 167 / 42
Регистрация: 20.02.2018
Сообщений: 285
|
||||||
08.11.2019, 17:46 | 77 | |||||
Элд Хасп, Позвольте, но тогда зачем нам поле, если вызов происходит один раз?
Мы может спокойно написать
Проверял в отладчике, вызов этого свойства происходит действительно один раз.
0
|
WPF Разработчик
463 / 167 / 42
Регистрация: 20.02.2018
Сообщений: 285
|
|
08.11.2019, 18:05 | 79 |
Элд Хасп, что плохого в том, что у нас будет несколько разных экземпляров команды? Какие могут быть последствия?
0
|
Модератор
|
|
08.11.2019, 18:18 [ТС] | 80 |
sttrox, засорение памяти.
Иногда бывают реализации где элемент обращающийся к команде много раз пересоздаётся и каждый раз будет создаваться новый экземпляр команды. Для простых случаев можно не заморачиваться, но это создаёт привычку к плохому коду. Лучше привыкать всегда делать однотипно и правильно.
0
|
08.11.2019, 18:18 | |
08.11.2019, 18:18 | |
Помогаю со студенческими работами здесь
80
WPF конвертеры [Элд Хасп] WPF vs WinForms (для начинающих) [Элд Хасп] Готовые решения, примеры и рекомендации начинающим на WPF [Элд Хасп] INPC (INotifyPropertyChanged) и получение данных из Модели [WPF, Элд Хасп] Пример создания приложения для тестирования [WPF, Элд Хасп] WPF.MVVM и команды Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |