|
4 | 4 | using Avalonia; |
5 | 5 | using Avalonia.Controls; |
6 | 6 | using Avalonia.Controls.ApplicationLifetimes; |
| 7 | +using Avalonia.Controls.Primitives; |
7 | 8 | using Avalonia.Input; |
8 | 9 | using Avalonia.Input.Platform; |
9 | 10 | using Avalonia.Layout; |
|
12 | 13 | using Avalonia.Media.Imaging; |
13 | 14 | using Avalonia.Platform; |
14 | 15 | using Avalonia.Styling; |
| 16 | +using Avalonia.Threading; |
15 | 17 | using CommunityToolkit.Mvvm.DependencyInjection; |
16 | 18 | using CommunityToolkit.Mvvm.Input; |
17 | 19 | using CommunityToolkit.Mvvm.Messaging; |
| 20 | +using FluentAvalonia.UI.Controls; |
18 | 21 | using FluentAvalonia.UI.Windowing; |
19 | 22 | using Microsoft.Extensions.DependencyInjection; |
20 | 23 | using Serilog; |
@@ -376,6 +379,56 @@ private async void HandleClipboardCopyEvent(NeedClipboardCopyEvent @event) |
376 | 379 | } |
377 | 380 | } |
378 | 381 |
|
| 382 | + private async void HandleErrorMessageEvent(ErrorMessageEvent @event) |
| 383 | + { |
| 384 | + await Dispatcher.UIThread.InvokeAsync(async () => |
| 385 | + { |
| 386 | + try |
| 387 | + { |
| 388 | + var textBlock = new SelectableTextBlock |
| 389 | + { |
| 390 | + Text = @event.FullError |
| 391 | + ? @event.Exception.ToString() |
| 392 | + : @event.Exception.Message, |
| 393 | + TextWrapping = TextWrapping.Wrap, |
| 394 | + MaxWidth = 600, |
| 395 | + }; |
| 396 | + |
| 397 | + var scrollViewer = new ScrollViewer |
| 398 | + { |
| 399 | + Content = textBlock, |
| 400 | + MaxHeight = 400, |
| 401 | + HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled, |
| 402 | + }; |
| 403 | + |
| 404 | + var dialog = new ContentDialog |
| 405 | + { |
| 406 | + Title = $"Error in {@event.Context}", |
| 407 | + Content = scrollViewer, |
| 408 | + CloseButtonText = "Close", |
| 409 | + DefaultButton = ContentDialogButton.Close, |
| 410 | + PrimaryButtonText = @event.FullError ? "Copy" : null, |
| 411 | + }; |
| 412 | + TaskHelpers.PlayNotificationSoundAsync(NotificationSound.Error); |
| 413 | + var result = await dialog.ShowAsync(); |
| 414 | + |
| 415 | + if (result == ContentDialogResult.Primary) |
| 416 | + { |
| 417 | + var topLevel = TopLevel.GetTopLevel( |
| 418 | + MyMainWindow is not null ? MyMainWindow : dialog |
| 419 | + ); |
| 420 | + await topLevel?.Clipboard?.SetTextAsync(@event.Exception.ToString()); |
| 421 | + } |
| 422 | + } |
| 423 | + catch (Exception ex) |
| 424 | + { |
| 425 | + // Fallback to console if the UI is in a state where dialogs can't open |
| 426 | + DebugHelper.Logger?.Error("Critical: Could not open FluentAvalonia ContentDialog."); |
| 427 | + DebugHelper.Logger?.Error(ex.ToString()); |
| 428 | + } |
| 429 | + }); |
| 430 | + } |
| 431 | + |
379 | 432 | private static async Task<IClipboard> GetClipboardAsync() |
380 | 433 | { |
381 | 434 | if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) |
@@ -440,6 +493,19 @@ private static async Task<IClipboard> GetOrCreateClipboardWindowAsync() |
440 | 493 |
|
441 | 494 | public override void OnFrameworkInitializationCompleted() |
442 | 495 | { |
| 496 | + // Crashes must be contained, AT ALL COSTS! |
| 497 | + Dispatcher.UIThread.UnhandledException += (s, e) => |
| 498 | + { |
| 499 | + e.Handled = true; |
| 500 | + var ex = e.Exception; |
| 501 | + |
| 502 | + ex.ShowError(true, "UI Dispatcher Critical Error"); |
| 503 | + }; |
| 504 | + TaskScheduler.UnobservedTaskException += (s, e) => |
| 505 | + { |
| 506 | + e.SetObserved(); |
| 507 | + e.Exception.ShowError(true, "Unobserved Task Exception"); |
| 508 | + }; |
443 | 509 | var locator = new ViewLocator(); |
444 | 510 | DataTemplates.Add(locator); |
445 | 511 | var services = new ServiceCollection(); |
|
0 commit comments