#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);
}
}