25-29对话框服务

声明

原文出处如下:

作者:RyzenAdorer 内容简介:Prism医疗案例

作者:可是我爱你啊 内容简介:Prism官方案例的学习分享

Prism官网开发文档

容器相关知识的学习

Microsoft官方文档

这是一篇记录作者学习Prism的随笔,该随笔的内容是作者通过5个资源学习汇总而成,主要是为了方便自己以后拾遗温习所用,如果文中内容看不懂,推荐直接阅读相关原文。

25-29对话框服务

了解如何使用InteractionRequest显示弹出窗口

Notification通知式

Prism通过InteractionRequest 来实现弹窗交互,它是一个泛型接口,不同的类型对应不同类型的弹窗方式。
在使用InteractionRequest的时候需要在,xaml中需要注册一个Trigger:

    <i:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding NotificationRequest}">
            <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True" />
        </prism:InteractionRequestTrigger>
    </i:Interaction.Triggers>

Interaction

这里用到了Interaction,他是i命名空间里的东西,那么i是什么呢?
interactivity这个是微软内置的类库,他提供了一组用户交互的类,比如我们这里用到的EventTrigger可以用来执行事件触发的操作。
在使用的时候,先引入xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
或者xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity",然后在xaml中使用他:

<i:Interaction.Triggers>
    <i:EventTrigger>

    </i:EventTrigger>
</i:Interaction.Triggers>

而 prism:PopupWindowAction 的 IsModal=True意味着弹框不被关闭的时候,父窗体无法使用。我刚搜索了一下,这个词的翻译竟然“模态”。

模态对话框(Modal Dialogue Box,又叫做模式对话框),是指在用户想要对对话框以外的应用程序进行操作时,必须首先对该对话框进行响应。 如单击【确定】或【取消】按钮等将该对话框关闭。

好,接着,我们在code-behind中声明,使用INotification类型:

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

在command的回调函数中就可以使用NotificationRequest:

            NotificationRequest.Raise(new Notification { Content = "Notification Message", Title = "Notification" }, r => Title = "Notified");

最后通过ConfirmationRequest.Raise()方法来实现调用弹窗,这里将Title修改为“Notified”。

了解如何使用ConfirmationRequest提示确认对话框

跟Notification的使用方法一样,先注册Trigger:

        <prism:InteractionRequestTrigger SourceObject="{Binding ConfirmationRequest}">
            <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True" />
        </prism:InteractionRequestTrigger>

然后在使用InteractionRequest的时候使用IConfirmation类型:

        public InteractionRequest<IConfirmation> ConfirmationRequest { get; set; }

callback:

            ConfirmationRequest.Raise(new Confirmation {
                Title = "Confirmation",
                Content = "Confirmation Message" }, 
                r => Title = r.Confirmed ? "Confirmed" : "Not Confirmed");

原本一直好奇为什么r能获取confirmationconfirmed属性,后来才发现,自学这个东西,急于求成是不行的。
看下prism的 ConfirmationRequest.Raise()方法:

        /// <summary>
        /// Fires the Raised event.
        /// </summary>
        /// <param name="context">The context for the interaction request.</param>
        /// <param name="callback">The callback to execute when the interaction is completed.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
        public void Raise(T context, Action<T> callback)
        {
            var handler = this.Raised;
            if (handler != null)
            {
                handler(this, new InteractionRequestedEventArgs(context, () => { if(callback != null) callback(context); } ));
            }
        }

了解如何将自己的内容用于与InteractionRequest一起显示的对话框

上面的通知和提示窗体,都是内置的,很多时候,我们需要自制一些弹窗来满足更复杂的使用场景,比如我们通过弹窗来传递一些信息,贴心的Prism同样为我们准备了一个接口IInteractionRequestAware:

        //
        // Summary:
        //     The Prism.Interactivity.InteractionRequest.INotification passed when the interaction
        //     request was raised.
        INotification Notification { get; set; }
        //
        // Summary:
        //     An System.Action that can be invoked to finish the interaction.
        Action FinishInteraction { get; set; }

蛤蛤,Notification正是我们需要的东西,再看看他是什么鬼

        //
        // Summary:
        //     Gets or sets the title to use for the notification.
        string Title { get; set; }
        //
        // Summary:
        //     Gets or sets the content of the notification.
        object Content { get; set; }

原来这个被用来传递的东西,也有标准,需要一个名字和一个内容,内容是object,也就是,我们可以用他来装下任何东西。

FinishInteractioninvoke来关闭交互界面,这个很简单,具体他怎么关闭的暂时不看了。接下来,我们大概有一个思路了:
首先,先定义好我们需要数据载体类实现INotification,再设计一个usercontrole,他的vm实现IInteractionRequestAware接口,然后在NotificationRequest.Raise的时候使用usercontrole,美滋滋!!!😆
我们来通过客制化弹窗,实现从一个字符串列表中选择一个字符串,返回给父窗体:

先设计Notification,他并没有直接实现INotification,而是实现了IConfirmation,IConfirmationINotification的基础上,添加了一个Confirmed属性,来获取弹窗返回状态,是布尔型的(布尔型只有两种状态,很多时候,我需要有战斗机一般多的按钮的时候,他就不够用了,到时候得重新设计一个枚举类型的),这里,我们就直接实现IConfirmation为什么先是搞了一个接口呢?当然是为了依赖注入啊!依赖注入很难讲,以前我也看了很多大佬的资料,但是没有懂,后来去问大佬,大佬说,你看懂了吗?我说似懂非懂,他说,那就去看代码吧,慢慢的就懂了。😂):

using Prism.Interactivity.InteractionRequest;

namespace UsingPopupWindowAction.Notifications
{
    public interface ICustomNotification : IConfirmation
    {
        string SelectedItem { get; set; }
    }
}

接着是我们的实现类(一个list(源),一个string(目标))继承 Confirmation实现我们的接口ICustomNotification,继承 Confirmation是因为他继承自Notification,而Notification是实现了INotification的,这样,我们就在我们的类里不用去实现INotification了,其实也可以不用继承·Confirmation·,完全可以自己实现ICustomNotification他所有的接口(话说若干年前我怎么记得接口不可以被继承只能被实现呢?记错了?):

using Prism.Interactivity.InteractionRequest;
using System.Collections.Generic;

namespace UsingPopupWindowAction.Notifications
{
    public class CustomNotification : Confirmation, ICustomNotification
    {
        public IList<string> Items { get; private set; }

        public string SelectedItem { get; set; }

        public CustomNotification()
        {
            this.Items = new List<string>();
            this.SelectedItem = null;

            CreateItems();
        }

        private void CreateItems()
        {
            //add some items
        }
    }
}

如果不继承Confirmation,则需要添加部分实现:

        public bool Confirmed { get ; set ; }
        public string Title { get ; set ; }
        public object Content { get ; set ; }

接下来设计我们的弹窗(一个列表(显示源),两个按钮,一个取消一个提交(获取目标)):

        <TextBlock Margin="10" TextWrapping="Wrap" FontWeight="Bold">Please select an item:</TextBlock>
        <ListBox SelectionMode="Single" Margin="10,0" Height="100" ItemsSource="{Binding Notification.Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}"></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>

弹窗的ViewModel,实现IInteractionRequestAware接口,这里设置了一个_notification来接收我们用来传递的那个类,这很像MVC里的Model,他只是个数据的载体,在每一个命令最后都需要关闭窗体,并且之前对confirmed和我们的SelectedItem进行赋值:
依旧省去了大部分代码,只看与我们有关的部分

    public class ItemSelectionViewModel : BindableBase, IInteractionRequestAware
    {
        public string SelectedItem { get; set; }
        private void CancelInteraction()
        {
            _notification.SelectedItem = null;
            _notification.Confirmed = false;
            FinishInteraction?.Invoke();
        }

        private void AcceptSelectedItem()
        {
            _notification.SelectedItem = SelectedItem;
            _notification.Confirmed = true;
            FinishInteraction?.Invoke();
        }

        public Action FinishInteraction { get; set; }

        private ICustomNotification _notification;

        public INotification Notification
        {
            get { return _notification; }
            set { SetProperty(ref _notification, (ICustomNotification)value); }
        }
    }
}

最后就是在Shell里调用这个客制化弹窗啦,跟之前的就一毛一样了,将INotification或者IConfirmation替换成我们的ICustomNotification,然后在new的时候使用CustomNotification,代码看上去应该是这个样子的:
xaml:

<prism:InteractionRequestTrigger SourceObject="{Binding CustomNotificationRequest}">
            <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True">
                <prism:PopupWindowAction.WindowContent>
                    <views:ItemSelectionView />
                </prism:PopupWindowAction.WindowContent>
            </prism:PopupWindowAction>
        </prism:InteractionRequestTrigger>

viewmodel:

   public InteractionRequest<ICustomNotification> CustomNotificationRequest { get; set; }
        public DelegateCommand CustomNotificationCommand { get; set; }

        public MainWindowViewModel()
        {
            CustomNotificationRequest = new InteractionRequest<ICustomNotification>();
            CustomNotificationCommand = new DelegateCommand(RaiseCustomInteraction);
        }
        private void RaiseCustomInteraction()
        {
            CustomNotificationRequest.Raise(new CustomNotification { Title = "Custom Notification" }, r =>
                {
                    if (r.Confirmed && r.SelectedItem != null)
                        Title = $"User selected: { r.SelectedItem}";
                    else
                        Title = "User cancelled or didn't select an item";
                });
        }

创建您自己的自定义请求以与InteractionRequest一起使用

响应任何事件调用命令

IDialogAware

在Prism中,通过一个IDialogAware接口来实现对话框服务:

Copypublic interface IDialogAware
{
    bool CanCloseDialog();
    void OnDialogClosed();
    void OnDialogOpened(IDialogParameters parameters);
    string Title { get; set; }
    event Action<IDialogResult> RequestClose;
}
  • CanCloseDialog()函数是决定窗体是否关闭
  • OnDialogClosed()函数是窗体关闭时触发,触发条件取决于CanCloseDialog()函数
  • OnDialogOpened()函数时窗体打开时触发,比窗体Loaded事件早触发
  • Title为窗体的标题
  • RequestClose为关闭事件,可由此控制窗体的关闭

对话框服务

在Prism中,通过一个IDialogAware接口来实现对话框服务:

Copypublic interface IDialogAware
{
    bool CanCloseDialog();
    void OnDialogClosed();
    void OnDialogOpened(IDialogParameters parameters);
    string Title { get; set; }
    event Action<IDialogResult> RequestClose;
}
  • CanCloseDialog()函数是决定窗体是否关闭
  • OnDialogClosed()函数是窗体关闭时触发,触发条件取决于CanCloseDialog()函数
  • OnDialogOpened()函数时窗体打开时触发,比窗体Loaded事件早触发
  • Title为窗体的标题
  • RequestClose为关闭事件,可由此控制窗体的关闭

创建对话框的View和ViewModel

AlertDialog.xaml:

Copy<UserControl x:Class="PrismMetroSample.Shell.Views.Dialogs.AlertDialog"
             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" 
             xmlns:local="clr-namespace:PrismMetroSample.Shell.Views.Dialogs"
             mc:Ignorable="d"  
             xmlns:prism="http://prismlibrary.com/"
             Width="350" Height="120" prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid  Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid Margin="0,0,0,10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Image Source="pack://application:,,,/PrismMetroSample.Infrastructure;Component/Assets/Photos/alter.png" Height="40" UseLayoutRounding="True" RenderOptions.BitmapScalingMode="HighQuality"/>
            <TextBlock  Grid.Column="1" Text="{Binding Message}" HorizontalAlignment="Left" VerticalAlignment="Center" Grid.Row="0" TextWrapping="Wrap"  FontSize="15" FontFamily="Open Sans"/>
        </Grid>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Button Margin="5"  Foreground="White"  FontSize="12" Background="#5cb85c" Command="{Binding CloseDialogCommand}" CommandParameter="true" Content="Yes" Width="64" Height="28" HorizontalAlignment="Right" Grid.Row="1"/>
            <Button Grid.Column="1" Margin="5"  Foreground="White"  FontSize="12" Background="#d9534f" Command="{Binding CloseDialogCommand}" CommandParameter="false" Content="No" Width="64" Height="28" HorizontalAlignment="Left" Grid.Row="1"/>
        </Grid>
       
    </Grid>
</UserControl>

AlertDialogViewModel.cs:

Copypublic class AlertDialogViewModel : BindableBase, IDialogAware
{
    private DelegateCommand<string> _closeDialogCommand;
    public DelegateCommand<string> CloseDialogCommand =>
        _closeDialogCommand ?? (_closeDialogCommand = new DelegateCommand<string>(ExecuteCloseDialogCommand));

    void ExecuteCloseDialogCommand(string parameter)
    {
        ButtonResult result = ButtonResult.None;
        if (parameter?.ToLower() == "true")
            result = ButtonResult.Yes;
        else if (parameter?.ToLower() == "false")
            result = ButtonResult.No;
         RaiseRequestClose(new DialogResult(result));
     }

     //触发窗体关闭事件
     public virtual void RaiseRequestClose(IDialogResult dialogResult)
     {
         RequestClose?.Invoke(dialogResult);
     }

     private string _message;
     public string Message
     {
         get { return _message; }
         set { SetProperty(ref _message, value); }
     }

     private string _title = "Notification";
     public string Title
     {
         get { return _title; }
         set { SetProperty(ref _title, value); }
     }

     public event Action<IDialogResult> RequestClose;

     public bool CanCloseDialog()
     {
         return true;
     }

     public void OnDialogClosed()
     {
            
     }

     public void OnDialogOpened(IDialogParameters parameters)
     {
         Message = parameters.GetValue<string>("message");
     }
 }

注册对话框

App.cs:

Copyprotected override void RegisterTypes(IContainerRegistry containerRegistry)
{
     containerRegistry.RegisterDialog<AlertDialog, AlertDialogViewModel>();
}

还可以注册时起名字:

CopycontainerRegistry.RegisterDialog<AlertDialog, AlertDialogViewModel>(“alertDialog”);

使用对话框服务

CreateAccountViewModel.cs(修改部分):

Copypublic CreateAccountViewModel(IRegionManager regionManager, IDialogService dialogService)
{
     _regionManager = regionManager;
     _dialogService = dialogService;
}

 public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
 {
     if (!string.IsNullOrEmpty(RegisteredLoginId) && this.IsUseRequest)
     {
          _dialogService.ShowDialog("AlertDialog", new DialogParameters($"message={"是否需要用当前注册的用户登录?"}"), r =>
           {
                 if (r.Result == ButtonResult.Yes)
                     navigationContext.Parameters.Add("loginId", RegisteredLoginId);
           });
      }
      continuationCallback(true);

 }

效果如下:

img

我们是通过调用IDialogService接口的ShowDialog函数来调用,下面是该接口的定义:

Copypublic interface IDialogService : Object
{
    Void Show(String name, IDialogParameters parameters, Action<IDialogResult> callback);
    Void ShowDialog(String name, IDialogParameters parameters, Action<IDialogResult> callback);
    
 }

我们可以发现show和ShowDialog函数都是一样形参,无非就是使用场景不一样

  • name:所要调用对话框view的名字,当注册别名时,只能使用别名来调用
  • parameters:IDialogParameters接口类型参数,传入的提示消息,通常是$"message={xxxx}"格式,然后再ViewModel的OnDialogOpened函数通过IDialogParameters接口的GetValue函数来获取
  • callback:用于传入无返回值回调函数

自定义对话框窗体

我们在上述可以看到,对话框的窗体时一个WPF自带的窗体,但是当我们要用自己自定义窗体,例如,去掉window的Icon,保留最大化,最小化和关闭,或者使用一些第三方的窗体控件,prism支持通过注册一个对话框窗体,然后通过再不同对话框的View指定其对话框窗体的style,则可以很灵活的实现不一样的对话框,下面让我们来看看如何操作:

注册自定义对话框窗体

新建一个窗体,DialogWindow.xaml:

Copy<Window x:Class="PrismMetroSample.Shell.Views.Dialogs.DialogWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PrismMetroSample.Shell.Views.Dialogs"
        mc:Ignorable="d"  
        xmlns:prism="http://prismlibrary.com/"
         >
    <Grid>
        
    </Grid>
</Window>

DialogWindow.xaml.cs:

Copypublic partial class DialogWindow : Window, IDialogWindow
{
    public DialogWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        WindowHelp.RemoveIcon(this);//使用win32函数去除Window的Icon部分
    }

    public IDialogResult Result { get; set; }
}

App.cs:

Copyprotected override void RegisterTypes(IContainerRegistry containerRegistry)
{
     containerRegistry.RegisterDialogWindow<DialogWindow>();//注册自定义对话框窗体
}

自定义对话框窗体Style

AlertDialog.xaml:

Copy <prism:Dialog.WindowStyle>
     <Style TargetType="Window">
          <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" />
          <Setter Property="ShowInTaskbar" Value="False"/>
          <Setter Property="SizeToContent" Value="WidthAndHeight"/>
     </Style>
 </prism:Dialog.WindowStyle>

效果如下:

img

如何我们要将窗体样式全部去掉,改动AlertDialog.xaml:

Copy <prism:Dialog.WindowStyle>
     <Style TargetType="Window">
          <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" />
          <Setter Property="ShowInTaskbar" Value="False"/>
          <Setter Property="SizeToContent" Value="WidthAndHeight"/>
          <Setter Property="WindowStyle" Value="None"/>
     </Style>
 </prism:Dialog.WindowStyle>

那么就变成了下面这样:

img

最终,我们的最后效果为这样:

img

小结

通过Prism的对话框服务,我们可以很好的通过一个IDialogService接口来统一管理对话框的弹出逻辑,而且可以使用依赖注入的模式,如果换成之前要定义一些自定义的对话框,那么也要强依赖View部分,而且可以通过自定义不同对话框的窗体样式,达到一定的灵活性(例如最终效果演示,用了两个不同的对话框样式),至此, .NET Core3.x Prism系列文章已经全部写完

源码

最后,附上整个demo的源代码:PrismDemo源码

posted @ 2020-08-10 01:53  AJun816  阅读(323)  评论(0编辑  收藏  举报