棱镜:DialogService - 激活现有的非模态对话框

Prism: DialogService - Activate existing non-modal dialog

我们正在努力将我们的 INotification/IConfirmation 对话框迁移到 Prism 的 DialogService。我们遇到的一个麻烦是支持我们对 PopupWindowAction 进行的自定义,以便在第二次调用该操作时可选择“将现有的非模态对话框置于前面”。

这可以用 DialogService 完成吗?

具体来说,如果非模态 window 被最小化或处于非活动状态(在另一个 window 之后),我们如何激活它?我们目前正在使用如下代码,它可以识别之前显示的特定 INotification 并简单地激活它。

if (BringToFrontIfExisting)
{
    if (NotificationWindowMap.TryGetValue(notification, out Window window))
    {
        if (window.WindowState == WindowState.Minimized)
        {
            window.WindowState = WindowState.Normal;
        }

        window.Activate();

        return;
    }
}

我们研究了扩展 DialogService;不幸的是,我们似乎需要扩展非虚拟的 ShowDialogInternalIDialogWindow 也不公开 WindowStateActivate.

看来我们可以通过注册对话框的实例并在 DialogService 外部管理此激活来在外部完成此操作。虽然我想在这里尽可能多地使用 Prism 并尽量减少 ViewModel.

的复杂性

Can this be accomplished with DialogService?

不,Prism 的 DialogService 不支持此功能。

[...] unfortunately it seems we'd need to extend ShowDialogInternal which is not virtual.

正如您已经注意到的,没有虚拟方法,因此您不能从它派生并覆盖它的行为。您必须创建自己的实现,但至少可以复制 default implementation from GitHub 并根据您的需要进行调整。

Nor does IDialogWindow expose WindowState or Activate.

IDialogWindow 接口有一个限制或设计选择,因为它不提供与 Window 相同的方法或属性。如果您可以保证您的对话宿主是从Window派生的类型,您可以将自定义对话服务中的IDialogWindow实例转换为Window.

更通用的变体是创建自定义对话框window界面:

  • 创建您自己的 ICustomDialogWindow 继承 IDialogWindow 的接口以实现兼容性
  • 将缺少的属性和方法添加到其中,例如Activate
  • 创建 CustomWindow,例如派生自 Window 并实施 ICustomDialogWindow
  • 在容器中将此 window 类型注册为新的默认对话主机 window
    containerRegistry.RegisterDialogWindow<ConfirmationWindow>();
    

现在您可以将对话框 window 实例转换为自定义对话框服务中的 ICustomDialogWindow 接口,然后 Activate 它。或者,您也可以在对话服务本身中实现激活行为,然后接口仅用于公开 window 所需的方法和属性来执行此操作。在具体对话框中实现激活 window class 更灵活,因为它可以专门用于底层类型或在无法实现通用实现时应用。

如果您已经使用 Prism 8 并允许多个对话主机,则可以在所有 window 主机中实现 ICustomDialogWindow 接口,如果主机不提供或仅提供不同的行为则抛出异常取决于类型,例如IDialogWindow 将不支持激活。通常,您必须自己创建对话主机 window 类型来实现接口,所以这应该不是问题。

Specifically, if a non-modal window is minimized or inactive (behind another window), how can we activate it?

您提供的代码在这种情况下应该可以正常工作。您只需决定是否要在对话服务或您的自定义 window 主机中实现它。

It seems we might be able to accomplish this externally by registering an instance of the dialog and managing this activation external to the DialogService.

那么您将完全绕过对话框服务,因为它不会公开已创建对话框的实例。您必须从应用程序 windows 集合中找到它们并自己处理激活,这几乎是对话服务的一半,因此您可以首先创建自定义服务。

以下是我为解决我的原始问题而提出的解决方案。本质上,DialogServiceEx 支持选择性地激活现有的无模式对话框,而不是显示第二个副本。此外,对话框所有者也是可选的;这允许对话框独立于所有者(可以独立最小化等)

注意:该服务不是 thread-safe。假设对话框只会显示在 UI 线程上。

public class DialogServiceEx : DialogService, IDialogServiceEx
{
    private readonly Dictionary<string, IDialogWindow> _reusableDialogWindows = new Dictionary<string, IDialogWindow>();

    public DialogServiceEx(IContainerExtension containerExtension)
        : base(containerExtension)
    {
    }

    public void Show(
        string name,
        IDialogParameters parameters,
        Action<IDialogResult> callback,
        bool reuseExistingWindow,
        bool setOwner = true)
            => ShowDialogInternalEx(name, parameters, callback, null, reuseExistingWindow, setOwner);

    public void Show(
        string name,
        IDialogParameters parameters,
        Action<IDialogResult> callback,
        string windowName,
        bool reuseExistingWindow,
        bool setOwner = true)
            => ShowDialogInternalEx(name, parameters, callback, windowName, reuseExistingWindow, setOwner);

    private void ShowDialogInternalEx(
        string name,
        IDialogParameters parameters,
        Action<IDialogResult> callback,
        string windowName,
        bool reuseExistingWindow,
        bool setOwner)
    {
        string dialogKey = $"{name}-{windowName}";
        if (reuseExistingWindow &&
            _reusableDialogWindows.TryGetValue($"{name}-{windowName}", out IDialogWindow dialogWindow))
        {
            dialogWindow.Show();
            if (dialogWindow is Window window)
            {
                // NOTE: IDialogWindow should always be a Window under WPF.

                if (window.WindowState == WindowState.Minimized)
                {
                    window.WindowState = WindowState.Normal;
                }

                window.Activate();
                return;
            }
        }

        dialogWindow = CreateDialogWindow(windowName);

        if (reuseExistingWindow)
        {
            _reusableDialogWindows.Add(dialogKey, dialogWindow);
            dialogWindow.Closed +=
                (_, __) =>
                {
                    Debug.Assert(_reusableDialogWindows.ContainsKey(dialogKey), "Expect single-threaded access only.");
                    _reusableDialogWindows.Remove(dialogKey);
                };
        }

        ConfigureDialogWindowEvents(dialogWindow, callback);
        ConfigureDialogWindowContent(name, dialogWindow, parameters ?? new DialogParameters());

        if (setOwner == false)
        {
            dialogWindow.Owner = null;
        }

        ShowDialogWindow(dialogWindow, false);
    }
}