Как разрешить несколько всплывающих окон одновременно в WinRT?
Если вы позвоните ShowAsync
командовать MessageDialog
возражать, когда другой MessageDialog
объект уже был показан пользователю, но не удален (т. е. вы показываете всплывающее окно, когда другой уже открыт), UnauthorizedAccessException
брошен Это может осложнить ситуацию, когда несколько потоков пытаются одновременно предупредить пользователя.
Мое текущее (временное) решение - просто окружить ShowAsync
вызовите блок try / catch и проглотите исключение. Это нежелательно приводит к тому, что пользователь пропускает последующие уведомления. Единственный способ обойти это - вручную реализовать какую-либо очередь всплывающих окон. Это похоже на чрезмерный объем работы, однако, учитывая, что другие платформы (например, Windows Phone) не имеют этой проблемы и будут просто отображать всплывающие окна один за другим, когда пользователь отклоняет их.
Есть ли другой способ решить эту проблему?
2 ответа
Есть много способов подойти к нему, и выбор может зависеть от ваших навыков, требований и предпочтений.
Мой личный выбор - вообще избегать использования диалоговых окон, так как они вредны для пользователя ( зло). Затем существуют альтернативные решения, такие как отображение отдельного экрана / страницы с пользовательским интерфейсом, требующим от пользователя предоставления некоторого ввода, когда это действительно необходимо, или отображение немодального всплывающего окна где-то по бокам / краю / углу, если пользовательский ввод является необязательным, и его скрытие. через мгновение или какое-то другое уведомление, которое не нарушает поток пользователей.
Если вы не согласны или у вас нет времени, ресурсов или навыков для реализации альтернативы - вы можете создать своего рода оболочку для вызова MessageDialog.ShowAsync(), чтобы либо ставить в очередь, либо игнорировать новые запросы, пока диалог уже показан.
Этот класс имеет методы расширения, позволяющие либо игнорировать новый запрос на показ, когда другой диалог уже отображается, либо ставить запросы в очередь:
/// <summary>
/// MessageDialog extension methods
/// </summary>
public static class MessageDialogExtensions
{
private static TaskCompletionSource<MessageDialog> _currentDialogShowRequest;
/// <summary>
/// Begins an asynchronous operation showing a dialog.
/// If another dialog is already shown using
/// ShowAsyncQueue or ShowAsyncIfPossible method - it will wait
/// for that previous dialog to be dismissed before showing the new one.
/// </summary>
/// <param name="dialog">The dialog.</param>
/// <returns></returns>
/// <exception cref="System.InvalidOperationException">This method can only be invoked from UI thread.</exception>
public static async Task ShowAsyncQueue(this MessageDialog dialog)
{
if (!Window.Current.Dispatcher.HasThreadAccess)
{
throw new InvalidOperationException("This method can only be invoked from UI thread.");
}
while (_currentDialogShowRequest != null)
{
await _currentDialogShowRequest.Task;
}
var request = _currentDialogShowRequest = new TaskCompletionSource<MessageDialog>();
await dialog.ShowAsync();
_currentDialogShowRequest = null;
request.SetResult(dialog);
}
/// <summary>
/// Begins an asynchronous operation showing a dialog.
/// If another dialog is already shown using
/// ShowAsyncQueue or ShowAsyncIfPossible method - it will wait
/// return immediately and the new dialog won't be displayed.
/// </summary>
/// <param name="dialog">The dialog.</param>
/// <returns></returns>
/// <exception cref="System.InvalidOperationException">This method can only be invoked from UI thread.</exception>
public static async Task ShowAsyncIfPossible(this MessageDialog dialog)
{
if (!Window.Current.Dispatcher.HasThreadAccess)
{
throw new InvalidOperationException("This method can only be invoked from UI thread.");
}
while (_currentDialogShowRequest != null)
{
return;
}
var request = _currentDialogShowRequest = new TaskCompletionSource<MessageDialog>();
await dialog.ShowAsync();
_currentDialogShowRequest = null;
request.SetResult(dialog);
}
}
Тестовое задание
// This should obviously be displayed
var dialog = new MessageDialog("await ShowAsync", "Dialog 1");
await dialog.ShowAsync();
// This should be displayed because we awaited the previous request to return
dialog = new MessageDialog("await ShowAsync", "Dialog 2");
await dialog.ShowAsync();
// All other requests below are invoked without awaiting
// the preceding ones to complete (dialogs being closed)
// This will show because there is no dialog shown at this time
dialog = new MessageDialog("ShowAsyncIfPossible", "Dialog 3");
dialog.ShowAsyncIfPossible();
// This will not show because there is a dialog shown at this time
dialog = new MessageDialog("ShowAsyncIfPossible", "Dialog 4");
dialog.ShowAsyncIfPossible();
// This will show after Dialog 3 is dismissed
dialog = new MessageDialog("ShowAsyncQueue", "Dialog 5");
dialog.ShowAsyncQueue();
// This will not show because there is a dialog shown at this time (Dialog 3)
dialog = new MessageDialog("ShowAsyncIfPossible", "Dialog 6");
dialog.ShowAsyncIfPossible();
// This will show after Dialog 5 is dismissed
dialog = new MessageDialog("ShowAsyncQueue", "Dialog 7");
dialog.ShowAsyncQueue();
// This will show after Dialog 7 is dismissed
dialog = new MessageDialog("ShowAsyncQueue", "Dialog 8");
dialog.ShowAsyncQueue();
Вы можете легко сделать это с помощью этого метода расширения:
public static class MessageDialogShower
{
private static SemaphoreSlim _semaphore;
static MessageDialogShower()
{
_semaphore = new SemaphoreSlim(1);
}
public static async Task<IUICommand> ShowDialogSafely(this MessageDialog dialog)
{
await _semaphore.WaitAsync();
var result = await dialog.ShowAsync();
_semaphore.Release();
return result;
}
}