PRISM 4: Диалоги (DialogModal) с пользователем или использование объектов Interaction Request

ru-RU | создано: 05.02.2011 | опубликовано: 05.02.2011 | обновлено: 01.01.2018 | просмотров за всё время: 7588

В шестой части руководства пользователя по PRISM 4, которая называется Advanced MVVM Scenarios есть немаловажный раздел "Использование интерактивного сервиса (Using Interaction Service)". В разделе описано как можно получать от пользователя результаты запроса относительно действий. Речь идет о диалоговых окнах, если быть проще. Расскажу как пользоваться этими объектами в этой статье.

"Я спросил у ясеня..."

Как поётся в одной хорошей песне, "...я спросил у ясеня, где моя любимая? Ясень не ответил мне..." - диалог не удался! В большенстве программ при работе с ней наблюдается потребность узнать у пользователя какую-то дополнительную информацию относительно какого-либо действия. Примером может быть подтверждение удаления какой-нибудь записи. Пользователь нажал кнопку [удалить], требуется получить подтверждение от пользователя его действий, задав соответствующий вопрос "Действительно ли Вы хотите удалить запись?". А вот после подтверждения можно уже и "рубить концы"...

Как можно спросить и/или показать...

Если Вы пробывали писать приложения (например на Silverlight) с использованием паттерна MVVM, то наверняка перед Вами возникал вопрос, как из ViewModel запустить диалог и получить ответ на этот запрос опять же во ViewModel. Существуют разные варианты реализации, одно время я даже пользовался своей разработкой. Но с момента появления PRISM 4 всё очень упростилось.

Создаем новое Silverlight-приложение, подключаем во вновь созданный  MainPageViewModel.cs сборки PRISM, которые потребуются для реализации хотелок.

using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Interactivity.InteractionRequest;
using Microsoft.Practices.Prism.ViewModel;

Далее, сразу же к свойствам перейдем:

#region свойства

public IInteractionRequest ShowWindowRequest
{
    get
    {
        return this.showWindowRequest;
    }
}
public IInteractionRequest ShowResultRequest
{
    get
    {
        return this.showResultRequest;
    }
}
public IInteractionRequest ShowCustomWindowRequest
{
    get
    {
        return this.showCustomWindowRequest;
    }
}
public IInteractionRequest ShowCustomResultRequest
{
    get
    {
        return this.showCustomResultRequest;
    }
}

#endregion

А теперь еще немножко полей:

#region поля
private InteractionRequest<Confirmation> showWindowRequest;
private InteractionRequest<Notification> showResultRequest;
private InteractionRequest<CustomConfirmation> showCustomWindowRequest;
private InteractionRequest<CustomNotification> showCustomResultRequest;
#endregion  

Интерфейс  IInteractionRequest выглядит так:

namespace Microsoft.Practices.Prism.Interactivity.InteractionRequest
{
    // 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;
    }
}

То есть имеет только одно событие Raised. Его-то и будет пользовать вдоль и поперек. Инициализируем переменные в конструкторе:

#region конструктор
public MainPageViewModel()
{
    if (!DesignerProperties.IsInDesignTool)
    {
        this.showWindowRequest = new InteractionRequest<Confirmation>();
        this.showResultRequest = new InteractionRequest<Notification>();
        this.showCustomWindowRequest = new InteractionRequest<CustomConfirmation>();
        this.showCustomResultRequest = new InteractionRequest<CustomNotification>();
    }
}
#endregion

Остальной код из ViewModel пока опустим и перейдем к разметки. В XAML подключим ViewModel простым способом:

<UserControl.DataContext>
    <local:MainPageViewModel />
</UserControl.DataContext>

Теперь надо подключить namespace'ы для достижения поставленной цели.

xmlns:local="clr-namespace:SilverlightApplication9"
xmlns:prism="http://www.codeplex.com/prism"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei=http://schemas.microsoft.com/expression/2010/interactions

Первая - это ссылка на локалный проект, вторая подключает наработки PRISM (Interaction Request objects), третья и четвертая - это, как видно, библиотека Expression.

Теперь приведу код непостредственно этих самых привязок (код триггеров):

<i:Interaction.Triggers>
    <prism:InteractionRequestTrigger
        SourceObject="{Binding ShowWindowRequest}">
        <prism:PopupChildWindowAction />
    </prism:InteractionRequestTrigger>
    <prism:InteractionRequestTrigger
        SourceObject="{Binding ShowResultRequest}">
        <prism:PopupChildWindowAction />
    </prism:InteractionRequestTrigger>
    <prism:InteractionRequestTrigger
        SourceObject="{Binding ShowCustomWindowRequest}">
        <prism:PopupChildWindowAction>
            <prism:PopupChildWindowAction.ChildWindow>
                <local:CustomChildWindow
                    DataContext="{Binding}" />
            </prism:PopupChildWindowAction.ChildWindow>
        </prism:PopupChildWindowAction>
    </prism:InteractionRequestTrigger>
    <prism:InteractionRequestTrigger
        SourceObject="{Binding ShowCustomResultRequest}">
        <prism:PopupChildWindowAction>
            <prism:PopupChildWindowAction.ChildWindow>
                <local:ResultChildWindow
                    DataContext="{Binding}" />
            </prism:PopupChildWindowAction.ChildWindow>
        </prism:PopupChildWindowAction>
    </prism:InteractionRequestTrigger>
