C# Modal dialog box (ShowDialog or MessageBox.Show) in async method not works as expected
Solution for problem-1:
ShowDialogAsync extension method solves the problem.
Solution for problem-2:
private async void button1_Click(object sender, EventArgs e)
{
var handle = this.Handle;
await System.Threading.Tasks.Task.Run(() =>
{
System.Threading.Thread.Sleep(2000);
//Solution for Problem2:
NativeWindow win32Parent = new NativeWindow();
win32Parent.AssignHandle(handle);
//Works as expected (Topmost and Modal):
MessageBox.Show(win32Parent, "Test");
});
}
Related topic
How can I use showDialog with await
Not sure if there is a better way, but the following appears to work. Note the "then" clause on the call to showDialog().
void _showAlert3(BuildContext context, String text, int seconds) async {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
title: Text("Error"),
content: Text(text),
)).then((val) {});
await Future.delayed(Duration(seconds: seconds));
Navigator.of(context).pop(true);
}
As for trolls, RTFQ ("structured so that this can be done in one function") and if you don't want to help then go away.
WPF - Task.Run(() = window.ShowDialog) fails
So the "real" question: How can I show a blocking Window when a long running task is running and closing it after the long running task has finished and, eventually, inform the window about the progress of the action.
You've already got most of the pieces; you just need to put them together.
How can I show a blocking Window
All UI should go on a single GUI thread. This isn't strictly necessary, but it's a great simplifier and works for the vast, vast majority of applications. A "blocking Window" is known in the UI world as a "modal dialog", and you show one by calling ShowDialog
.
// Start the long-running operation
var task = LongRunningOperationAsync();
// Show the dialog
progressWindow.ShowDialog();
// Retrieve results / propagate exceptions
var results = await task;
closing it after the long running task has finished
For this, you need to wire up the completion of the task to close the window. This is pretty straightforward to do using async
/await
:
async Task DoOperationAsync(ProgressWindow progressWindow)
{
try
{
await LongRunningOperationAsync();
}
finally
{
progressWindow.Close();
}
}
// Start the long-running operation
var task = DoOperationAsync(progressWindow);
// Show the dialog
progressWindow.ShowDialog();
// Retrieve results / propagate exceptions
var results = await task;
inform the window about the progress of the action
Assuming your operation is using the standard IProgress<T>
interface for reporting progress:
async Task DoOperationAsync(Window progressWindow, IProgress<int> progress)
{
try
{
await LongRunningOperationAsync(progress);
}
finally
{
progressWindow.Close();
}
}
var progressWindowVM = ...;
var progress = new Progress<int>(value =>
{
progressWindowVM.Progress = value;
});
var task = DoOperationAsync(progressWindow, progress);
progressWindow.ShowDialog();
var results = await task;
Another common use case to consider is the cancelling of the operation if the user closes the progress dialog themselves. Again, this is straightfoward if your operation is already using the standard CancellationToken
:
async Task DoOperationAsync(Window progressWindow, CancellationToken token, IProgress<int> progress)
{
try
{
await LongRunningOperationAsync(token, progress);
}
catch (OperationCanceledException) { }
finally
{
progressWindow.Close();
}
}
var progressWindowVM = ...;
var progress = new Progress<int>(value =>
{
progressWindowVM.Progress = value;
});
var cts = new CancellationTokenSource();
progressWindow.Closed += (_, __) => cts.Cancel();
var task = DoOperationAsync(progressWindow, cts.Token, progress);
progressWindow.ShowDialog();
var results = await task;
The solution should (preferably) work with the MVVM pattern and not rely on (too much) code behind.
MVVM works great within a single window. As soon as you start trying to data-bind window-level actions and attributes, a lot of it falls apart. This is not due to MVVM being a poor pattern, but rather just that a lot of MVVM frameworks do not handle this well.
The example code above only uses data binding to report progress to the progress dialog. If your MVVM framework can data-bind the showing/hiding of a modal window, then you could use my NotifyTaskCompletion
type to drive that. Also, some frameworks have a more elegant (MVVM) way to handle Window.Closed
, but the details depend on your framework.
Button.DialogResult, Form.ShowDialog and async event handlers out of -expected- order execution
This is expected behaviour, as you wait asynchronously for the Task.Delay
task to finish. While that is being done, the method returns to its caller, and this is by design. Take this example:
private void button1_Click(object sender, EventArgs e)
{
method();
textBox1.Text += "C";
}
async void method()
{
textBox1.Text += "A";
await Task.Delay(1000);
textBox1.Text += "B";
}
This is pretty much the same situation, the text of the TextBox will be ACB
after pressing button1, as the method method()
returns to its caller when await Task.Delay(1000);
is called.
To get the desired output, do it as Hans Passant suggested in the comments: Set the DialogResult
property of the button to None
, so that the method does not return to the Form1
. Then, whenever you wish to hand control over to Form1
again, simply call this.DialogResult = DialogResult.OK
in the method on runtime, which will have the same effect.
Continue code execution after calling ShowDialog()
As the author of the linked answer, I feel summoned :) I think I understand the problem you're running into. Try the following, see the comments inline:
// on the main message loop of the UI thread
var LoadingTask = LoadingWindow.ShowDialogAsync();
// dialog isn't shown here yet, yield to the message loop
await Task.Yield();
// now ShowDialog has been called and the dialog is visible,
// we're now on the nested message loop run by ShowDialog
await Task.Run(() =>
{
// on a thread pool thread, do the work here
Thread.Sleep(2000);
});
// on the UI thread, still on the nested message loop of ShowDialog
// close the dialog
LoadingWindow.Close();
// still on the nested message loop, despite the window has closed
await LoadingTask;
// this is when ShowDialog method has returned,
// and we're back to the main message loop of the UI thread
Don't use Thread.Sleep
or any other long-running synchronous code on the UI thread, even when experementing. The dialog is shown asynchronously. Thread.Sleep
blocks the message loop and the ShowDialog
isn't getting a chance to execute when you expect it to.
It helps to understand what Task.Yield
actually does behind the sence in this case. The continuation code after await Task.Yield()
won't get magically executed until after some future iterations of the message loop. And there will be no future iterations while you're making a blocking call on the UI thread. That's what the thread pool and Task.Run
are for.
Otherwise, the continuation callback will be just sitting in the queue managed by WindowsFormsSynchronizationContext
or DispatcherSynchronizationContext
.
Flutter awaiting result of dialog
You can add await keyword in-front of showdialog and return value at the end of show dialog.
added await.
await showDialog(
add return value
barrierDismissible: false);
return true; // added line
Show modal dialog while performing an asynchronous operation
As discussed in the comments, you are simply using the wrong Control reference to call BeginInvoke() on. The dialog cannot work, it wasn't created yet. Which is important, .NET cannot figure out what thread owns the window until the window is committed to a specific thread. Which doesn't happen until you call Show().
But any other window will do, you must already have one since it isn't legal or practical to display a dialog without an owner. You must provide a constructor or a property so the client code can provide a reference to such a window. Any will do, the main window of the app is a practical choice.
You can completely separate a class from the UI implementation (not practical here since you display a dialog) by copying the value of Synchronization.Current in the constructor. On which you later can call its Post() method. All UI class libraries in .NET install a synchronization provider that will marshal the call.
Related Topics
Entity Framework with Xml Files
How to Call the Parent Version of an Overridden Method? (C# .Net)
Patch Async Requests with Windows.Web.Http.Httpclient Class
Active Directory Com Exception - an Operations Error Occurred (0X80072020)
Wpf Binding - Default Value for Empty String
How to Create an Instance from a String in C#
C# Getting Value of Params Using Reflection
Is That Possible to Send Httpwebrequest Using Tls1.2 on .Net 4.0 Framework
How to Convert Namevaluecollection to JSON String
How to Make 'Always-On-Bottom'-Window
Simulating Keyboard with Sendinput API in Directinput Applications
How to Change Originating Ip in Httpwebrequest
Detect Windows Font Size (100%, 125%, and 150%)
Avoiding Null Reference Exceptions
Different Forms of the Wcf Service Contract Interface
MVC 4 How Pass Data Correctly from Controller to View