WPF中的Prism弹出新窗口

5
我应该如何在不违反MVVM模式规则的情况下在WPF中打开/关闭新窗口?我只想模仿ms office outlook的登录模块。
我已经阅读了this article,但是在传递参数confirmation时出现错误。
我目前正在使用prism 5.0。
3个回答

13
幸运的是,Prism 5.0(我认为6.0也是,但还没有使用过)有一个名为InteractionRequest<T>的类,您可以从代码中使用它来发出交互请求。
交互请求基本上是一个窗口,询问用户执行某个操作并调用回调函数(如果必要或者需要)以获取用户的决策/操作。
public class ShellViewModel : BindableBase
{
    private readonly IRegionManager regionManager;

    public ShellViewModel(IRegionManager regionManager)
    {
        if (regionManager == null)
            throw new ArgumentNullException("regionManager");

        this.regionManager = regionManager;
        this.OptionSettingConfirmationRequest = new InteractionRequest<IConfirmation>();

        openConnectionOptionsCommand = new DelegateCommand(RaiseConnectionOptionsRequest);
    }

    public InteractionRequest<IConfirmation> OptionSettingConfirmationRequest { get; private set; }

    private readonly ICommand openConnectionOptionsCommand;
    public ICommand OpenConnectionOptionsCommand { get { return openConnectionOptionsCommand; } }

    private void RaiseConnectionOptionsRequest()
    {
        this.OptionSettingConfirmationRequest.Raise(new Confirmation { Title = "Options not saved. Do you wish to save?" }, OnConnectionOptionsResponse);
    }

    protected virtual void OnConnectionOptionsResponse(IConfirmation context)
    {
        if(context.Confirmed)
        {
            // save it
        }

        // otherwise do nothing
    }
}
在XAML中,您可以这样做:
<Button Content="Options" Command="{Binding OpenConnectionOptionsCommand}">
    <i:Interaction.Triggers>
        <pit:InteractionRequestTrigger SourceObject="{Binding OptionSettingConfirmationRequest, Mode=OneWay}" >
            <pie:LazyPopupWindowAction RegionName="ConnectionSettings" 
                                NavigationUri="ConnectionSettingsView" IsModal="True" />
        </pit:InteractionRequestTrigger>
    </i:Interaction.Triggers>
</Button>

我使用了自己实现的PopupWindowAction(请看GitHub项目页面获取其实现),名为LazyPopupWindowAction,点击时会实例化嵌入的ConnectionSettingsView视图。如果您不在意视图只被实例化一次,可以随意使用PopupWindowAction,此时它将与包含该操作的视图同时被实例化。

这基本上就是从我的某个项目中拷贝并削减了一些无用行。我使用了IConfirmationINotification接口而非具体实现。

XAML中使用PopupWindowAction

<Button Content="Options" Command="{Binding OpenConnectionOptionsCommand}">
    <i:Interaction.Triggers>
        <pit:InteractionRequestTrigger SourceObject="{Binding OptionSettingConfirmationRequest, Mode=OneWay}" >
            <pi:PopupWindowAction>
                <pi:PopupWindowAction.WindowContent>
                    <views:CustomPopupView />
                </pi:PopupWindowAction.WindowContent>
            </pi:PopupWindowAction>
        </pit:InteractionRequestTrigger>
    </i:Interaction.Triggers>
</Button>

命名空间声明

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:pi="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:pit="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:pie="clr-namespace:MyProject.UI.Prism.Interactivity;assembly=MyProject.UI"
更新: 鉴于人们不断询问有关LazyPopupWindowAction的问题,我已经将源代码放在GitHub Gist中。基本上它是基于Prism 5的PopupWindowAction(针对Prism而言,尚未使用Prism 6进行测试,可能需要调整才能正常工作),并且执行完全相同的操作,但还添加了区域和导航支持以及打开的窗口,这是我在应用程序中所需要的。

我不喜欢默认实现的一件事是,在Shell实例化时视图及其视图模型将同时实例化,当你关闭它时(实际上只是隐藏了它),视图模型仍然保持原状。


谢谢您给了我一些关于这个问题的想法。非常感谢! - Neil
根据这个链接,该库已被弃用,你有其他的 Prism 6 解决方案吗?谢谢。 - Hakan Fıstık
另外注意:您如何设置Button类的Label="Options",因为Button没有这样的属性。您是指应该使用Content="Options"吗? - Hakan Fıstık
@HakamFostok:是的,它应该是Content='"Options"。我把大部分代码从当时正在工作的项目中复制过来并进行了改装。我使用的是RibbonControlsLibrary和它的RibbonSplitButton,它使用Label而不是Content来描述。 - Tseng
如何在ShellViewModel之外的其他地方引发交互? - Shimmy Weitzhandler

10

本回答仅适用于 Prism 7,
如果您使用之前的版本(6及以下),
那么本回答不适用于您

Prism 7对打开新窗口的方式进行了彻底的改变。
如果您想阅读官方文档,请查看此处

此外,还有一段Youtube视频由Prism库的作者解释这个思路。


Prism 7引入了DialogService,一种全新的打开新窗口的方式。

  1. 创建一个新的UserControl.xaml,代表窗口的内容。
    最简单的空白内容可以是:
<UserControl x:Class="YourUserControlName"
             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:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
    </Grid>
</UserControl>
  1. 创建此视图的相应视图模型。
    该视图模型必须实现IDialogAware接口。
    这是一个例子。
public class BaseDialogViewModel : IDialogAware
{
    public string Title { get; }

    public event Action<IDialogResult> RequestClose;

    public virtual void RaiseRequestClose(IDialogResult dialogResult)
    {
        RequestClose?.Invoke(dialogResult);
    }

    public virtual bool CanCloseDialog()
    {
        return true;
    }

    public virtual void OnDialogClosed()
    {

    }

    public virtual void OnDialogOpened(IDialogParameters parameters)
    {

    }
}

您需要像这样注册对话框:
public void RegisterTypes(IContainerRegistry containerRegistry)
{
    // replace 'YourUserControlName' with the class of the view which you created in setp 1
    containerRegistry.RegisterDialog<YourUserControlName>();
}
  1. 最后一步是在您需要时显示对话框。
    通常,您希望在用户单击按钮或执行操作时显示对话框,
    因此以下代码通常会在某些命令执行时执行,
    但这取决于您。
_dialogService.ShowDialog(nameof(YourUserControlName), new DialogParameters(), action);
  • _dialogServiceIDialogService类型的,它被注入到视图模型中,在使用时会用到它。例如:
public MainViewModel(IDialogService dialogService) 
{
   this._dialogService = dialogService;
}

所有先前的步骤都是为了显示窗口所必需的


还有一些其他可选步骤(如果需要)

  1. 您可以通过添加以下prism:Dialog.WindowStylexaml元素来指定窗口的属性。
<UserControl x:Class="YourUserControlName"
             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:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True">

    <prism:Dialog.WindowStyle>
        <Style TargetType="Window">
            <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" />
            <Setter Property="ResizeMode" Value="NoResize"/>
            <Setter Property="ShowInTaskbar" Value="False"/>
            <Setter Property="WindowState" Value="Maximized"/>
        </Style>
    </prism:Dialog.WindowStyle>

    <Grid>
    </Grid>
</UserControl>

你可以创建一个扩展方法来实现显示窗口的功能。
public static class DialogServiceExtensions
{
    public static void ShowWindowTest(this IDialogService dialogService, Action<IDialogResult> action)
    {
        dialogService.ShowDialog(nameof(WindowTestView), new DialogParameters(), action);
    }
}

Prism文档建议这样做,但不要求


如果您想要一个新的Prism 7 WPF应用程序的样板设置,并带有.NET Core 3.1,则可以查看此Github存储库
它包含上述设置和许多其他有用的功能,用于启动WPF Prism应用程序。

免责声明:我是该存储库的作者


7

你是否使用Prism 7?
如果是,则立即停止阅读并转到下面的Prism 7答案
如果不是,则继续阅读


更新
我提供另一个答案的原因是在我的项目中,使用了Prism 6,无法应用已接受的答案。但在放置原始答案(请参见下文)并在评论中讨论后,我发现核心问题是:Prism 6更改了一些类的名称空间,所有已在接受的答案中使用的类仍然存在于Prism 6中,但在其他dll和命名空间中。因此,如果您正在使用Prism 6,则可以通过这些修改应用已接受的答案。

首先替换这些名称空间

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:pi="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:pit="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"

具有以下命名空间

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="http://prismlibrary.com/"

第二次更新将 XAML 更新为以下内容。
<Button Content="Options" Command="{Binding OpenConnectionOptionsCommand}">
    <i:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding OptionSettingConfirmationRequest, Mode=OneWay}" >
            <prism:PopupWindowAction>
                <prism:PopupWindowAction.WindowContent>
                    <views:CustomPopupView />
                </prism:PopupWindowAction.WindowContent>
            </prism:PopupWindowAction>
        </prism:InteractionRequestTrigger>
    </i:Interaction.Triggers>
