WinRT - MessageDialog.ShowAsync will throw Unautho

2019-02-16 13:14发布

I Want to write my own control, when the ctor is invoked, a MessageBox is shown.

public class Class1
{
    public Class1()
    {
        ShowDialog();
    }
    void ShowDialog()
    {
        SynchronizationContext context = SynchronizationContext.Current;
        if (context != null)
        {
            context.Post((f) =>
            {
                MessageDialog dialog = new MessageDialog("Hello!");
                dialog.ShowAsync();
            }, null);
        }
    }
}

If my class is used by someone, and write the codes as below, UnauthorizedAccessException is always thrown in dialog.ShowAsync();

void MainPage_Loaded(object sender, RoutedEventArgs e)
        {

            ClassLibrary1.Class1 c1 = new ClassLibrary1.Class1();
            MessageDialog dialog1 = new MessageDialog("");
            dialog1.ShowAsync();
        }

Is there a way to show a message dialog without exception?


I found a way, enjoy it!

Task ShowDialog()
        {
            CoreDispatcher dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
            Func<object, Task<bool>> action = null;
            action = async (o) =>
            {
                try
                {
                    if (dispatcher.HasThreadAccess)
                        await new MessageDialog("Hello!").ShowAsync();
                    else
                    {
                        dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                        () => action(o));
                    }
                    return true;
                }
                catch (UnauthorizedAccessException)
                {
                    if (action != null)
                    {
                        Task.Delay(500).ContinueWith(async t => await action(o));
                    }
                }
                return false;
            };
            return action(null);
        }

4条回答
Summer. ? 凉城
2楼-- · 2019-02-16 13:41

