In-window popups: How to block UI thread?

1k Views Asked by At

I'm currently implementing a MessageDialog control. It is there to replace MessageBox entirely and is displayed as an "in-window popup" (correct UX term needed).

Currently, its constructor is private and there is a method Show, just like in MessageBox.Show. However, MessageBox blocks the UI thread and returns a result.


What my MessageDialog control currently does is having a Action<MessageDialogResult> callback parameter which gets called when a button is clicked.

Utilizing MessageDialog

// class MessageDialog
public static void MessageDialog.Confirmation(Window owner, string message, Action<MessageDialogResult> callback);

// When used by other controls
MessageDialog.Confirmation(WindowMain.Singleton, true, (result) =>
{
    if (result.Button == MessageDialogButton.Yes)
    {
        //...
    }
});

dialog

However, having a callback instead of a blocking method call like in MessageBox.Show yields absolutely no benefits for me. It makes things rather complicated. What I'm rather trying to achieve is something like...

if (MessageDialog.Confirmation(WindowMain.Singleton, true).Button == MessageDialogButton.Yes)
{
    //...
}

... which is much cleaner in my opinion.

The current code behind is basically

  1. Create instance of MessageDialog and populate content with text
  2. Add it to the children of Window.Content.Children
  3. On button click, call callback(result) and remove from Window.Content.Children

The question: What I would like to achieve is having a blocking method call instead of one that triggers a callback.

2

There are 2 best solutions below

1
On BEST ANSWER

How about something like this:

//DialogControl.cs
bool _closed = false;
DialogResult _result;

DialogResult ShowModal()
{
    this.Show();
    while(!_closed) Application.DoEvents();  //Infinite loop
    return _result;
}

public void OkButton_OnClick(EventArgs e, object sender)
{
    _result = DialogResult.OK;
    _closed = true;
}

public void CancelButton_OnClick(EventArgs e, object sender)
{
    _result = DialogResult.Cancel;
    _closed = true;
}
4
On

Even though the accepted answer seems to work, I propose a better solution using TaskCompletionSource. This is exactly what await was made for - it's still basically just a callback (won't block the thread), but your code looks a lot simpler when using it.

TaskCompletionSource<DialogResult> taskSource;

Task<DialogResult> ShowAsync()
{
    return taskSource.Task;
}

public void OkButton_OnClick(EventArgs e, object sender)
{
    taskSource.SetResult(DialogResult.OK);
}

public void CancelButton_OnClick(EventArgs e, object sender)
{
    taskSource.SetResult(DialogResult.Cancel);
}

You then have to await the call: await Dialog.ShowAsync()