Я реализую модель представления, которая используется приложениями на нескольких платформах. Я использую MvvmCross v3, у которого есть собственный класс MvxEventToCommand, но я считаю, что проблема такая же для других фреймворков, таких как MVVM Light. Пока событие используется без параметров, реализация проста, и это относится к простым взаимодействиям, таким как касание элемента управления.
Но когда команде необходимо обрабатывать аргументы событий, все становится сложнее. Например, модель представления должна реагировать на определенные изменения полосы прокрутки (и загружать дополнительные элементы в связанное представление списка). Вот пример XAML:
<cmd:EventToCommand
Command="{Binding ScrollChanged}"
CommandParameter="{Binding EventArgs}" />
(MvvmCross использует MvxEventToCommand, но принцип тот же).
Тогда в моей модели у меня может быть следующий обработчик команд:
public ICommand ScrollChanged
{
get
{
return new RelayCommand<ScrollChangedEventArgs>(e =>
{
MessageBox.Show("Change!");
});
}
}
(MvxCommand в MvvmCross).
Проблема в том, что ScrollChangedEventArgs зависит от платформы, и этот код просто не скомпилируется в переносимой библиотеке классов. Это общая проблема с любой командой, которая требует не только отправки при запуске события, но и более конкретных сведений о событии. Перемещение этого кода в специфичную для платформы часть глупо, потому что это более или менее убивает концепцию переносимых моделей представлений и представлений без кода программной части. Я попытался найти проекты, в которых модели представления используются на разных платформах, но все они используют простые события, такие как «Tap», без прикрепленных сведений о событии.
ОБНОВЛЕНИЕ 1 Я согласен с замечанием Стюарта о том, что модели представления должны иметь дело только с абстракциями более высокого уровня, поэтому я перефразирую первоначальную проблему: как сопоставить результаты низкоуровневых взаимодействий с платформо-нейтральным событием, которое запускает команда бизнес-логики? Рассмотрим приведенный выше пример: команда бизнес-логики — «загрузить больше элементов в список», т. е. мы имеем дело с виртуализацией списка, когда изначально загружается ограниченное количество элементов из большой коллекции, и прокрутка вниз до конца списка должна вызывают загрузку дополнительных элементов.
WinRT может позаботиться о виртуализации списков, используя наблюдаемые коллекции, поддерживающие интерфейс ISupportIncrementalLoading. Среда выполнения обнаруживает эту возможность и автоматически запрашивает дополнительные элементы из соответствующей службы, когда пользователь прокручивает список вниз. На других платформах эта функция должна быть реализована вручную, и я не могу найти другого способа, кроме как реагировать на событие ScrollViewer ScrollChanged. Тогда я вижу еще два варианта:
- Поместите обработчик OnScrollChanged в файл кода программной части и вызовите событие более высокого уровня переносимой модели представления (например, «OnItemsRequested»);
- Избегайте кода программной части и изо всех сил пытайтесь связать событие ScrollChanged напрямую с моделью представления, тогда нам сначала нужно будет переназначить событие для конкретной платформы.
Пока нет поддержки второго варианта, размещение обработчика событий в файле кода программной части допустимо, если это делается с единственной целью сопоставления событий. Но я хотел бы исследовать, что можно сделать, используя второй вариант. У MvvmCross есть класс MapCommandParameter, который, похоже, может помочь, поэтому мне интересно, следует ли мне использовать его.
ОБНОВЛЕНИЕ 2 Я попробовал подход MapCommandParameter, и он сработал, позволив мне вставить адаптер для конкретной платформы, который будет сопоставлять низкоуровневые события для просмотра команд, специфичных для модели. Так что второй вариант сработал без проблем. Стюарт также предложил подклассы listview, поэтому нет необходимости заботиться о событиях прокрутки. Я планирую поиграть с ним позже.