|
Модератор
|
||||||||||||||||
Платформо-Независимая ViewModel07.01.2023, 14:19. Показов 1090. Ответов 16
Метки нет (Все метки)
Хоть вопрос и проистекает напрямую из реализации MVVM и, соответственно, тесно связан с WPF, но данный аспект, как мне кажется, больше относится к Шарпу в целом. Поэтому задаю его в этом разделе.
Преамбула. Во всех паттернах семейства MV* предполагается реализация Модели независящая от Представления, его типа и даже, вообще, его наличия. Такую Модель лучше всего реализовывать на Standard и потом можно будет использовать в приложении с любым типом GUI: Формы, WPF, UWP, Avalonia, Xamarin и т.д.В семействе WPF GUI используется паттерн MVVM. Паттерн строго иерархический View -> ViewModel -> Model.Связь верхнего слоя с нижним - сильная, обратная - слабая (через события и делегаты обратного вызова). Связи через слой - не допускаются ни в каком направлении. Требования к ViewModel в основном возникают из-за внедрённого в WPF механизма привязок. Привязки возможны только к свойствам объекта источника. Для автообновления представления по привязкам в объекте-ичтонике должны подыматься события интерфейсов INotifyPropertyChanged, INotifyCollectionChanged, IBindingList (редко используется) и ICommnad. Так как Модель, в общем случае, имеет произвольную реализацию, то в ней может не быть свойств вовсе, могут использоваться любые (часто кастомные) события, вся работа строится через методы. Поэтому, для удобной работы WPF, основная функция ViewModel - это отражение Модели в своих свойствах. Проблема. Так всё семейство WPF (UWP, Avalonia, Xamarin и др.) построены очень похоже, то по идее одна и та же VM может работать с любым их этих типов GUI. И в однопоточном приложении это действительно так. Сложности возникают при маршализации событий из других потоков в Представление. Рассмотрим это на примере WPF vs UWP. В WPF событие INotifyPropertyChanged.PropertyChanged можно подымать в любом потоке, так как его маршалинг в UI поток встроен в WPF привязки.Поэтому типичная реализация базовой VM такая (взято из Паттерн MVVM - WPF - Professor Web):
А вот для события ICommand.CanExecuteChanged в кнопках и пунктах меню такого маршилинга нет.Поэтому простейшая реализация (от kolorotur) не всегда правильно работает:
Invalidate() не из UI потока, то будет выкинуто исключение.Поэтому для WPF лучше использовать такую реализацию (Простые реализации для тем на форуме):
INotifyCollectionChanged.CollectionChanged в ItemsControl. Приходится либо в VM маршализировать изменения коллекций в UI поток, либо синхронизировать эти изменения с привязками методом BindingOperations.EnableCollectionSynchronization(...). Ладно, встроили всё это в VM для WPF. НО!!! Для UWP её использовать не получится, хотя во всей остальной логике никаких изменений не понадобится. В UWP нет маршалинга INotifyPropertyChanged.PropertyChanged. Зато есть для INotifyCollectionChanged.CollectionChanged и ICommand.CanExecuteChanged. Так же не получится сделать общий маршалинг и подписку команд, так как в UWP нет CommandManager и другой Dispatcher.В результате мы получаем, что для WPF и UWP нужны полностью одинаковые VM по основной своей функциональности - отражение Модели в своих свойствах. Но разные на уровне базовой реализации в которые встроены маршалинг и подписка команд. Чего хочется. Так как отличия относятся к деталям реализации типа Представления, то разрешать это на уровне Представления, а не на уровне ViewModel. Как решаю в текущее время. Через рефлексию при первом обращении пытаюсь определить тип Представления и "подсовываю" через фабрику нужные базовые типы. Получается очень муторно, путано, не уверен, что будет работать всегда и точно будет работать только для тех типов, что я "железно вшил" в базовую реализацию. Какую-то гибкую настройку по новому типу GUI добавить не получится. Придётся менять исходный библиотечный код.
0
|
||||||||||||||||
| 07.01.2023, 14:19 | |
|
Ответы с готовыми решениями:
16
Mvvm ViewModel в ViewModel |
|
|
||
| 07.01.2023, 19:44 | ||
|
Да, видимо одним из вариантов вы предполагаете - правильная абстракция. Но это тогда и должна быть абстрактная тема про абстрактные классы с общими методами делающими потенциально что-то одно и тоже. Вообщем, лично я пока не вижу причин или чистых решений делать что-то универсальное. А насчёт абстракций. Если вы действительно можете свои методы каждой платформы сгруппировать в какой-то общий интерфейс. То это уже и будет решение - типа сервис INotifyChanger, а в каждом проекте свои реализации типа IWpfPropertyChanger, IUwpPropertyChanger. И уж пусть они там делают Invalidate или не делают. Но это явно уже тянет на создание своего мультиплатформенного минифреймворка или библиотеки.
0
|
||
|
Модератор
|
|||||||||||||||||
| 08.01.2023, 15:30 [ТС] | |||||||||||||||||
|
HF, попробую на примере .
Есть некая Модель с асинхронным событием:
Но, из-за разных особенностей WPF и UWP, реализация классов ViewModelBase и RelayCommand разная на этих платформах.Поэтому приходится код VM дублировать "один в один" в разных сборках. Добавлено через 7 минут Пока в виде фабрики. Но как-то всё это громоздко получается.
0
|
|||||||||||||||||
|
|
||||
| 08.01.2023, 16:18 | ||||
|
Это и был начальный посыл - проекты каждой платформы регистрируют свои реализации (сервисы) для этих общих интерфейсов. Понимаю что это громкие слова, идеальное ООП, но именно к этому надо стремиться. (пример потом гляну) Добавлено через 14 минут Ну например, вы говорите что RelayCommand разная. Тут я немного засомневался, или в своём знании или в вашем примере. Команды же вроде бы одинаковые в WPF, UWP, Avalonia и т.п. Или имелось ввиду что разные пространства имён? И если сами команды не совместимы, то.. само действие, внутри команды, можно же сделать "разным"? Но если вы копируете 1:1 то значит действия одинаковые. Значем нужно копировать? Вот это я пока не понял. Наверное пока сам "не потрогаю не пойму". ![]()
0
|
||||
|
Модератор
|
|||||||||||||||||||||||||
| 08.01.2023, 17:42 [ТС] | |||||||||||||||||||||||||
|
Сделано всё через рефлексию (для WPF .Net и UWP) и нужные фабрики определяются автоматически. Но вот как дальше развивать.... Получается нужно будет определять с десяток известных платформ. А если появится ещё какая-то? В рефлексии всё не учтёшь. Отсюда и появились мысли о том к какому конечному результату следует прийти. Реализовывал ещё вариант когда маршалинг встраивал в свои кастомные привязки. Работает неплохо, довольно логично. Но представил как это на практике в применении.... Нужно вместо дефолтных привязок во всём коде использовать мои. Точно неудобно. Я до сих пор никак понять не могу, почему в "родных" библах всё это не реализовано? На их уровне это добавить, вообще, как "два пальца...". Ещё и сделано как-то через ж.... в одной платформе для одной части есть маршалинг, на другой для другой. Какие-то "танцы с бубном" на пустом месте. Может чего-то недопонимаю? Такие сомнения тоже есть. Добавлено через 5 минут Тот же INPC нужно по разному подымать в WPF и UWP в многопоточном приложении. Из ViewModelBase для WPF - всё привычно:
Для RelayCommand наоборот. Для WPF нужен маршалинг:
HF, есть ещё вариант с определением потока при подключении прослушки. И если у него есть контекст, то подымать его в этом же потоке. Основано на этом коде: RelayCommandSyncContext. Добавлено через 56 минут Просто не знали об этом. По XAML кодогенератором создаётся файл *.g.i.cs, в котором содержится код частичного класса для Code Front.Именно из-за наличия этого файла вы и можете обращаться в Code Behind к элементам по именам. Для Форма файл другой, но по сути аналогично. Когенерация здесь не поможет. Она может, как например в ToolKit, автоматически для приватного поля создать публичное свойство для доступа у нему.
0
|
|||||||||||||||||||||||||
|
Модератор
|
||
| 08.01.2023, 18:05 [ТС] | ||
|
В тех же *.g.i.cs - это же коды классов.Но в данном случае мне же не писать под каждую VM свой кодогенератор. И как их потом будут факту использовать? Они рассчитаны, по больше части, на генерацию часто повторяющегося кода. Что-то вроде "навороченный сниппетов", которые создают код в отдельном файле и могут его самостоятельно очень гибко настраивать. Мне же нужен, какой-то простой, понятный всем способ смены базовых классов без затрагивания конечного кода создаваемого разработчиком. Для команд это более менее понятно. Они создаются экземплярами, поэтому фабрика хорошо подходит. Нужную. реализацию можно фиксировать при старте приложения (через какую-то службу). Но здесь тоже есть свои нюансы для Времени Разработки, так как в это время регистрация не будет исполняться. Или опять придётся придумывать какие-то неочевидные хитрости. А вот с VM, вообще, засада. Здесь же не экземпляр создаётся, а нужно создавать производный класс. И как здесь изменять базовый без пересборки - ума не приложу. Придётся наверное PropertyChanged делать прокси свойством. А событие подымать в каком-то встроенном внутреннем (приватном) экземпляре, который и будет меняться через фабрику или службу.
0
|
||
|
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
|
||||||||||
| 10.01.2023, 17:02 | ||||||||||
|
Разработка под разные UI-фреймворки здесь ничем не отличается от разработки под разные платформы: у каждого свои заморочки и полностью универсальным код может быть только тогда, когда все целевые платформы ведут себя абсолютно одинаково (зачем они тогда нужны разные?). Создаете два файла проекта: ViewModel.WPF.csproj и ViewModel.UWP.csproj с соответствующими зависимостями, помещаете их в одну папку, чтобы оба видели одну и ту же структуру папок и файлов с исходниками. Добавляете оба проекта в решение, в ViewModel.WPF устанавливаете флаг WPF, в ViewModel.UWP — UWP, потом в исходниках стратегично вставляете #if/#else. На вашем примере:
Код в результате один, с условными изменениями под индивидуальные заморочки фреймворка/платформы.
1
|
||||||||||
|
Модератор
|
|||
| 10.01.2023, 19:50 [ТС] | |||
|
Но как заставить UWP самостоятельно маршалировать PropertyChanged в UI поток? Это без проблем можно встроить в привязки, но для этого -же надо менять их исходный код. Можно создать свой класс привязок, но с практическим их применением тоже будет не мало головняков. Я поэтому и интересуюсь как концептуально и практически лучше сделать. И кроме директив предпроцессора нужно ещё и разные ссылки на сборки подключать. Вариант с поздним связыванием (на уровня слушателя - View) концептуально - идеален. Но я не понимаю как его на практике "красиво" реализовать. Сейчас получилось сделать только через рефлексию. Определяю какие сборки загружены приложением и вытягиваю или System.Windows.Threading.Dispatcher или Windows.UI.Core.CoreDispatcher. Но что-то мне кажется это так "грязно"....
0
|
|||
|
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
|
|||
| 10.01.2023, 20:07 | |||
|
По этой же причине все кроссплатформенные проекты делают отдельные сборки под каждую платформу/фреймворк — тот же CommunityToolkit. Ну или копировать код, но так в здравом уме никто не делает.
0
|
|||
|
Модератор
|
|||
| 10.01.2023, 21:22 [ТС] | |||
|
"Беда" рефлексии - это то что заранее нужно прописывать все возможные платформы. То есть нужно заранее для всех платформ прописать реализацию платформозависимых типов и членов.
0
|
|||
|
|
|||
| 10.01.2023, 21:44 | |||
|
Но вы снова начинаете возвращаться к коду зависимому от платформ. А значит - или вы не нашли этот "базовый универсальный код" и вы всё равно гнёте свою линию. ![]() Или же в вашей реализации это невозможно и нужно и можно "копипастить". kolorotur предложил отличный вариант - с управляющими командами компиляции. Про них я и забыл. Действительно, хоть это грязно, но если такого кода не много, то это намного универсальнее, чем копипастить кучу классов. Даже вот так. Вопрос - не уже ли код через рефлексию будет универсальным и поддерживаемым? Мне кажется вы сделаете что-то рабочее, но на один раз. Больше вы его видеть не захотите вообще.
0
|
|||
|
Модератор
|
|||
| 10.01.2023, 23:21 [ТС] | |||
|
Я пишу как у меня сделано. И тему эту завёл потому, что мне это не нравится. Те же привязки "под капотом" полностью построены на рефлексии. Иначе как можно было бы по имени свойства получать и задавать его значение? Что-то супер-пупер универсальное, абстрактное приходится строить на рефлексии. Недавно делал что-то типа автотаблицы свойств для любого типа. Как тут без рефлексии. В данном случае я пока ищу лучшее решение. Из тех что уже реализовал 1) Это вариант на репозиторий которого давал ссылку выше https://github.com/EldHasp/CrossPlatformViewModel В нём VM и её базовые классы - отдельные ветки для каждой платформы; 2) Вариант на рефлексии. Пока реализован только для команд. В нём есть универсальная фабрика команд в которой при старте регистрируется нужный базовый тип. Что мне кажется оптимальным. Это перенос всего зависимого от платформы View кода, собственно в View. На моём уровне знаний это можно сделать двумя способами: 3) Применением кастомных привязок в которых встроена адаптация объектов источников под платформу View; 4) Инжекция нужных типов в фабрику, которая будет создавать их экземпляры. Это то что вы предлагали. Первый вариант сильно изменит XAML компоновки. Второй вызывает конфликты в Дизайнере XAML в Режиме Разработки, которые пока я не смог придумать как обойти. К сожалению в WPF многие вещи в рантайм и в дизайнере работаю по разному. Вариант от kolorotur, если я правильно, это немного усовершенствованный вариант 1. Есть ещё идейно близкий вариант - предложенный вами вариант с кодогенератором, который вставляет код с нужным базовым классом.
1
|
|||
|
|
||
| 11.01.2023, 00:06 | ||
|
Реалвью в дизайнере это конечно клёво и удобно, но если только это останавливает вас реализовать более менее приемлимый вариант - то подумайте ещё раз. Мы жили без реалвью и уж можно наверное ради такого ещё поработать без него? (я правильно понял о чём речь? - когда он сразу отображает дизайн с наполнением, хотя это было реализовано через код а не через дизайн. Фишки позволяющие "подсовывать реалистичную симуляцию" (уж не знаю как это назвать)).
0
|
||
|
Модератор
|
|||
| 11.01.2023, 10:02 [ТС] | |||
|
В View можно определять это Режим разработки или нет. По крайней мере для WPF и UWP точно можно. И можно этот параметр передать в VM или вызывать VM Времени Разработки. VM, соответственно, подсовывает демо данные для этого режима. Можно прокидывать параметр и дальше в сервисы и Модель чтобы те тоже возвращали демо данные. И получается очень удобная разработка и одновременная интерактивная отладка компоновки. Это всё нормально работает и надо только уметь этим пользоваться. Засада кроется немного в другом. В данном, случае для DI нужна регистрация. Делать её нужно в CB App. В конструкторе или событии Startup. А CB в Режиме разработки не исполняется. Получается что нет и регистрации. И теперь если я в VM буду создавать (например) команду тип которой должен получить от DI, то поймаю исключение. И Дизайнер XAML обрушится. В рантайм оно-то всё будет работать. Но создавать "всплепую" XAML компоновку... так себе удовольствие. Да и есть достаточно платформ для MVVM чтобы конкурировать с ними. Данный вопрос это скорее для собственного развития, изучения "труднодоступных" возможностей WPF/UWP и дань перфекционизму.
0
|
|||
|
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759
|
|||||||||||||
| 11.01.2023, 12:17 | |||||||||||||
Реализация клиентского кода - соблюдение некоторых соглашений по используемым типам и начальной инициализации. Но я это только на уровне экспериментов реализовывал для INPC (вместо Fody) и команд. В целом, работает, но на реальных проектах опыта не было.
0
|
|||||||||||||
|
Модератор
|
|||
| 11.01.2023, 12:30 [ТС] | |||
|
Подойдёт для команд, так как это свойства. Но для смены базового класса VM, да, ещё в рантайм ... вряд ли, я думаю.
0
|
|||
| 11.01.2023, 12:30 | |
|
Помогаю со студенческими работами здесь
17
Платформо-независимая связка QDataStream >> QBitArray >> базовый тип Независимая форма
Независимая страница в wordperess Независимая копия террейна Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |
|
Новые блоги и статьи
|
||||
|
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта
Симптом:
После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
|
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
|
Новый ноутбук
volvo 07.12.2025
Всем привет.
По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне:
Ryzen 5 7533HS
64 Gb DDR5
1Tb NVMe
16" Full HD Display
Win11 Pro
|
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
|
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
|
|
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов
На странице:
https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/
нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
|
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов.
. . .
|
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
|
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
|
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут.
В век Веб все очень привыкли к дизайну Single-Page-Application .
Быстренько разберем подход "на фреймах".
Мы делаем одну. . .
|