Sunday, June 1, 2014

Использование объектов запроса взаимодействия в Prism 5

В этой статье мы продолжим разговор на тему о паттернах взаимодействия с пользователем с помощью библиотеки Prism 5. В предыдущей статье мы рассмотрели один из таких паттернов, который был построен на основе использования службы взаимодействия. Сегодня же рассмотрим другой подход для простого взаимодействия с пользователем, который с помощью объекта запроса позволяет общаться модели представления с запросом непосредственно к представлению.
Объект запроса взаимодействия инкапсулирует детали запроса и его ответ, а также позволяет связаться с представлением через события. Представление подписывается на эти события, чтобы инициировать пользовательскую часть взаимодействия. Представление обычно будет инкапсулировать пользовательское взаимодействие в поведении, которое связывается с данными к объекту запроса взаимодействия, предоставленному моделью представления, как показано в следующей иллюстрации, взятой с официальной документации.
Благодаря этому подходу сохраняется разделение между представлением и моделью представления, что позволяет модели представления инкапсулировать логику представления, включая любое необходимое взаимодействие с пользователем, а представлению – полностью инкапсулировать визуальные аспекты взаимодействия. Этот подход отлично ложится на паттерн MVVM и позволяет представлению отражать изменения состояния, за которым оно наблюдает в модели представления, и использовать двухстороннюю привязку для передачи данных между ними. Инкапсуляция не визуальных элементов взаимодействия в объекте запроса взаимодействия, и использование соответствующего поведения для управления визуальными элементами, очень подобны объектам команды и объектам поведения.
В Prism реализация этого паттерна основывается на поддержке интерфейса IInteractionRequest и класс InteractionRequest<T>. Реализация интерфейса IInteractionRequest очень простая и приведена ниже.
// Summary:
       //     Represents a request from user interaction.
       //
       // Remarks:
       //     View models can expose interaction request objects through properties and
       //     raise them when user interaction is required so views associated with the
       //     view models can materialize the user interaction using an appropriate mechanism.
       public interface IInteractionRequest
       {
             // Summary:
             //     Fired when the interaction is needed.
             event EventHandler<InteractionRequestedEventArgs> Raised;
       }
Там всего лишь одно событие, которое позволяет инициировать взаимодействие. Класс InteractionRequest<T> реализует интерфейс IInteractionRequest и используется для координации взаимодействия модели представления с представлением во время запроса взаимодействия.
public class InteractionRequest<T> : IInteractionRequest where T : Microsoft.Practices.Prism.Interactivity.InteractionRequest.INotification
       {
             public InteractionRequest();

             // Summary:
             //     Fired when interaction is needed.
             public event EventHandler<InteractionRequestedEventArgs> Raised;

             // Summary:
             //     Fires the Raised event.
             //
             // Parameters:
             //   context:
             //     The context for the interaction request.
             public void Raise(T context);
             //
             // Summary:
             //     Fires the Raised event.
             //
             // Parameters:
             //   context:
             //     The context for the interaction request.
             //
             //   callback:
             //     The callback to execute when the interaction is completed.
             public void Raise(T context, Action<T> callback);
       }
Как видим, класс InteractionRequest<T> реализует два метода Raise, чтобы позволить модели представления инициировать взаимодействия и для определения контекста запроса, а также, дополнительно, делегата обратного вызова. Интересная особенность, что в Prism 5 в данном классе нет никакого отличия от версии Prism 4.1. Поэтому если вы уже работали с данным классом, то у вас не возникнет проблем и сейчас. Prism предоставляет предопределённые классы контекста, который содержит общие сценарии запроса взаимодействия.
Класс Notification является базовым классом для всех объектов контекста. Он нужен в случае, когда запрос взаимодействия используется для уведомления пользователя о важном событии в приложении. Он имеет два свойства: Title и Content, которые будут показаны пользователю. Как правило, уведомления являются односторонними, таким образом, не ожидается, что пользователь изменит эти значения во время взаимодействия.
Класс Confirmation наследуется от класса Notification и добавляет третье свойство – Confirmed, которое используется, чтобы показать, что пользователь подтвердил или отменил запрос. Класс Confirmation используется, чтобы реализовать взаимодействия стиля MessageBox, где необходимо получить ответ да/нет от пользователя. Можно определить пользовательский класс контекста, который наследуется от класса Notification, для инкапсуляции данных и состояний, которые необходимы для взаимодействия с пользователем.
Чтобы использовать класс InteractionRequest<T>, класс модели представления должен создать экземплярInteractionRequest<T> и определить свойство только для чтения, чтобы позволить представлению связаться с ним. Когда модель представления захочет инициировать запрос, она вызовет метод Raise, передавая объект контекста и, дополнительно, делегат обратного вызова.
Одним из интересных новшеств в Prism 5 стало добавление интерфейса IInteractionRequestAware, который позволяет использовать базовый класс Notification или Confirm для объекта контекста, благодаря свойству Notification. А также событие FinishInteraction, для того чтобы уведомить о завершении показа всплывающего окна.
// Summary:
       //     Interface used by the Microsoft.Practices.Prism.Interactivity.PopupWindowAction.
       //      If the DataContext object of a view that is shown with this action implements
       //     this interface it will be populated with the Microsoft.Practices.Prism.Interactivity.InteractionRequest.INotification
       //     data of the interaction request as well as an System.Action to finish the
       //     request upon invocation.
       public interface IInteractionRequestAware
       {
             // Summary:
             //     An System.Action that can be invoked to finish the interaction.
             Action FinishInteraction { get; set; }
             //
             // Summary:
             //     The Microsoft.Practices.Prism.Interactivity.InteractionRequest.INotification
             //     passed when the interaction request was raised.
             INotification Notification { get; set; }
       }
Давайте рассмотрим пример использования данного сценария взаимодействия с пользователем. Для этого создадим новое WPF приложение, которое назовем InteractionRequestSample.
Затем по старинке, как описано в статье "Введение в Prism 5. Bootstrapper", реализуем начальную структуру проекта. Первым делом добавим в проект папку Models, для того чтобы хранить там модель Book. В этой модели мы будем хранить информацию о книгах. Мы создадим popup окно, в котором добавим возможность выбирать себе книгу. Реализация модели Book показана в примере ниже.
public class Book : BindableBase
{
       private long _id;
       public long Id
       {
             get { return _id; }
             set
             {
                    _id = value;
                    OnPropertyChanged(() => Id);
             }
       }

       private string _author;
       public string Author
       {
             get { return _author; }
             set
             {
                    _author = value;
                    OnPropertyChanged(() => Author);
             }
       }

       private string _title;
       public string Title
       {
             get { return _title; }
             set
             {
                    _title = value;
                    OnPropertyChanged(() => Title);
             }
       }

       private DateTime _year;
       public DateTime Year
       {
             get { return _year; }
             set
             {
                    _year = value;
                    OnPropertyChanged(() => Year);
             }
       }

       private string _sn;
       public string SN
       {
             get { return _sn; }
             set
             {
                    _sn = value;
                    OnPropertyChanged(() => SN);
             }
       }

       private double _price;
       public double Price
       {
             get { return _price; }
             set
             {
                    _price = value;
                    OnPropertyChanged(() => Price);
             }
       }
}
Следующим делом добавим новый класс ItemSelectionNotification, который наследуем от класса Confirmation, о котором шла речь выше. Этот класс будет реализовывать необходимую нам логику по работе с книгами. Мы можем добавить этот класс в папку Models, либо создать отдельно папочку Helpers или Notifications и добавить этот класс туда. Если вы хотите создавать кастомные сообщения от пользователя, которые будут вам говорить о действиях пользователя, то больше пользы будет, если вынести в отдельный слой и назвать этот слой Notifications. Так вы будете знать, где у вас лежит вся логика.  
public class ItemSelectionNotification : Confirmation
{
       public ItemSelectionNotification(IEnumerable<Book> items)
       {
             Items = new ObservableCollection<Book>(items);
       }

       public ObservableCollection<Book> Items { get; private set; }

       public Book SelectedItem { get; set; }
}
Для того чтобы проверить разницу в реализации дефолтного использования Popup окон, перейдем в папку Views и добавим новый контрол CustomPopupView.xaml. Реализация этого контрола выглядит так:
<UserControl x:Class="InteractionRequestSample.Views.CustomPopupView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             Height="100"
             d:DesignHeight="100" d:DesignWidth="300">
    <Grid>
        <TextBlock Text="Popup window sample"></TextBlock>
        <Button Margin="10" VerticalAlignment="Bottom"  HorizontalAlignment="Right" Width="75" Click="Button_Click">Close</Button>
    </Grid>
</UserControl>
Как видим, в UI логике, по сути, ничего нет, кроме текстового поля и кнопки. Вот с этой кнопкой, по сути, и связана вся основная логика. Для этого перейдем в класс CustomPopupView.xaml.cs и реализуем обработчик события этой кнопки.
public partial class CustomPopupView : UserControl, IInteractionRequestAware
{
       public CustomPopupView()
       {
             InitializeComponent();
       }

       private void Button_Click(object sender, RoutedEventArgs e)
       {
             if (FinishInteraction != null)
                    FinishInteraction();
       }

       public Action FinishInteraction { get; set; }

       public INotification Notification { get; set; }
}
Основную логику в данном примере играет интерфейс IInteractionRequestAware. Этот интерфейс я описал ранее. С помощью события FinishInteraction мы вызываем событие завершения обмена с пользователем. Давайте также реализуем до конца наше кастомное окно, которое позволит зафиксировать, какую книгу выбрал пользователь. Для этого создадим модель представления BookSelectionViewModel, которая возьмёт на себя обработку взаимодействия с пользователем.
public class BookSelectionViewModel : BindableBase, IInteractionRequestAware
{
       private ItemSelectionNotification _notification;

       public BookSelectionViewModel()
       {
             SelectItemCommand = new DelegateCommand(AcceptSelectedItem);
             CancelCommand = new DelegateCommand(CancelInteraction);
       }

       // Both the FinishInteraction and Notification properties will be set by the PopupWindowAction
       // when the popup is shown.
       public Action FinishInteraction { get; set; }

       public INotification Notification
       {
             get
             {
                    return _notification;
             }
             set
             {
                    if (value is ItemSelectionNotification)
                    {
                           _notification = value as ItemSelectionNotification;
                           this.OnPropertyChanged(() => Notification);
                    }
             }
       }

       public Book SelectedItem { get; set; }

       public ICommand SelectItemCommand { get; private set; }

       public ICommand CancelCommand { get; private set; }

       public void AcceptSelectedItem()
       {
             if (_notification != null)
             {
                    _notification.SelectedItem = SelectedItem;
                    _notification.Confirmed = true;
             }

             FinishInteraction();
       }

       public void CancelInteraction()
       {
             if (_notification != null)
             {
                    _notification.SelectedItem = null;
                    _notification.Confirmed = false;
             }

             FinishInteraction();
       }
}
В нашей модели представления есть две кнопки. Одна фиксирует выбор пользователем книги, вторая просто отвечает за отмену выбора. Ниже представлен xaml-код окна BookSelectionView.
<UserControl x:Class="InteractionRequestSample.Views.BookSelectionView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             Height="180"
             d:DesignHeight="180" d:DesignWidth="300">
    <Grid>
        <StackPanel>
            <ListBox SelectionMode="Single" Margin="10" Height="100" ItemsSource="{Binding Notification.Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock Text="{Binding Author}" />
                            <TextBlock Text="{Binding Title}" />
                            <TextBlock Text="{Binding Price, StringFormat={}{0:C}}" FontWeight="Bold" />
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>

                <Button AutomationProperties.AutomationId="ItemsSelectButton" Grid.Column="0" Margin="10" Command="{Binding SelectItemCommand}">Select Item</Button>
                <Button AutomationProperties.AutomationId="ItemsCancelButton" Grid.Column="1" Margin="10" Command="{Binding CancelCommand}">Cancel</Button>
            </Grid>
        </StackPanel>
    </Grid>
</UserControl>
Визуальное представление окна в дизайнере приведено ниже.
Для данного окна я пошел на очень некрасивый шаг. Поскольку нам нужно для данного окна установить свойство DataContext в модель представления BookSelectionViewModel, можно добавить в конструктор класса BookSelectionView установку данного свойства, например, вот так:
/// <summary>
/// Interaction logic for BookSelectionView.xaml
/// </summary>
public partial class BookSelectionView : UserControl
{
       public BookSelectionView()
       {
             InitializeComponent();
             DataContext = new BookSelectionViewModel();
       }
}
Это самый простой и самый неправильный способ. Дело в том, что одна абстракция у нас просто утекла с нашего IoC контейнера. Вторая проблема заключается в том, что для того чтобы решить данную проблему, нам понадобится использовать Property Injection для установки свойства DataContext. Такое понятие в мире внедрения зависимостей известно как Resolving Cyclic Dependencies и неплохо описано в книге Марка Симана  "Dependency Injection in .NET".
One of the most common situations where we can’t redesign our way out of a DEPENDENCY cycle is when we deal with external APIs. One such example is WPF. In WPF, we can use the MVVM pattern to implement separation of concerns by splitting the code into Views and underlying models. The models are assigned to the Views through a DataContext property. This is essentially PROPERTY INJECTION at work.
A DataContext serves as the Window’s DEPENDENCY, but the model plays a large part in controlling which Views are activated where. One of the actions a model must be able to perform is to pop up a dialog box
Не правда ли, подобно той проблеме, которая возникла у нас. Марк предлагает рассмотреть, как можно решить такую проблему.
We need to find a relationship where we can cut the cycle to introduce PROPERTY INJECTION. In this case it’s easy, because the relationship between a WPF Window and a ViewModel already uses PROPERTY INJECTION. This is where we’ll cut.
The simplest solution is to wire up anything else and set the DataContext property
on the MainWindow as the last thing before showing it. This is possible but not particularly DI CONTAINER–friendly, because it would require us to explicitly assign a DEPENDENCY after composition has been performed.
As an alternative, we can encapsulate this deferred assignment in a lazy-loading adapter. This enables us to wire up everything properly with a DI CONTAINER
В своей замечательной книге он приводит несколько вариантов решения этой проблемы. Но для написания адаптера, или фабрики, или использования сервисов, как в предыдущей статье, для нашего примера сойдет и такое решение для презентации работы. Если взять пример InteractivityQuickstart, который идет в поставке с Prism 5, то там разработчики не ставили вообще за цель правильность написания. Часть кода задано просто через xaml, жёсткая привязка данных и т.д. Но для того чтобы вы знали, как сделать правильно, чтобы работало, нужно использовать Property Injection или Method Injection. И лучше это все вынести в отдельный интерфейс или службу. Но в нашем примере есть проблема, которая зарыта намного глубже. И как только мы дойдем до создания главного окна, мы ее рассмотрим. 
Мы слишком отклонились от темы, пора сделать основную модель представления ShellViewModel, чтобы продемонстрировать процесс взаимодействия с пользователем.
public class ShellViewModel : BindableBase
{
       public ShellViewModel()
       {
             ConfirmationRequest = new InteractionRequest<IConfirmation>();
             NotificationRequest = new InteractionRequest<INotification>();
             CustomPopupViewRequest = new InteractionRequest<INotification>();
             ItemSelectionRequest = new InteractionRequest<ItemSelectionNotification>();

             //Commands
             RaiseConfirmationCommand = new DelegateCommand(RaiseConfirmation);
             RaiseNotificationCommand = new DelegateCommand(RaiseNotification);
             RaiseCustomPopupViewCommand = new DelegateCommand(RaiseCustomPopupView);
             RaiseItemSelectionCommand = new DelegateCommand(RaiseItemSelection);
       }

       #region Public Properties
       private string _resultMessage;
       public string InteractionResultMessage
       {
             get
             {
                    return _resultMessage;
             }
             set
             {
                    _resultMessage = value;
                    OnPropertyChanged(() => InteractionResultMessage);
             }
       }
       #endregion

       #region InteractionRequest
       public InteractionRequest<IConfirmation> ConfirmationRequest { get; private set; }

       public InteractionRequest<INotification> NotificationRequest { get; private set; }

       public InteractionRequest<INotification> CustomPopupViewRequest { get; private set; }

       public InteractionRequest<ItemSelectionNotification> ItemSelectionRequest { get; private set; }
       #endregion

       #region Command
       public ICommand RaiseConfirmationCommand { get; private set; }

       public ICommand RaiseNotificationCommand { get; private set; }

       public ICommand RaiseCustomPopupViewCommand { get; private set; }

       public ICommand RaiseItemSelectionCommand { get; private set; }
       #endregion

       #region Private Methods
       private void RaiseItemSelection()
       {
             var notification = new ItemSelectionNotification(GenerateBooks());

             notification.Title = "Books";

             // The custom popup view in this case has its own view model which implements the IInteractionRequestAware interface
             // therefore, its Notification property will be automatically populated with this notification by the PopupWindowAction.
             // Like this that view model is able to recieve data from this one without knowing each other.
             InteractionResultMessage = "";
             ItemSelectionRequest.Raise(notification,
                    returned =>
                    {
                           if (returned != null && returned.Confirmed && returned.SelectedItem != null)
                           {
                                  InteractionResultMessage = "The user selected: " + returned.SelectedItem.Author + " "
                                        + returned.SelectedItem.Price.ToString("C");
                           }
                           else
                           {
                                  InteractionResultMessage = "The user cancelled the operation or didn't select an item.";
                           }
                    });
       }

       private void RaiseCustomPopupView()
       {
             InteractionResultMessage = "";
             CustomPopupViewRequest.Raise(
                    new Notification { Content = "Message for the CustomPopupView", Title = "Custom Popup" });
       }

       private void RaiseNotification()
       {
             NotificationRequest.Raise(
                    new Notification { Content = "Notification Message", Title = "Notification" },
                    n => { InteractionResultMessage = "The user was notified."; });
       }

       private void RaiseConfirmation()
       {
             ConfirmationRequest.Raise(
                    new Confirmation { Content = "Confirmation Message", Title = "Confirmation" },
                    c => { InteractionResultMessage = c.Confirmed ? "The user accepted." : "The user cancelled."; });
       }
       #endregion

       #region static methdos
       public static IEnumerable<Book> GenerateBooks()
       {
             yield return new Book { Id = 1, Author = "Jon Skeet", Title = "C# in Depth", Price = 22.5, SN = "ISBN: 9781617291340", Year = new DateTime(2013, 9, 10) };
             yield return new Book { Id = 2, Author = "Martin Fowler", Title = "Refactoring: Improving the Design of Existing Code", Price = 41.52, SN = "ISBN-10: 0201485672", Year = new DateTime(1999, 7, 8) };
             yield return new Book { Id = 3, Author = "Jeffrey Richter", Title = "CLR via C# (Developer Reference)", Price = 35, SN = "ISBN-10: 0735667454", Year = new DateTime(2012, 12, 4) };
       }
       #endregion
}
В данной модели было создано 4 команды для демонстрации разных вариантов использования классов Notification, Confirm, а также использование интерфейса IInteractionRequestAware. Как это изменит нашу оболочку Shell.xaml, вы можете увидеть ниже.
<Window x:Class="InteractionRequestSample.Shell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
        xmlns:interactivity="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:views="clr-namespace:InteractionRequestSample.Views"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <i:Interaction.Triggers>

            <!-- All the InteractionRequestTriggers here subscribe to the corresponding interaction requests through simple bindings -->
            <!-- In this case all of them raise a PopupWindowAction, but you can use other actions too -->

            <prism:InteractionRequestTrigger SourceObject="{Binding ConfirmationRequest, Mode=OneWay}">
                <!-- This PopupWindowAction does not have a custom view defined, therefore it will try to use a DefaultNotificationWindow -->
                <!-- which is a window used by default by Prism to shown INotification implementations -->
                <!-- That window will be show as a modal dialog and centered over this window -->
                <interactivity:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True"/>
            </prism:InteractionRequestTrigger>

            <prism:InteractionRequestTrigger SourceObject="{Binding NotificationRequest, Mode=OneWay}">
                <!-- This PopupWindowAction does not have a custom view defined, therefore it will try to use a DefaultConfirmationWindow -->
                <!-- which is a window used by default by Prism to shown IConfirmation implementations -->
                <!-- That window will be show as a modal dialog and centered over this window -->
                <interactivity:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True"/>
            </prism:InteractionRequestTrigger>

            <prism:InteractionRequestTrigger SourceObject="{Binding CustomPopupViewRequest, Mode=OneWay}">
                <!-- This PopupWindowAction has a custom view defined. When this action is executed the view will be shown inside a new window -->
                <!-- Take into account that the view is created only once and will be reused each time the action is executed -->
                <interactivity:PopupWindowAction>
                    <interactivity:PopupWindowAction.WindowContent>
                        <views:CustomPopupView />
                    </interactivity:PopupWindowAction.WindowContent>
                </interactivity:PopupWindowAction>
            </prism:InteractionRequestTrigger>

            <prism:InteractionRequestTrigger SourceObject="{Binding ItemSelectionRequest, Mode=OneWay}">
                <!-- This PopupWindowAction has a custom view defined. When this action is executed the view will be shown inside a new window -->
                <!-- Take into account that the view and its view model are created only once and will be reused each time the action is executed -->
                <interactivity:PopupWindowAction>
                    <interactivity:PopupWindowAction.WindowContent>
                        <views:BookSelectionView />
                    </interactivity:PopupWindowAction.WindowContent>
                </interactivity:PopupWindowAction>
            </prism:InteractionRequestTrigger>

        </i:Interaction.Triggers>
        <StackPanel>
            <Button Margin="5" Content="Raise Default Notification" Command="{Binding RaiseNotificationCommand}"  Width="250"/>
            <Button Margin="5" Content="Raise Default Confirmation" Command="{Binding RaiseConfirmationCommand}" Width="250"/>
            <Button Margin="5" Content="Raise Custom Popup View Interaction" Command="{Binding RaiseCustomPopupViewCommand}" Width="250"/>
            <Button Margin="5" Content="Raise Item Selection Popup" Command="{Binding RaiseItemSelectionCommand}" Width="250"/>
        </StackPanel>
        <TextBlock AutomationProperties.AutomationId="ResultTextBlock" Margin="5" FontWeight="Bold" Foreground="DarkRed" VerticalAlignment="Bottom" Text="{Binding InteractionResultMessage}"/>
    </Grid>
</Window>
Визуальное представление можно посмотреть на рисунке ниже.
Теперь для нашей оболочки нужно установить модель представления ShellViewModel как свойство DataContext оболочки Shell. Это нужно сделать в нашем загрузчике Bootstrapper.
public class Bootstrapper : UnityBootstrapper
{
       protected override DependencyObject CreateShell()
       {
             return Container.Resolve<Shell>();
       }

       protected override void InitializeShell()
       {
             Application.Current.MainWindow = (Window) Shell;
             var viewModel = Container.Resolve<ShellViewModel>();
             Application.Current.MainWindow.DataContext = viewModel;
             Application.Current.MainWindow.Show();
       }
}
После того как мы проделали всю работу, можно запустить наш пример и посмотреть на результат.
А теперь после того, как мы убедились, что все работает, рассмотрим, почему же это все работает. Дело в том, что вся работа по тому, какое показать окно, ложится на класс InteractionRequestTrigger. Представление должно быть настроено для обнаружения события запроса взаимодействия и предоставления соответствующего визуального представления для запроса. Microsoft Expression Blend Behaviors Framework поддерживает понятие триггеров и действий. Триггеры используются, чтобы инициировать действия всякий раз, когда определённое событие случается.
Стандартный EventTrigger, предоставленный Expression Blend, может использоваться, чтобы следить за событием запроса взаимодействия, связываясь с объектами запроса взаимодействия, представленными моделью представления. Однако Prism определяет пользовательский EventTrigger, названный InteractionRequestTrigger, который автоматически соединяется с соответствующим Raised событием интерфейса IInteractionRequest. Это уменьшает количество XAML и уменьшает шанс непреднамеренного введения неправильного имени события.
После того, как событие генерируется, InteractionRequestTrigger вызовет указанное действие.
Если внимательно посмотреть на код оболочки Shell, то можно увидеть там такой код:
<prism:InteractionRequestTrigger SourceObject="{Binding CustomPopupViewRequest, Mode=OneWay}">
    <!-- This PopupWindowAction has a custom view defined. When this action is executed the view will be shown inside a new window -->
    <!-- Take into account that the view is created only once and will be reused each time the action is executed -->
    <interactivity:PopupWindowAction>
        <interactivity:PopupWindowAction.WindowContent>
            <views:CustomPopupView />
        </interactivity:PopupWindowAction.WindowContent>
    </interactivity:PopupWindowAction>
</prism:InteractionRequestTrigger>
Именно благодаря этим строкам наш код и работает. Но поскольку наш xaml-код становится очень сильно напичкан разными событиями, в Visual Studio мы можем ловить время от времени такие ошибки как ошибка, приведенная ниже.
Это большая проблема, которая идет, начиная с Visual Studio 2010, и, к сожалению, не исправлена до сих пор в Visual Studio 2013. Как падал дизайнер в Visual Studio при работе со стилями из сторонних библиотек, если прописана какая-то сложная логика, так и падает время от времени. Правда, одну из проблем исправили разработчики Prism 5: добавили возможность работать с регионами в режиме дизайнера. Это является огромным плюсом, по сравнению с предыдущей версией, разумеется, в том случае если вам приходилось работать с регионами.  На этой ноте я, пожалуй, буду завершать данную статью. Статья получилась большой и не такой простой для понимания, как мне бы хотелось. Исходники к статье вы можете скачать по ссылке InteractionRequestSample

No comments:

Post a Comment