</i:Interaction.Triggers>

Привязки помечены жирным - это те самые свойства из ViewModel, которые мы выставили наружу в виде свойств. Всю работу за нас делает InteractionRequestTrigger, который вызывает подготовленные окна диалогов. Окна диалогов, как не трудно заметить, основываются на ChildWindow.

Вызов диалога

Из ViewModel вызываем стандартный диалог по нажатию на кнопку, вернее дилалог со стандартным видом окна:

// выполнение команды ShowWindow
this.showWindowRequest.Raise(new Confirmation() { 
Content = "Вы действительно в самом деле?", 
Title = "Подтверждение" },
    (c) =>
    {
        if (c.Confirmed)
        {
            this.showResultRequest.Raise(new Notification()
            {
                Content = "Спасибо что Вы в натуре!",
                Title = "Уведомление"
            });
        }
        else
        {
            this.showResultRequest.Raise(new Notification()
            {
                Content = "Жаль что Вы  не с нами!",
                Title = "Уведомление"
            });
        }
    });

Теперь следует сделать некоторое отступление, и внести пояснения (извините за каламбур).  В PRISM встроены стандартные окна диалогов, которые используются если в качестве параметра Context в методе Raise идет стандарный тип (Notification или Confirmation). Как не трудно догадаться, окна для таких диалогов будут иметь NotificationChildWindow и ConfirmationChildWindow соответственно. Но можно использовать и собственные варианты диалоговых окон. Достаточно просто создать новое ChildWindow и подставить его как параметр:

<prism:InteractionRequestTrigger
    SourceObject="{Binding ShowCustomWindowRequest}">
    <prism:PopupChildWindowAction>
        <prism:PopupChildWindowAction.ChildWindow>
            <local:CustomChildWindow DataContext="{Binding}" />
        </prism:PopupChildWindowAction.ChildWindow>
    </prism:PopupChildWindowAction>
</prism:InteractionRequestTrigger>  

Как показано выше. Для стандартного типа окна диалога такого делать не требуется.

<prism:InteractionRequestTrigger
    SourceObject="{Binding ShowWindowRequest}">
    <prism:PopupChildWindowAction />
</prism:InteractionRequestTrigger>

Если требуется использовать нестандартный вариант диалогового окна, то следует создать класс-насследник от Notification или Confirmation. В данном примере, так и сделано:

public class CustomConfirmation : Confirmation, INotifyPropertyChanged
{
    #region свойство Result

    /// <summary>
    /// поле для хранения значений свойства <see cref="Result"/>
    /// </summary>
    private bool? result;

    /// <summary>
    /// описание.
    /// </summary>
    public bool? Result
    {
        get
        {
            return result;
        }
        set
        {
            result = value;
            RaisePropertyChanged("Result");
        }
    }

    #endregion свойство Result

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion
}

И теперь, раз уж создали свой собственный аргумент для метода Raise, то вызвать диалог с нестандартным видом окна - легкотня! :) 

this.showCustomWindowRequest.Raise(new CustomConfirmation() { 
Content = "Спрашиваю еще раз! Вы действительно в самом деле в натуре?", 
Title = "Снова подтверждение" },
   (c) =>
   {
       if ((c.Result.HasValue) && (c.Result.Value))
       {
           this.showCustomResultRequest.Raise(new CustomNotification()
           {
               Content = "Спасибо за то, что Вы в натуре как надо! Ура!",
               Title = "Уведомление с радостью",
               ImageUrl = new Uri("/SilverlightApplication9;component/aga.png", UriKind.Relative)
           });
       }
       else
       {
           this.showCustomResultRequest.Raise(new CustomNotification()
           {
               Content = "Жаль, что Вы не с нами! Обидно до слёз! :(",
               Title = "Уведомление с сожалением",
               ImageUrl = new Uri("/SilverlightApplication9;component/nea.png", UriKind.Relative)
           });
       }
   });  

Вот и всё.

Комментарии к статье (4)

29.07.2011 10:45:00 Ali

Спасибо за подсказку. Да действительно я оказаывется два раза объявлял триггер

12.07.2011 23:46:00 Calabonga

Спасибо за комментарий.
И конечно же надо смотреть сам проект, чтобы определить почему такое происходит. Но складывается впечатление, что либо подписка на какое-то событие не имеет отбратного действия (отписки нет), либо экземпляр объекта который вызывает окно не уничтожается, а всегда в пямяти и события к нему только подписываются. Конкретнее сказать не могу, надо смотреть проект.

29.11.2011 11:33:00 Алексей Бурдыкин

Добрый день!
помогите пожалуйста связаться с автором - у меня есть для него предложение

29.11.2011 11:43:00 Calabonga

если тема только для автора - пиши через форму обратной связи