3

如果您在另一个对象已向用户显示但未关闭时调用该ShowAsync命令(即MessageDialog当另一个MessageDialog对象已启动时显示弹出窗口),UnauthorizedAccessException则会抛出 an 。当您有多个线程试图同时提醒用户时,这会使事情变得困难。

我当前的(权宜之计)解决方案只是ShowAsync用 try/catch 块包围调用并吞下异常。这不希望地导致用户错过后续通知。我能想到的唯一其他方法是手动实现某种弹出队列。这似乎是一项过多的工作,然而,考虑到其他框架(如 Windows Phone)没有这个问题,并且只会在用户关闭它们时一个接一个地显示弹出窗口。

有没有其他方法可以解决这个问题?

4

2 回答 2

11

您可以使用此扩展方法轻松做到这一点:

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;
    }
}
于 2013-01-23T22:15:56.413 回答
10

有很多方法可以解决它,选择可能取决于您的技能、要求和偏好。

我个人的选择是完全避免使用对话框,因为它们不利于用户体验(邪恶)。然后有替代解决方案,例如使用 UI 显示单独的屏幕/页面,要求用户在确实需要时提供一些输入,或者如果用户输入是可选的,则在侧面/边缘/角落的某处显示非模态弹出窗口并隐藏它经过片刻或其他不会破坏用户流程的通知。

如果您不同意或没有时间、资源或技能来实现替代方案 - 您可以围绕 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();
于 2013-01-23T20:59:11.803 回答