Prism: DialogService — Активировать существующий немодальный диалог

#c# #wpf #prism

#c# #wpf #prism

Вопрос:

Мы работаем над переносом наших INotification / IConfirmation диалогов в Prism DialogService . Одна из проблем, с которой мы сталкиваемся, заключается в поддержке настройки, которую мы внесли, чтобы PopupWindowAction при необходимости «вывести на передний план» существующий немодальный диалог, если действие было вызвано во второй раз.

Можно ли это сделать с DialogService ?

В частности, если немодальное окно свернуто или неактивно (за другим окном), как мы можем его активировать? В настоящее время мы используем код, подобный следующему, который распознает, что ранее отображалось определенное INotification , и просто активирует его.

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

        window.Activate();

        return;
    }
}
  

Мы рассмотрели возможность расширения DialogService ; к сожалению, кажется, что нам нужно будет расширить ShowDialogInternal , который не является виртуальным. Также не IDialogWindow предоставляет WindowState или Activate .

Кажется, мы могли бы выполнить это извне, зарегистрировав экземпляр диалога и управляя этой активацией извне DialogService . Хотя я хотел бы работать с Prism как можно больше здесь и минимизировать сложность для ViewModel .

Ответ №1:

Можно ли это выполнить с помощью DialogService?

Нет, это не поддерживается Prism DialogService .

[…] к сожалению, кажется, что нам нужно будет расширить ShowDialogInternal, который не является виртуальным.

Как вы уже заметили, виртуальных методов не существует, поэтому вы не можете извлекать из него и переопределять его поведение. Вы должны создать свою собственную реализацию, но, по крайней мере, вы можете скопировать реализацию по умолчанию с GitHub и адаптировать ее к вашим потребностям.

IDialogWindow также не предоставляет WindowState или Activate.

IDialogWindow Интерфейс имеет ограничение или выбор дизайна в том смысле, что он не предоставляет те же методы или свойства, что и Window имеет. Если вы можете гарантировать, что ваш узел диалога является типом, производным от Window , вы можете просто привести IDialogWindow экземпляр в вашей пользовательской службе диалога к Window .

Более обобщенным вариантом было бы создать пользовательский интерфейс диалогового окна:

  • Создайте свой собственный ICustomDialogWindow интерфейс, который наследуется IDialogWindow для совместимости
  • Добавьте к нему недостающие свойства и методы, например Activate
  • Создайте CustomWindow , например, извлекая из Window и реализуя ICustomDialogWindow
  • Зарегистрируйте этот тип окна в качестве нового основного окна диалога по умолчанию в контейнере
     containerRegistry.RegisterDialogWindow<ConfirmationWindow>();
      

Теперь вы можете привести экземпляр диалогового окна к ICustomDialogWindow интерфейсу вашего пользовательского диалогового сервиса и Activate его. В качестве альтернативы, вы также можете реализовать поведение активации в самой службе диалога, тогда интерфейс просто служит для предоставления необходимых методов и свойств окна для этого. Реализация активации в конкретном классе диалогового окна является более гибкой, поскольку ее можно специализировать для базового типа или применять, когда обычная реализация невозможна.

Если вы уже используете Prism 8 и допускаете несколько узлов диалога, вы можете реализовать ICustomDialogWindow интерфейс во всех оконных узлах и выдать исключение, если хост этого не делает, или просто предоставить разные варианты поведения в зависимости от типа, например, IDialogWindow просто не будет поддерживать активацию. В общем, вам нужно самостоятельно создавать типы окон узла диалога для реализации интерфейсов, поэтому это не должно быть проблемой.

В частности, если немодальное окно свернуто или неактивно (за другим окном), как мы можем его активировать?

Предоставленный вами код должен нормально работать в этом сценарии. Вам просто нужно решить, хотите ли вы реализовать его в службе диалога или в вашем пользовательском хосте window.

Кажется, мы могли бы выполнить это извне, зарегистрировав экземпляр диалога и управляя этой активацией извне по отношению к DialogService.

Тогда вы полностью обойдете службу диалога, поскольку она не предоставляет экземпляры созданных диалогов. Вам нужно было бы найти их в коллекции приложений Windows и самостоятельно выполнить активацию, что составило бы почти половину диалогового сервиса, поэтому вы могли бы создать пользовательский сервис в первую очередь.

Комментарии:

1. Спасибо, это подтверждает мои подозрения. Я могу отправить запрос на улучшение в Prism, чтобы сделать ShowDialogInternal (или аналогичный) виртуальным. Я полагаю, что тогда я мог бы выполнить это без полной замены сервиса.

Ответ №2:

Ниже приведено решение, которое я придумал для решения моего первоначального вопроса. По сути, DialogServiceEx поддерживает необязательную активацию существующего немодального диалога вместо отображения второй копии. Кроме того, владелец диалога также необязателен; это позволяет диалогу быть независимым от владельца (может быть независимо свернут и т.д.)

Примечание: Служба не является потокобезопасной. Предполагается, что диалоги будут отображаться только в потоке пользовательского интерфейса.

 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 amp;amp;
            _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);
    }
}