-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Question on how to show a modal dialog synchronously #4810
Comments
You have it right, just run in main UI thread (Outside the task) So, in your main UI code: async void callMyDialog()
{
Dialog dialog = new Dialog();
await dialog.ShowDialog(this);
} |
All your calls must be awaitable and marked as async. async void showDialog()
{
await callMyDialog(); //I want to block the showDialog method from executing at this point until the dialog is closed
}
async void callMyDialog()
{
Dialog dialog = new Dialog();
await dialog.ShowDialog(this);
}
await showDialog(); |
That's exactly the point of this question: how do I do this without my method being async? Is it impossible? |
but why that requirement? why you want your methods not to be async? Anyway, you can just call dialog.ShowDialog(this);, and attach an OnClose event if you want to process some data from it in case you dont want to await. eg: var window = new AboutWindow();
window.OnClosed += (...) => My code here after window close
window.ShowDialog(this);
// This will continue to execute after your last call, without wait for dialog close,
// but will also lock the background window from user interaction Also note you can't call UI components from another thread. |
How do you trigger the synchronous Does it e.g. happen from a click/Command? |
void showDialog()
{
dialog.ShowDialog(this).GetAwaiter().GetResult();
} |
A workaround (adapted from #857 (comment)) is this: It will block until the dialog closes. |
@MarkusAmshove There's a long line of synchronous methods that call this - it's not just a matter of making the caller (or even the caller's caller) async @workgroupengineering I had tried that before - that still results in the empty dialog box issue where it prevents the dialog UI from loading. @jp2masa Ah, thanks to your help I got it! Here is what I ended up using:
And then I can iterate on top of that to add the dialog message and other things |
@derekantrican if that works fine for you and you use it for multiple windows, consider making extension to ease your code. public static void ShowDialogSync(this Window window, Window parent = null)
{
if (parent is null) parent = window;
using (var source = new CancellationTokenSource())
{
window.ShowDialog(parent).ContinueWith(t => source.Cancel(), TaskScheduler.FromCurrentSynchronizationContext());
Dispatcher.UIThread.MainLoop(source.Token);
}
}
public static T ShowDialogSync<T>(this Window window, Window parent = null)
{
if (parent is null) parent = window;
using (var source = new CancellationTokenSource())
{
var task = window.ShowDialog<T>(parent);
task.ContinueWith(t => source.Cancel(), TaskScheduler.FromCurrentSynchronizationContext());
Dispatcher.UIThread.MainLoop(source.Token);
return task.Result;
}
return default(T);
} |
After doing a bunch of iterating since yesterday (and stumbling into a number of issues) I've improved upon the solution here in a way that still provides the desired functionality (acting like private MyDialogResult ShowDialog(DialogViewModel dialogViewModel)
{
MyDialogResult result = MyDialogResult.Cancel;
using (CancellationTokenSource source = new CancellationTokenSource())
{
if (Dispatcher.UIThread.CheckAccess()) //Check if we are already on the UI thread
{
Dialog dialog = new Dialog(dialogViewModel);
dialog.ShowDialog<MyDialogResult>(Parent).ContinueWith(t =>
{
result = t.Result;
source.Cancel();
});
Dispatcher.UIThread.MainLoop(source.Token);
}
else
{
Dispatcher.UIThread.InvokeAsync(() =>
{
Dialog dialog = new Dialog(dialogViewModel);
dialog.ShowDialog<MyDialogResult>(Parent).ContinueWith(t =>
{
result = t.Result;
source.Cancel();
});
});
while (!source.IsCancellationRequested) { } //Loop until dialog is closed
}
}
return result;
} Of course, this is based on a MVVM format and could also be "extension-ized" like @sn4k3 's suggestion but should give a good starting point if anyone else comes across this |
For anyone coming here in the future, this is a bad idea if you plan to have your application run on MacOS: #5229 . Use async dialogs instead |
The following solution provided by @grokys worked for us. I tested it in macOS high Sierra, Windows 10, Ubuntu 19.04. |
The |
Actually, it looks like the problem is with MessageBox.Avalonia. When using a simple |
The code provided grokys worked, but a strange bug happens here, My program look like this: [1] Async Event (ButonClicked) => [2] calls a sync function => [3] sync function calls ShowDialog after the ShowDialog returns, the window keep visible To be able to hide the window at least, I call Hide() with Dispatcher before Close() and it's fine. (Tested under Linux) Why I call a sync function? |
Running into this issue in a case where I need to spawn a PIN entry dialog from a callback delegate. I have no control over the library expecting the delegate, and the delegate cannot be async. A simple way of creating a modal dialog that stops the main thread until it returns would be nice to have. |
@SCLDGit on desktop (and only desktop) you can create this extension in 11.0: internal static T WaitOnDispatcherFrame<T>(this Task<T> task)
{
if (!task.IsCompleted)
{
var frame = new DispatcherFrame();
_ = task.ContinueWith(static (_, s) => ((DispatcherFrame)s).Continue = false, frame);
Dispatcher.UIThread.PushFrame(frame);
}
return task.GetAwaiter().GetResult();
} But again, if it's possible, it should be avoided even at high cost. |
My 2 cents here:
Check out AwsomeAvalonia for inspiration (for example Aura.UI). Good luck. |
I know it is too late but here is what is the solution using Version 11.x (do not know if its the same for previous versions ) |
@maxkatz6 using this approach seems to work, however, we're experiencing issues with TextBoxes, at least in macOS. When we display a dialog using this mechanism, and the dialog has a input.movIs there any workaround for this? Thanks! |
Please try the latest nightly, we've recently fixed an issue with timers not ticking in a nested event loop on macOS if the loop was started from a timer callback - #15425 |
In WPF, the
Window.ShowDialog();
method blocks the thread and continues when the dialog is closed (the same wayMessageBox.Show();
works in WinForms). In Avalonia, the docs indicate thatWindow.ShowDialog()
executes asynchronously, meaning the thread can continue before the dialog is closed.Maybe this is a matter of me not being good at async vs sync coding but how would I produce a similar result to WPF/WinForms but using the way Avalonia has been architected? I want to call
ShowDialog
from a non-async method and have execution pause until the dialog is closed.I've tried the following:
Task.WhenAll(dialog.ShowDialog(parent));
,dialog.ShowDialog(parent).Result;
, anddialog.ShowDialog(parent).Wait();
block the dialog thread and prevents the dialog from loading.throws an exception of System.InvalidOperationException: 'Call from invalid thread'
What is the proper way to do this?
The text was updated successfully, but these errors were encountered: