В этой статье мы
продолжим разговор на тему о паттернах взаимодействия с пользователем с помощью
библиотеки 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.
{
// 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