The cleaner solution may look like this. The interesting part ist hidden in die showDialogAsync(). For convenience you can use the Close() method to close the current dialog again programmatically. The IUIDispatcher is another helper interface you can rebuild yourself easily:

    private readonly IUIDispatcher _dispatcher;
    readonly Object _queueMonitor = new object();
    readonly Object _showMonitor = new object();
    private IAsyncOperation<IUICommand> _currentDialogOperation;
    readonly Queue<MessageDialog> _dialogQueue = new Queue<MessageDialog>();


    public async Task ShowAsync(string content)
    {
        var md = new MessageDialog(content);
        await showDialogAsync(md);
    }

    public async Task ShowAsync(string content, string caption)
    {
        var md = new MessageDialog(content, caption);
        await showDialogAsync(md);
    }

    public async Task<MessageDialogResult> ShowAsync(string content, MessageDialogType dialogType)
    {
        var messageDialogResult = await ShowAsync(content, null, dialogType);
        return messageDialogResult;
    }

    public async Task<MessageDialogResult> ShowAsync(string content, string caption, MessageDialogType dialogType)
    {
        var result = MessageDialogResult.Ok;


            var md = string.IsNullOrEmpty(caption) ?  new MessageDialog(content) : new MessageDialog(content, caption);

        switch (dialogType)
        {
            case MessageDialogType.Ok:
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
                md.CancelCommandIndex = 0;
                md.DefaultCommandIndex = 0;
                break;
            case MessageDialogType.OkCancel:
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
                md.DefaultCommandIndex = 0;
                md.CancelCommandIndex = 1;
                break;
            case MessageDialogType.YesNo:
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
                  md.DefaultCommandIndex = 0;
                md.CancelCommandIndex = 1;
                break;
            case MessageDialogType.YesNoCancel:
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
                md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
                md.DefaultCommandIndex = 0;
                md.CancelCommandIndex = 1;
                break;
            default:
                throw new ArgumentOutOfRangeException("dialogType");
        }

        await showDialogAsync(md);

        return result;
    }


    /// <summary>
    /// Shows the dialogs, queued and one after the other.
    /// We need this as a workaround for the the UnauthorizedAcsess exception. 
    /// </summary>
    /// <param name="messageDialog">The message dialog.</param>
    /// <returns></returns>
    async Task showDialogAsync(MessageDialog messageDialog)
    {
        //Calls this function in a separated task to avoid ui thread deadlocks.
        await Task.Run(async () => 
        { 
            lock (_queueMonitor)
            {
                _dialogQueue.Enqueue(messageDialog);
            }
            try
            {
                while (true)
                {
                    MessageDialog nextMessageDialog;
                    lock (_queueMonitor)
                    {
                        if (_dialogQueue.Count > 1)
                        {
                            Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Next dialog is waiting for MessageDialog to be accessable!!");
                            Monitor.Wait(_queueMonitor); //unlock and wait - regains lock after waiting
                        }

                        nextMessageDialog = _dialogQueue.Peek();
                    }

                    var showing = false;
                    _dispatcher.Execute(async () =>  
                    {
                        try
                        {
                            lock (_showMonitor)
                            {
                                showing = true;
                                _currentDialogOperation = nextMessageDialog.ShowAsync();
                            }

                            await _currentDialogOperation;

                            lock (_showMonitor)
                                _currentDialogOperation = null;
                        }
                        catch (Exception e)
                        {
                            Debug.WriteLine("MessageDialogService.cs | showDialogAsync | " + e);
                        }
                        lock (_showMonitor)
                        {
                            showing = false;
                            Monitor.Pulse(_showMonitor); //unlock and wait - regains lock after waiting
                        }
                    });


                    lock (_showMonitor)
                    {
                        if (showing)
                        {
                            Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Waiting for MessageDialog to be closed!!");
                            //we must wait here manually for the closing of the dialog, because the dispatcher does not return a waitable task.
                            Monitor.Wait(_showMonitor); //unlock and wait - regains lock after waiting
                        }
                    }
                    Debug.WriteLine("MessageDialogService.cs | showDialogAsync | MessageDialog  was closed.");
                    return true;
                }
            }
            finally
            {
                //make sure we leave this in a clean state
                lock (_queueMonitor)
                {
                    _dialogQueue.Dequeue();
                    Monitor.Pulse(_queueMonitor);
                }
            }
        });
    }


    public void Close(string keyContent="")
    {
        try
        {
            if (keyContent.IsNullOrEmpty())
            {
                lock (_showMonitor)
                {
                    if (_currentDialogOperation == null) return;
                    _currentDialogOperation.Cancel();
                    _currentDialogOperation = null;
                }
            }
            else
            {
                var cancel = false;
                lock (_queueMonitor)
                {
                    if (_dialogQueue.Count == 0)
                        return;

                    var currentDialog = _dialogQueue.Peek();

                    Debug.WriteLine("MessageDialogService.cs | Close | {0}", currentDialog.Content);
                    if (currentDialog.Content == keyContent)
                    {
                        cancel = true;
                    }
                }
                if (!cancel) return;
                lock (_showMonitor)
                {
                    if (_currentDialogOperation == null) return;
                    _currentDialogOperation.Cancel();
                    _currentDialogOperation = null;
                }
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine("MessageDialogService.cs | Close | " + e);
        }

    }
查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-02-16 13:53

As MessageDialogue needs to run on the UI thread, can you try switching it to:

var dispatcher = Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher;
dispatcher.RunAsync(DispatcherPriority.Normal, <lambda for your code which should run on the UI thread>);
查看更多
该账号已被封号
4楼-- · 2019-02-16 13:59

I think I've found it. I had the same problem when creating messageboxes in any other threads besides the main thread. This is the C++ solution but I think you can convert it easily ;)

    IAsyncOperation<IUICommand^> ^Op = msgbox->ShowAsync();
    task<IUICommand^>( Op ).then([=](IUICommand^ C)
    {

    }).then([](task<void> t) 
        {
            try 
            {
                t.get();
            }
            catch (Platform::Exception ^e)
            {   
                //ERROR!                            
            }           
        });

On a side note this is the correct way to handle ANY WinRT/Windows 8 Store C++ exception.

查看更多
萌系小妹纸
5楼-- · 2019-02-16 14:07

You can always use

Execute.OnUIThread( async () => {
...
var dialog = new MessageDialog(yourMessage);
await dialog.ShowAsync();
...
});

This doesn't solve race conditions in the UI if you are trying to launch multiple dialogs from background threads. But you could use a try/catch to make sure you cover for that case.

查看更多
登录 后发表回答