MVVM: Предоставить сервис для работы с определенной частью пользовательского интерфейса? - PullRequest
5 голосов
/ 10 декабря 2011

скажем, в моем приложении есть пользовательский интерфейс для представления (географической) карты.Он интегрирован в приложение как UserControl и имеет свою модель представления.

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

Поэтому есть несколько вопросов:

  1. Имеет ли смысл и стоит ли в первую очередь внедрять такие сервисы (которые действуют как промежуточную ссылку на пользовательский интерфейс) в первую очередь?
  2. Поскольку сервис работает непосредственно на модели представления карты, должен ли онбыть самой моделью представления, которая реализует интерфейс службы?
  3. Уместно ли для интерфейса службы предоставлять события (например, помимо предоставления метода для изменения масштаба карты, предоставьте событие, что масштаб карты также был изменен)?Или предпочтительнее использовать какой-либо механизм вещания событий (агрегатор) для выталкивания таких уведомлений из сервисных интерфейсов?

Заранее благодарен за помощь.

Ответы [ 4 ]

3 голосов
/ 13 декабря 2011

Рассмотрите возможность использования Messenger в наборе инструментов MVVM Light.См. Больше в другом ответе SO:

https://stackoverflow.com/a/2700324/117625

2 голосов
/ 13 декабря 2011

EventAggregator - это еще один механизм, который устанавливает связь между моделями отключенного представления. Я полагаю, что все другие части вашего приложения будут использовать тот же MVVm и будут иметь viewmodel для выполнения операций. Опубликуйте событие, скажем, Zoom с необходимыми аргументами из других частей приложения и перехватите его на карте, используя механизм подписки.

http://msdn.microsoft.com/en-us/magazine/dd943055.aspx#id0420209

Prism имеет хорошую реализацию Event Aggregator. Вы можете использовать эту часть.

1 голос
/ 18 декабря 2011

Всякий раз, когда вам нужны две модели представления для связи (или что-то подобное, например, кнопка, которая хочет вызвать команду на модели представления, отличной от своей собственной), лучшая практика, которую я нашел, - это использовать канал обмена сообщениями. (MVVM Light имеет класс Messenger; и Prism, и Caliburn.Micro имеют EventAggregator.) Когда создается экземпляр команды (ваша модель представления карты), он регистрируется для определенных команд в канале обмена сообщениями. Когда инициатор (например, кнопка) создается, он может отправлять команды по тому же каналу. Это держит ваши компоненты слабо связаны. Команды из канала обмена сообщениями могут быть легко смоделированы для модульного тестирования. Это также открывает вам другие возможности, такие как одновременное открытие нескольких карт (просто используйте другой канал обмена сообщениями или какой-либо токен).

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

Пропустить события. Кажется, они ничего не добавляют.

1 голос
/ 13 декабря 2011

Что если бы у вас был агрегатный объект Command, который указывал соответствующее поведение? Я попытаюсь немного конкретизировать ваш вопрос в деталях, поправьте меня, если я ошибаюсь:

Предположим, что есть две соответствующие части вашего приложения - компонент карты, который можно масштабировать и панорамировать и т. Д., И набор элементов управления, которые представляют пользовательский интерфейс для масштабирования, панорамирования и выбора между ними - что-то вроде набор селекторов режимов. Вы не хотите, чтобы один из них имел прямую ссылку на другой, и соблазн состоит в том, чтобы карта знала непосредственно о своем наборе элементов управления, чтобы она могла отлавливать события из них и переключать состояние режима соответствующим образом.

Одним из способов позаботиться об этом может быть набор библиотек CompositeCommands (доступны из Prism Application Guidance ) внутри объекта, внедренного в каждую из них. Таким образом, вы получаете разделение и строгое описание интерфейса (вы также можете использовать события, если вы так склонны).

public class MapNavigationCommands{
  public static CompositeCommand startPanning = new CompositeCommand();
  public static CompositeCommand startZooming = new CompositeCommand();
  public static CompositeCommand setViewbox = new CompositeCommand();
}

Ваши элементы управления режимом вверху на ленте регистрируются в вашей DI-инфраструктуре для ее внедрения (не желая вводить DI в этом примере, я только что напрямую ссылался на эти статические элементы).

public class ModeControls : UserControl{
  ...
  public void PanButtonSelected(object sender, RoutedEventArgs e){
    MapNavigationCommands.StartPanning.Execute(this); //It doesn't really care who sent it, it's just good event practice to specify the event/command source.
  }
}

В качестве альтернативы в XAML:

...
  <Button Command={x:Static yourXmlns:MapNavigationCommands.StartPanning}>Start</Button>
...

Теперь, на стороне карты:

public class PannableMapViewModel{
  public PannableMapViewModel(){
    MapNavigationCommands.StartPanning.RegisterCommand(new DelegateCommand<object>(StartPanning));
    MapNavigationCommands.SetViewbox.RegisterCommand(new DelegateCommand<Rectangle>(SetViewBox));

  }
  private void StartPanning(object sender){
    this.SetMode(Mode.Pan); //Or as appropriate to your application.  The View is bound to this mode state
  }
  private void SetViewbox(Rectangle newView){
    //Apply appropriate transforms.  The View is bound to your transform state.
  }
}

Теперь у вас есть разрозненный строго определенный интерфейс между двумя элементами управления, поддерживающий разделение ViewModel, которое можно смоделировать для ваших тестов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...