WPF-Prism8.0 核心教程 笔记

1,395 阅读9分钟

视频地址:www.bilibili.com/video/av543…

1 创建Prism程序

1.1 从WPF项目变更

  1. 创建WPF项目

  2. 安装Nuget组件

    Install-Package Prism.DryIoc
    
  3. 修改启动类App继承自PrismApplication,并重写方法

    public partial class App : PrismApplication
    {
      protected override Window CreateShell()
      {
        // 使用Prism提供的容器注入MainWindow窗口
        return Container.Resolve<MainWindow>();
      }
    
      protected override void RegisterTypes(IContainerRegistry containerRegistry)
      {
    		// 用于将其他类型注入IoC容器中
      }
    }
    
  4. 修改App.xaml

    <prism:PrismApplication x:Class="prism_wpf.App"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:local="clr-namespace:prism_wpf"
                 xmlns:prism="http://prismlibrary.com/" >
        <Application.Resources>
             
        </Application.Resources>
    </prism:PrismApplication>
    
    
    • 添加命名空间 xmlns:prism="http://prismlibrary.com/"
    • 修改根标签为prism:PrismApplication
    • 移除StartupUri,(Prism从CreateShell()启动)

1.2 从模板创建项目

  1. 安装拓展(扩展 - 管理扩展) Prism Template Pack
  2. 启动时,使用 Prsim Blank App 创建项目,创建时会提示要使用的容器类型,选择DryIoc

2 Region(区域)

传统应用程序开发中,视图中的组件基本是固定的。使用Region后,可以通过代码动态变更Region标记的组件。

2.1 使用内置的Region

  1. Prism对一些组件已经适配了Region,可通过指定RegionName来作为Region使用,可通过2种方式指定:

    • 在XAML中,通过RegionManager.RegionName属性指定
    <Window x:Class="BlankApp1.Views.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:prism="http://prismlibrary.com/"
            prism:ViewModelLocator.AutoWireViewModel="True"
            Title="Test" Height="350" Width="525" >
        <Grid>
            <ContentControl prism:RegionManager.RegionName="ContentRegion" />
        </Grid>
    </Window>
    
    • 在代码中,通过RegionManager.SetRegionName指定:(需要先给组件增加x:Name="ctr"来获取该组件)
    public partial class MainWindow : Window
    {
      public MainWindow()
      {
        InitializeComponent();
        RegionManager.SetRegionName(ctr, "ContentRegion");
      }
    }
    
  2. 创建用户控件(WPF) ViewA

    <UserControl x:Class="BlankApp1.Views.ViewA"
                 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:BlankApp1.Views"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
        <Grid Background="Yellow">
            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="60">Prism!</TextBlock>
        </Grid>
    </UserControl>
    
  3. 修改MainWindow的ViewModel

    public class MainWindowViewModel : BindableBase
    {
      private readonly IRegionManager regionManager;
    
      public MainWindowViewModel(IRegionManager regionManager)
      {
        this.regionManager = regionManager;
        regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
      }
    }
    
    • 增加IRegionManager字段,其会通过构造函数,由Prism提供的IoC容器注入
    • 调用RegisterViewWithRegion方法,可将指定View实例化到Region中

2.2 RegionManager的用途

  1. 维护区域集合

  2. 提供对区域的访问

    var regions = regionManager.Regions["ContentRegion"];
    
  3. 合成视图

  4. 区域导航

  5. 定义区域

    RegionManager.SetRegionName(ctr, "ContentRegion");
    

2.3 RegionAdapter

Region通过适配器实现对组件内容的替换,而Prism官方实现了以下组件的适配器:

  • ContentControlRegionAdapter
  • ItemsControlRegionAdapter
  • SelectorRegionAdapter
    • ComboBox
    • ListBox
    • Ribbon
    • TabControl

因此我们可以直接在上述组件中定义Region,但若在其他组件上定义Region时就会引发异常

