WinRT - MessageDialog.ShowAsync will throw UnauthorizedAccessException in my custom class
Asked Answered
B

4

11

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);
        }
Bottleneck answered 11/12, 2012 at 3:12 Comment(6)
Well, that's painful. You can't show a message box when the UI is already showing one. This needs to be interlocked, very hard to do of course. No real cure but avoid worker threads from pushing boxes by themselves.Bettyannbettye
I find a way, just modify the ShowDialog method as the workaroundBottleneck
Well, that's not a real fix. What happens to code you didn't write that calls ShowAsync(). Still a kaboom.Bettyannbettye
I did write ShowAsync(). await new MessageDialog("Hello!").ShowAsync(); And it can solve this issue.Bottleneck
@Hans Passant I'm trying to show a messagebox after I finish downloading something. An exception should not be thrown because it doesn't have thread access or whatever. Isn't it Async for a reason ? I just don't see it being async anymore. Not after this issue.Regal
Making message box async was evilly unnecessary. So evil and unnecessary that they could have had only one possible goal in mind. To stop you from using it. Which is fair, it is a horrible UI gadget.Bettyannbettye
T
3

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>);
Trahurn answered 11/12, 2012 at 3:20 Comment(1)
It doesn't work. In fact, the effect is same as SynchronizationContext.Post or Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync. All of them are used to show the dialog in UI Thread.Bottleneck
A
1

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

    }
Adal answered 16/10, 2013 at 13:0 Comment(0)
R
0

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.

Regal answered 25/1, 2013 at 7:23 Comment(0)
S
0

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.

Stringpiece answered 23/4, 2013 at 5:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.