</Button>

注意 1
确保你使用的视图(在上面的示例中为<views:CustomPopupWindow>)不是一个窗口,否则会收到异常。

注意 2
只有在使用Prism 6时才需要进行这些修改。因为(如下面的原始答案中所说),被接受的答案使用的dll在Prism 6中已被弃用。

注意 3
确保您正在引用Prism.Wpf dll,该dll可以从Nuget下载


原始答案
被接受的答案针对Prism 5,它使用这个库,该库在Prism 6中已被弃用。

实际上,你问题中引用的文章对我很有帮助,它没有崩溃。

我会尝试总结那篇文章。

ViewModel(视图模型)

public class ViewModel : BindableBase
{
    public ViewModel()
    {
        _showWindowCommand = new DelegateCommand(ShowWindow);
        _interactionRequest = new InteractionRequest<Confirmation>();
    }

    private readonly DelegateCommand _showWindowCommand;
    private InteractionRequest<Confirmation> _interactionRequest;

    public ICommand ShowWindowCommand
    {
        get { return _showWindowCommand; }
    }

    public IInteractionRequest InteractionRequest
    {
        get { return _interactionRequest; }
    }

    private void ShowWindow()
    {
        _interactionRequest.Raise(
            new Confirmation(),
            OnWindowClosed);
    }

    private void OnWindowClosed(Confirmation confirmation)
    {
        if (confirmation.Confirmed)
        {
            //perform the confirmed action...
        }
        else
        {

        }
    }
}

XAML

<Button Command="{Binding ShowWindowCommand}" Content="Show Window" >
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Raised" SourceObject="{Binding InteractionRequest}">
            <i:EventTrigger.Actions>
                <local:ShowWindowAction></local:ShowWindowAction>
            </i:EventTrigger.Actions>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

你需要使用这些命名空间

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:The namespace which contains the ShowWindowAction">

ActionTrigger

using System;
using Prism.Interactivity.InteractionRequest;
using System.Windows.Interactivity;
using System.Windows;

public class ShowWindowAction : TriggerAction<FrameworkElement>
{
    protected override void Invoke(object parameter)
    {
        InteractionRequestedEventArgs args = parameter as InteractionRequestedEventArgs;
        if (args != null)
        {
            Confirmation confirmation = args.Context as Confirmation;
            if (confirmation != null)
            {
                // Replace ParametersWindow with your own window.
                ParametersWindow window = new ParametersWindow();
                EventHandler closeHandler = null;
                closeHandler = (sender, e) =>
                {
                    window.Closed -= closeHandler;
                    args.Callback();
                };
                window.Closed += closeHandler;
                window.Show();
            }
        }
    }
}

说明

  1. 为使此代码正常工作,您需要至少使用Prism.CorePrism.Wpf dlls。
  2. ShowWindow方法将触发ShowWindowActionInvoke方法,从而真正显示窗口。
  3. 您可以在OnWindowClosed中处理窗口的关闭,我们将其作为回调传递给ShowWindowAction类,在那里关闭窗口时调用它。

有什么区别吗?PopupWindowAction在Prism 6中仍然存在(https://github.com/PrismLibrary/Prism/blob/master/Source/Wpf/Prism.Wpf/Interactivity/PopupWindowAction.cs),而且`IConfirmation`和`Confirmation`类在两个框架中仍然存在。除了Prism5当时仍由MS维护,而Prims6现在是社区的,对这些类的使用没有改变。除非您需要`PopupWindowAction`不提供的内容(例如延迟加载,我需要每次调用时都有新的视图和视图模型),否则不需要自己编写`TriggerAction`。 - Tseng
@Tseng 是的,你说得对,我认为区别在于命名空间,类仍然存在,但命名空间已经改变。此外,我刚刚提供了另一种方法,使用自定义的 ActionTrigger 也许对其他人有帮助,我还使用了一个 i:EventTrigger 而不是 InteractionRequestTrigger,但总的来说,我们的答案非常接近。 - Hakan Fıstık
你的代码中的 ShellViewModel 是什么?它是指 MainWindowViewModel 吗?InteractionRequest 应该放在哪里,是在 MainWindow.xaml 中吗? - Shimmy Weitzhandler
这个答案有点令人困惑。它说不要使用Window,否则会崩溃,但没有指明应该使用什么替代品?编辑:这进一步混淆了“CustomPopupWindow”中的“Window”一词。 - Lewis Cianci
@LewisCianci 您必须使用“UserControl”而不是窗口,我会更新我的答案。谢谢。 - Hakan Fıstık
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接