但我们可以实现自己的RegionAdapter,从而可以在其他组件中定义Region,以StackPanel为例:

  1. 创建StackPanelRegionAdapter

    using Prism.Regions;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace BlankApp1;
    
    public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
    {
        public StackPanelRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory) : base(regionBehaviorFactory)
        {
        }
    
        // 编写将组件注入到Region组件的逻辑
        protected override void Adapt(IRegion region, StackPanel regionTarget)
        {
            // 检测Region中视图的变化
            region.Views.CollectionChanged += (s, e) =>
            {
                // 如果是新增事件
                if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                {
                    // 将新增元素添加到Region组件中
                    foreach (FrameworkElement item in e.NewItems)
                    {
                        regionTarget.Children.Add(item);
                    }
                }
            };
        }
    
        // 创建Region区域
        protected override IRegion CreateRegion()
        {
            return new Region();
        }
    }
    
  2. 在App中注册RegionAdapter

    public partial class App
    {
      protected override Window CreateShell()
      {
        return Container.Resolve<MainWindow>();
      }
    
      protected override void RegisterTypes(IContainerRegistry containerRegistry)
      {
    
      }
    	
      // 重写该方法并注册
      protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
      {
        base.ConfigureRegionAdapterMappings(regionAdapterMappings);
    
        regionAdapterMappings.RegisterMapping<StackPanel, StackPanelRegionAdapter>();
      }
    }
    

3 Module(模块化)

Prism模块化允许界面在不同项目中,甚至是不同解决方案里,在主项目中,通过Region切换显示不同模块中的视图。

3.1 模块开发

创建模块的2种方式:

  1. 在同一解决方案中创建新项目
  2. 通过Prism Module WPF模板创建

创建模块:每个独立模块项目需要在根目录创建IModule接口的实现类

public class ModuleAModule : IModule
{
  public void OnInitialized(IContainerProvider containerProvider)
  {
    // 通过IoC容器获取IRegionManager,并将模块中的视图注入到Region中
    var regionManager = containerProvider.Resolve<IRegionManager>();
    regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
  }

  public void RegisterTypes(IContainerRegistry containerRegistry)
  {

  }
}

3.2 在主项目中引入模块

通过在主项目的App.xaml.cs中重写CreateModuleCatalog*()方法引入模块

  1. 通过配置文件方式引入

    这种方式引入模块无需关联项目,因为它是通过dll引入的

    public partial class App : PrismApplication
    {
      protected override Window CreateShell()
      {
        return Container.Resolve<MainWindow>();
      }
    
      protected override void RegisterTypes(IContainerRegistry containerRegistry)
      {
    
      }
    
      protected override IModuleCatalog CreateModuleCatalog()
      {
        return new ConfigurationModuleCatalog();
      }
    }
    

    此时,Prism会从根目录下的App.Config获取模块配置

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <configSections>
        <section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf" />
      </configSections>
      <startup>
      </startup>
      <modules>
        <module assemblyFile="ModuleA.dll" moduleType="ModuleA.ModuleAModule, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleAModule" startupLoaded="True" />
      </modules>
    </configuration>
    
  2. 通过代码引入

    需要在主项目中关联模块项目

    protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
    {
      moduleCatalog.AddModule<ModuleA.ModuleAModule>();
    }
    
  3. 通过路径引入

    protected override IModuleCatalog CreateModuleCatalog()
    {
      return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
    }
    
  4. 手动引入

    需要在主项目中关联模块项目

    Module注册到Prism会以ModuleInfo的形式存在,包含了Module的相关信息

    protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
    {
      var moduleAType = typeof(ModuleAModule);
      moduleCatalog.AddModule(new ModuleInfo()
                              {
                                // Module的标识符,相当于Module的ID
                                ModuleName = moduleAType.Name,
                                // Module的AssemblyQualifiedName
                                ModuleType = moduleAType.AssemblyQualifiedName,
                                // 初始化模式: OnDemand - 在调用模块时时初始化,WhenAvailable - 程序启动时初始化
                                InitializationMode = InitializationMode.OnDemand
                              });
    }
    
  5. 通过XAML引入

    需要在主项目中关联模块项目

    protected override IModuleCatalog CreateModuleCatalog()
    {
      return new XamlModuleCatalog(new Uri("/Modules;component/ModuleCatalog.xaml", UriKind.Relative));
    }
    

    在根目录创建ModuleCatalog.xaml

    <m:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:m="clr-namespace:Prism.Modularity;assembly=Prism.Wpf">
    
        <m:ModuleInfo ModuleName="ModuleAModule" 
                      ModuleType="ModuleA.ModuleAModule, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    
    </m:ModuleCatalog>
    

3.3 视图注入

每个子模块可以先注册不显示。再使用IRegionManager实现导航。

  1. 在模块配置类中重写RegisterTypes方法,将视图注测到导航中
  2. 通过RequestNavigate()将Region导航至指定视图
public class Module1Module : IModule
{
  public void OnInitialized(IContainerProvider containerProvider)
  {
    var regionManager = containerProvider.Resolve<IRegionManager>();
    // 导航到指定View有2种方式,任选其一:
    // 1.先从Regions中获取指定Region,在调用其RequestNavigate方法
    regionManager.Regions["ContentRegion"].RequestNavigate("ViewA");
    // 2.直接调用IRegionManager的RequestNavigate方法
    regionManager.RequestNavigate("ContentRegion", "ViewA");
  }

  public void RegisterTypes(IContainerRegistry containerRegistry)
  {
    containerRegistry.RegisterForNavigation<ViewA>();
  }
}

4 MVVM

常见的MVVM框架当中基本都围绕了ICommandINotifyPropertyChanged封装实现绑定、通知等功能。而对于不同的框架,大部分只是写法不同,实现的功能都一样。下图列举了几个常见的框架的区别。

功能PrismMvvmlightMicorosoft.Toolkit.Mvvm
通知BindableBaseViewModelBaseObservableObject
命令DelegateCommandRelayCommandAsync/RelayCommand
聚合器IEventAggregatorIMessengerIMessenger
模块化xx
容器xx
依赖注入xx
导航xx
对话xx

4.1 ViewModel

Prism可以自动找到ViewModel与View关联,无需手动指定DataContext,但其必须遵循如下约定:

  • View必须放在根目录Views
  • ViewModel必须放在根目录ViewModels目录下
  • ViewModel的类名必须是{View名称}ViewModel格式
  • View根节点增加属性prism:ViewModelLocator.AutoWireViewModel="True"
  1. 创建继承BindableBase的ViewAViewModel

    public class ViewAViewModel : BindableBase
    {
      private string title;
      public DelegateCommand OpenCommand { get; set; }
    
      public ViewAViewModel()
      {
        Title = "Hello!";
        OpenCommand = new DelegateCommand(() => Title = "Prism");
    
      }
    
      public string Title
      {
        get { return title; }
        set { title = value; RaisePropertyChanged(); }
      }
    
    }
    
  2. 在ViewA根节点上增加属性ViewModelLocator.AutoWireViewModel

    <UserControl x:Class="BlankApp1.Views.ViewA"
                 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:BlankApp1.Views"
                 xmlns:prism="http://prismlibrary.com/"
                 prism:ViewModelLocator.AutoWireViewModel="True"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
        <Grid>
            <StackPanel>
                <Button Command="{Binding OpenCommand}" Content="UpdateText" />
                <TextBlock FontSize="38" Text="{Binding Title}" />
            </StackPanel>
        </Grid>
    </UserControl>
    

4.2 复合命令

通过CompositeCommand,可一次触发多个命令

public class ViewAViewModel : BindableBase
{
  private string title;
  public DelegateCommand OpenCommand1 { get; private set; }
  public DelegateCommand OpenCommand2 { get; private set; }
  public CompositeCommand OpenAll { get; set; }

  public ViewAViewModel()
  {
    Title = "Hello!!!!";
    OpenCommand1 = new DelegateCommand(() => Title += " Prism1");
    OpenCommand2 = new DelegateCommand(() => Title += " Prism2");
    OpenAll = new CompositeCommand();
    OpenAll.RegisterCommand(OpenCommand1);
    OpenAll.RegisterCommand(OpenCommand2);

  }

  public string Title
  {
    get { return title; }
    set { title = value; RaisePropertyChanged(); }
  }

}
<Grid>
  <StackPanel>
    <Button Command="{Binding OpenAll}" Content="UpdateText" />
    <TextBlock FontSize="38" Text="{Binding Title}" />
  </StackPanel>
</Grid>

4.3 事件聚合器

通过依赖注入IEventAggregator可以进行事件的订阅发布

public ViewBViewModel(IEventAggregator eventAggregator)
{
  // 订阅事件
  eventAggregator.GetEvent<MyCustomEvent>().Subscribe(OnReceiveMessage);
  // 过滤事件
  eventAggregator.GetEvent<MyCustomEvent>().Subscribe(
                msg => Title += msg + "\r\n",
                ThreadOption.PublisherThread,
                false,
                msg => msg.Equals("Hello"));
  // 取消订阅
  eventAggregator.GetEvent<MyCustomEvent>().Unsubscribe(OnReceiveMessage);
  // 发布事件
  eventAggregator.GetEvent<MyCustomEvent>().Publish("Hello");
}

public void OnReceiveMessage(string msg) {
  ...
}

Subscribe的4个参数:

  1. action:发布事件时执行的委托
  2. ThreadOption枚举:指定在哪个线程上接收委托回调
  3. keepSubscriberReferenceAlive:如果为true,则Prism.Events.PubSubEvent保留对订阅者的引用因此它不会被垃圾回收
  4. flter:进行筛选以评估订阅者是否应接收事件

5 Navigation(导航)

5.1 使用导航

  1. 在App.xaml.cs中注册导航组件

    public partial class App
    {
      protected override Window CreateShell()
      {
        return Container.Resolve<MainWindow>();
      }
    
      protected override void RegisterTypes(IContainerRegistry containerRegistry)
      {
    		 containerRegistry.RegisterForNavigation<ViewA>();
         // 注册为别名
         containerRegistry.RegisterForNavigation<ViewA>(“PageA”);
         containerRegistry.RegisterForNavigation<ViewB>();
         // 不使用自动关联的方式手动关联ViewModel
         containerRegistry.RegisterForNavigation<ViewB, ViewBViewModel>();
      }
    }
    
  2. 导航到目标视图

    regionManager.RequestNavigate("ContentRegion", "ViewA");
    

5.2 带参导航

导航组件的ViewModel可以实现INavigationAware来处理导航过程

public class ViewBViewModel : BindableBase, INavigationAware
{
  private string title;

  public string Title
  {
    get { return title; }
    set { title = value; RaisePropertyChanged(); }
  }

  // 是否重用当前视图,返回true表示重用,false创建新的实例
  public bool IsNavigationTarget(NavigationContext navigationContext)
  {
    return true;
  }

  // 导航离开当前页面时触发
  public void OnNavigatedFrom(NavigationContext navigationContext)
  {

  }

  // 导航到当前页面前触发,接收传入的参数以及控制是否允许导航
  public void OnNavigatedTo(NavigationContext navigationContext)
  {
    // 获取导航传过来的参数
    Title = navigationContext.Parameters.GetValue<string>("Value");
  }
}

INavigationAware执行流程:

graph LR
A[RequestNavigate]
B[OnNavigatedFrom]
C[IsNavigationTarget]
D[Resolve View]
E[OnNavigatedTo]
F[Navigate Complete]
A-->B-->C-->D-->E-->F

切换导航时,可以通过第三个参数,或以URI方式传递参数

// 方法1
NavigationParameters param = new NavigationParameters();
param.Add("Value", "Hello");
regionManager.RequestNavigate("ContentRegion", "PageA", param);
// 方法2
regionManager.RequestNavigate("ContentRegion", $"PageA?Value=Hello");

5.3 拦截导航请求

可在离开当前视图时并导航到其他页面时进行确认。

修改INavigationAwareIConfirmNavigationRequest接口

public interface IConfirmNavigationRequest : INavigationAware
{
  void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback);
}

它继承自INavigationAware,并增加了一个新方法。它会在离开当前视图时触发,通过continuationCallback判断是否可以继续导航。

public class ViewBViewModel : BindableBase, IConfirmNavigationRequest
{
  public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
  {
    if (MessageBox.Show("确认导航?", "温馨提示", MessageBoxButton.YesNo) == MessageBoxResult.No)
    {
      // 取消导航,留在当前视图
      continuationCallback(false);
      return;
    }
    // 继续导航到下一视图,离开当前视图
    continuationCallback(true);
  }
	...
}

IConfirmNavigationRequest执行流程:

graph LR
A[RequestNavigate]
B[ConfirmNavigationRequest]
C[OnNavigatedFrom]
D[Continue Navigation Process]
A-->B-->C-->D

5.4 导航日志

即IRegionNavigationJournal接口,可以控制导航返回上一页、下一页。该接口包含以下功能:

  • GoBack():返回上一页
  • CanGoBack:是否可以返回上一页
  • GoForward():返回后一页
  • CanGoForward:是否可以返回后一页

可以通过以下2种方式获取该接口实例:

  1. 通过RegionManager获取

    var journal = regionManager.Regions["ContentRegion"].NavigationService.Journal;
    
  2. 通过NavigationContext获取

    regionManager.RequestNavigate("ContentRegion", "ViewB", arg => {
      var journal = r.Context.NavigationService.Journal;
    });
    

    这个重载第三个参数为回调函数,会有一个NavigationResult入参,从中可以获取到NavigationContext

6 Dialog(对话服务)

Prism提供了一组对话服务(IDialogService),封装了常用对话框组件的功能:

  • 对话框组件的注册
  • 弹出对话框
  • 向对话框传递参数
  • 接收对话框的返回值

6.1 创建对话框组件

  1. 创建对话框的View,和常规组件一样

    <UserControl x:Class="BlankApp1.Views.InputDialog"
                 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:BlankApp1.Views"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
        <Grid Background="White">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto" />
                <RowDefinition/>
                <RowDefinition Height="auto" />
            </Grid.RowDefinitions>
    
            <TextBlock VerticalAlignment="center" FontSize="22" Text="编辑" />
    
            <TextBox Grid.Row="1" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Center" FontSize="39" Text="{Binding Content}" />
    
            <StackPanel Grid.Row="2" HorizontalAlignment="Right" Orientation="Horizontal">
                <Button Width="100" Height="35" Margin="10" FontSize="22" Command="{Binding SaveCommand}" Content="确认" />
                <Button Width="100" Height="35" Margin="10" FontSize="22" Command="{Binding CancelCommand}" Content="取消" />
            </StackPanel>
        </Grid>
    </UserControl>
    
  2. 创建对话框的ViewModel,需要额外实现IDialogAware接口

    public interface IDialogAware
    {
      string Title { get; }
      event Action<IDialogResult> RequestClose;
      bool CanCloseDialog();
      void OnDialogClosed();
      void OnDialogOpened(IDialogParameters parameters);
    }
    
    public class InputDialogViewModel : BindableBase, IDialogAware
    {
      private string content;
      public string Content
      {
        get { return content; }
        set { content = value; RaisePropertyChanged(); }
      }
      
      public DelegateCommand saveCommand { get; set; }
      public DelegateCommand cancelCommand { get; set; }
    
      // 标题,用不上的话就直接放这里
      public string Title { get; set; }
    	// 事件,调用时会被Prism监听到从而关闭窗口
      public event Action<IDialogResult> RequestClose;
    
      // 是否允许关闭当前窗口
      public bool CanCloseDialog()
      {
        return true;
      }
    
      // 关闭Dialog时触发
      public void OnDialogClosed()
      {
      }
    
      // 打开Dialog时触发,可接收创建时传入的参数
      public void OnDialogOpened(IDialogParameters parameters)
      {
    
      }
    }
    
  3. 注册对话框(3种方式,和注册导航一样)

    public partial class App
    {
      protected override Window CreateShell()
      {
        return Container.Resolve<MainWindow>();
      }
    
      protected override void RegisterTypes(IContainerRegistry containerRegistry)
      {
    		containerRegistry.RegisterDialog<InputDialog>();
        // 起别名
        containerRegistry.RegisterDialog<InputDialog>(“Input”);
        // 手动关联ViewModel
        containerRegistry.RegisterDialog<InputDialog, InputDialogViewModel>();
    
      }
    }
    
  4. 打开对话框

    IDialogService dialog = _; // 通过依赖注入获取
    // 非模态对话框,弹出后允许和其他窗口交互
    dialog.Show("InputDialog");
    // 模态对话框,弹出后不允许和其他窗口交互
    dialog.ShowDialog("InputDialog");
    

6.2 向对话框传参

  1. 打开对话框时,通过第二个参数传入

    DialogParameters param = new DialogParameters();
    param.Add("Value", "Hello");
    dialog.ShowDialog("InputDialog", param ,arg => { });
    
  2. 在对话框ViewModel中获取

    public void OnDialogOpened(IDialogParameters parameters)
    {
      var value = parameters.GetValue<string>("Value");
      MessageBox.Show("传入参数:" + value);
    }
    

6.3 接收对话框结果

  1. 打开对话框时,通过第三个参数传入回调接收结果

    dialog.Show("InputDialog", param ,arg => 
      {
        if (arg.Result == ButtonResult.OK)
        {
          var value = arg.Parameters.GetValue<string>("Value");
        }
    });
    
  2. 在对话框ViewModel中,调用RequestClose.Invoke,该方法需要传入一个DialogResult参数,最终会返回给外层窗口。

    public DelegateCommand saveCommand { get; set; }
    public DelegateCommand cancelCommand { get; set; }
    
    public InputDialogViewModel()
    {
      saveCommand = new DelegateCommand(() =>
        {
          DialogParameters param = new DialogParameters();
          param.Add("Value", Content);
          RequestClose.Invoke(new DialogResult(ButtonResult.OK, param));
        });
      cancelCommand = new DelegateCommand(() =>
        {
          RequestClose.Invoke(new DialogResult(ButtonResult.No));
        });
    }