diff --git a/RolandK.AvaloniaExtensions.sln b/RolandK.AvaloniaExtensions.sln
index 8c46065..a7c630c 100644
--- a/RolandK.AvaloniaExtensions.sln
+++ b/RolandK.AvaloniaExtensions.sln
@@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RolandK.AvaloniaExtensions.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RolandK.AvaloniaExtensions.DependencyInjection.Tests", "src\RolandK.AvaloniaExtensions.DependencyInjection.Tests\RolandK.AvaloniaExtensions.DependencyInjection.Tests.csproj", "{03D69FBA-0153-4CF3-AC81-07E8CC0BB526}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RolandK.AvaloniaExtensions.ExceptionHandling", "src\RolandK.AvaloniaExtensions.ExceptionHandling\RolandK.AvaloniaExtensions.ExceptionHandling.csproj", "{FC6CF99C-571A-454F-B385-52F0DEC21BFA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -39,6 +41,10 @@ Global
{03D69FBA-0153-4CF3-AC81-07E8CC0BB526}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03D69FBA-0153-4CF3-AC81-07E8CC0BB526}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03D69FBA-0153-4CF3-AC81-07E8CC0BB526}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FC6CF99C-571A-454F-B385-52F0DEC21BFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FC6CF99C-571A-454F-B385-52F0DEC21BFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FC6CF99C-571A-454F-B385-52F0DEC21BFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FC6CF99C-571A-454F-B385-52F0DEC21BFA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index b12ba65..90ff9fd 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -2,7 +2,7 @@
- 2.0.4
+ 2.1.0
diff --git a/src/RolandK.AvaloniaExtensions.DependencyInjection.Tests/RolandK.AvaloniaExtensions.DependencyInjection.Tests.csproj b/src/RolandK.AvaloniaExtensions.DependencyInjection.Tests/RolandK.AvaloniaExtensions.DependencyInjection.Tests.csproj
index 0752a5f..43ab347 100644
--- a/src/RolandK.AvaloniaExtensions.DependencyInjection.Tests/RolandK.AvaloniaExtensions.DependencyInjection.Tests.csproj
+++ b/src/RolandK.AvaloniaExtensions.DependencyInjection.Tests/RolandK.AvaloniaExtensions.DependencyInjection.Tests.csproj
@@ -19,7 +19,7 @@
all
-
+
diff --git a/src/RolandK.AvaloniaExtensions.DependencyInjection/RolandK.AvaloniaExtensions.DependencyInjection.csproj b/src/RolandK.AvaloniaExtensions.DependencyInjection/RolandK.AvaloniaExtensions.DependencyInjection.csproj
index dcfd085..8dc8aa0 100644
--- a/src/RolandK.AvaloniaExtensions.DependencyInjection/RolandK.AvaloniaExtensions.DependencyInjection.csproj
+++ b/src/RolandK.AvaloniaExtensions.DependencyInjection/RolandK.AvaloniaExtensions.DependencyInjection.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/AggregateExceptionAnalyzer.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/AggregateExceptionAnalyzer.cs
new file mode 100644
index 0000000..e949f31
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/AggregateExceptionAnalyzer.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data.Analyzers;
+
+public class AggregateExceptionAnalyzer : IExceptionAnalyzer
+{
+ ///
+ public IEnumerable? ReadExceptionInfo(Exception ex)
+ {
+ return null;
+ }
+
+ ///
+ public IEnumerable? GetInnerExceptions(Exception ex)
+ {
+ if (ex is AggregateException aggEx)
+ {
+ foreach (var actInnerException in aggEx.InnerExceptions)
+ {
+ yield return actInnerException;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/ArgumentExceptionAnalyzer.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/ArgumentExceptionAnalyzer.cs
new file mode 100644
index 0000000..5e9e7e4
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/ArgumentExceptionAnalyzer.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data.Analyzers;
+
+public class ArgumentExceptionAnalyzer : IExceptionAnalyzer
+{
+ ///
+ public IEnumerable? ReadExceptionInfo(Exception ex)
+ {
+ if (ex is not ArgumentException argumentException) { yield break; }
+
+ yield return new ExceptionProperty("ParamName", argumentException.ParamName ?? string.Empty);
+ }
+
+ ///
+ public IEnumerable? GetInnerExceptions(Exception ex)
+ {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/DefaultExceptionAnalyzer.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/DefaultExceptionAnalyzer.cs
new file mode 100644
index 0000000..4953d6f
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/DefaultExceptionAnalyzer.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data.Analyzers;
+
+public class DefaultExceptionAnalyzer : IExceptionAnalyzer
+{
+ ///
+ public IEnumerable? ReadExceptionInfo(Exception ex)
+ {
+ yield return new ExceptionProperty("Type", ex.GetType().FullName ?? string.Empty);
+ yield return new ExceptionProperty("Message", ex.Message);
+ yield return new ExceptionProperty("HResult", ex.HResult.ToString());
+ yield return new ExceptionProperty("HelpLink", ex.HelpLink ?? string.Empty);
+ yield return new ExceptionProperty("Source", ex.Source ?? string.Empty);
+
+ if (ex.TargetSite != null)
+ {
+ var sourceMethod = ex.TargetSite;
+ yield return new ExceptionProperty("SourceMethod.Name", sourceMethod.Name);
+ yield return new ExceptionProperty("SourceMethod.IsStatic", sourceMethod.IsStatic.ToString());
+ yield return new ExceptionProperty(
+ "SourceMethod.Type",
+ sourceMethod.DeclaringType?.FullName ?? string.Empty);
+ }
+
+ yield return new ExceptionProperty("StackTrace", ex.StackTrace ?? string.Empty);
+ }
+
+ ///
+ public IEnumerable? GetInnerExceptions(Exception ex)
+ {
+ if(ex.InnerException != null)
+ {
+ yield return ex.InnerException;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/SystemIOExceptionAnalyzer.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/SystemIOExceptionAnalyzer.cs
new file mode 100644
index 0000000..5a99928
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/Analyzers/SystemIOExceptionAnalyzer.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data.Analyzers;
+
+public class SystemIOExceptionAnalyzer : IExceptionAnalyzer
+{
+ ///
+ public IEnumerable? ReadExceptionInfo(Exception ex)
+ {
+ switch (ex)
+ {
+ case FileLoadException fileLoadEx:
+ yield return new ExceptionProperty("FileName", fileLoadEx.FileName ?? string.Empty);
+ yield return new ExceptionProperty("FusionLog", fileLoadEx.FusionLog ?? string.Empty);
+ break;
+
+ case FileNotFoundException fileNotFoundEx:
+ yield return new ExceptionProperty("FileName", fileNotFoundEx.FileName ?? string.Empty);
+ yield return new ExceptionProperty("FusionLog", fileNotFoundEx.FusionLog ?? string.Empty);
+ break;
+
+ case DirectoryNotFoundException:
+ break;
+ }
+ }
+
+ ///
+ public IEnumerable? GetInnerExceptions(Exception ex)
+ {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/ExceptionInfo.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/ExceptionInfo.cs
new file mode 100644
index 0000000..e79ff0a
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/ExceptionInfo.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using RolandK.AvaloniaExtensions.ExceptionHandling.Data.Analyzers;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data;
+
+public class ExceptionInfo
+{
+ ///
+ /// Gets a collection containing all child nodes.
+ ///
+ public List ChildNodes { get; set; } = new();
+
+ ///
+ /// Gets or sets the main message.
+ ///
+ public string MainMessage
+ {
+ get;
+ set;
+ } = string.Empty;
+
+ public string Description
+ {
+ get;
+ set;
+ } = string.Empty;
+
+ public ExceptionInfo()
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ExceptionInfo(Exception ex, IEnumerable? exceptionAnalyzers = null)
+ {
+ exceptionAnalyzers ??= CreateDefaultAnalyzers();
+
+ this.MainMessage = "Unexpected Error";
+ this.Description = ex.Message;
+
+ // Analyze the given exception
+ ExceptionInfoNode newNode = new(ex);
+ this.ChildNodes.Add(newNode);
+
+ AnalyzeException(ex, newNode, exceptionAnalyzers);
+ }
+
+ public static IEnumerable CreateDefaultAnalyzers()
+ {
+ yield return new DefaultExceptionAnalyzer();
+ yield return new SystemIOExceptionAnalyzer();
+ yield return new AggregateExceptionAnalyzer();
+ yield return new ArgumentExceptionAnalyzer();
+ }
+
+ ///
+ /// Analyzes the given exception.
+ ///
+ /// The exception to be analyzed.
+ /// The target node where to put all data to.
+ /// All loaded analyzer objects.
+ private static void AnalyzeException(Exception ex, ExceptionInfoNode targetNode, IEnumerable exceptionAnalyzers)
+ {
+ // Query over all exception data
+ var analyzedInnerExceptions = new HashSet(2);
+ foreach(IExceptionAnalyzer actAnalyzer in exceptionAnalyzers)
+ {
+ // Read all properties of the current exception
+ var exceptionInfos = actAnalyzer.ReadExceptionInfo(ex);
+ if (exceptionInfos != null)
+ {
+ foreach (ExceptionProperty actProperty in exceptionInfos)
+ {
+ if (string.IsNullOrEmpty(actProperty.Name)) { continue; }
+
+ ExceptionInfoNode propertyNode = new(actProperty);
+
+ targetNode.ChildNodes ??= new List();
+ targetNode.ChildNodes.Add(propertyNode);
+ }
+ }
+
+ // Read all inner exception information
+ var innerExceptions = actAnalyzer.GetInnerExceptions(ex);
+ if (innerExceptions == null) { continue; }
+
+ foreach (Exception actInnerException in innerExceptions)
+ {
+ if(analyzedInnerExceptions.Contains(actInnerException)){ continue; }
+ analyzedInnerExceptions.Add(actInnerException);
+
+ ExceptionInfoNode actInfoNode = new(actInnerException);
+ AnalyzeException(actInnerException, actInfoNode, exceptionAnalyzers);
+
+ targetNode.ChildNodes ??= new List();
+ targetNode.ChildNodes.Add(actInfoNode);
+ }
+ }
+
+ // Sort all generated nodes
+ if (targetNode.ChildNodes?.Count > 0)
+ {
+ targetNode.ChildNodes.Sort();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/ExceptionInfoNode.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/ExceptionInfoNode.cs
new file mode 100644
index 0000000..d225255
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/ExceptionInfoNode.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Reflection;
+using System.Collections.Generic;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data;
+
+public class ExceptionInfoNode : IComparable
+{
+ ///
+ /// Gets a collection containing all child nodes.
+ ///
+ public List? ChildNodes { get; set; }
+
+ public bool IsExceptionNode { get; set; }
+
+ public string PropertyName { get; set; } = string.Empty;
+
+ public string PropertyValue { get; set; } = string.Empty;
+
+ public ExceptionInfoNode()
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ExceptionInfoNode(Exception ex)
+ {
+ this.IsExceptionNode = true;
+ this.PropertyName = ex.GetType().GetTypeInfo().Name;
+ this.PropertyValue = ex.Message;
+ }
+
+ public ExceptionInfoNode(ExceptionProperty property)
+ {
+ this.PropertyName = property.Name;
+ this.PropertyValue = property.Value;
+ }
+
+ public int CompareTo(ExceptionInfoNode? other)
+ {
+ if (other == null) { return -1; }
+ if(this.IsExceptionNode != other.IsExceptionNode)
+ {
+ if (this.IsExceptionNode) { return 1; }
+ else { return -1; }
+ }
+
+ return 0;
+ }
+
+ public override string ToString()
+ {
+ return $"{this.PropertyName}: {this.PropertyValue}";
+ }
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/ExceptionProperty.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/ExceptionProperty.cs
new file mode 100644
index 0000000..fa6fb9f
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/ExceptionProperty.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data;
+
+public class ExceptionProperty(string name, string value)
+{
+ public string Name { get; } = name;
+ public string Value { get; } = value;
+
+ public override string ToString()
+ {
+ return $"{this.Name}: {this.Value}";
+ }
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/IExceptionAnalyzer.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/IExceptionAnalyzer.cs
new file mode 100644
index 0000000..ff33f87
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/IExceptionAnalyzer.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data;
+
+///
+/// This interface is used by the error-reporting framework.
+/// It queries for all information provided by an exception which will be presented to
+/// the user / developer.
+///
+public interface IExceptionAnalyzer
+{
+ ///
+ /// Reads all exception information from the given exception object.
+ ///
+ /// The exception to be analyzed.
+ IEnumerable? ReadExceptionInfo(Exception ex);
+
+ ///
+ /// Gets all inner exceptions provided by the given exception object.
+ ///
+ /// The exception to be analyzed.
+ IEnumerable? GetInnerExceptions(Exception ex);
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/ExceptionViewerApplication.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/ExceptionViewerApplication.cs
new file mode 100644
index 0000000..9e32ed6
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/ExceptionViewerApplication.cs
@@ -0,0 +1,49 @@
+using System.Text.Json;
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Threading;
+using RolandK.AvaloniaExtensions.ExceptionHandling.Data;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling;
+
+public class ExceptionViewerApplication : Application
+{
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (this.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ ExceptionInfo? exceptionInfo = null;
+ try
+ {
+ var filePath = desktop!.Args![0];
+
+ using var inStream = File.OpenRead(filePath);
+ exceptionInfo = JsonSerializer.Deserialize(inStream);
+ }
+ catch (Exception)
+ {
+ // Nothing we can do here
+ }
+
+ if (exceptionInfo == null)
+ {
+ // We need to wait some time. Otherwise, an exception is thrown after Shutdown()
+ Task.Delay(100).ContinueWith(_ =>
+ {
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ desktop.Shutdown();
+ });
+ });
+ base.OnFrameworkInitializationCompleted();
+ return;
+ }
+
+ var dialog = new UnexpectedErrorDialog();
+ dialog.DataContext = exceptionInfo;
+ desktop.MainWindow = dialog;
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/GlobalErrorReporting.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/GlobalErrorReporting.cs
new file mode 100644
index 0000000..a5e0778
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/GlobalErrorReporting.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using RolandK.AvaloniaExtensions.ExceptionHandling.Data;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling;
+
+public static class GlobalErrorReporting
+{
+ ///
+ /// Shows an error dialog in the current process.
+ ///
+ /// The exception to be shown to the user.
+ /// The parent window for the error dialog.
+ /// If null, a default collection of IExceptionAnalyzers ist used.
+ public static async Task ShowGlobalExceptionDialogAsync(
+ Exception exception,
+ Window parentWindow,
+ IEnumerable? exceptionAnalyzers = null)
+ {
+ var exceptionInfo = new ExceptionInfo(exception, exceptionAnalyzers);
+
+ var dialog = new UnexpectedErrorDialog();
+ dialog.DataContext = exceptionInfo;
+ await dialog.ShowDialog(parentWindow);
+ }
+
+ ///
+ /// Tries to show an error dialog with some exception details.
+ /// If it is not possible for any reason, this method simply does nothing.
+ ///
+ /// The exception to be shown to the user.
+ /// This should be a technical name, the method uses it to create a temporary directory in the filesystem.
+ /// The project name of the executable showing the error dialog.
+ /// If null, a default collection of IExceptionAnalyzers ist used.
+ public static void TryShowGlobalExceptionDialogInAnotherProcess(
+ Exception exception,
+ string applicationTempDirectoryName,
+ string exceptionViewerExecutableProjectName,
+ IEnumerable? exceptionAnalyzers = null)
+ {
+ try
+ {
+ // Write exception details to a temporary file
+ var errorDirectoryPath = GetErrorFileDirectoryAndEnsureCreated(applicationTempDirectoryName);
+ var errorFilePath = GenerateErrorFilePath(errorDirectoryPath);
+
+ WriteExceptionInfoToFile(exception, exceptionAnalyzers, errorFilePath);
+ try
+ {
+ if (!TryFindViewerExecutable(exceptionViewerExecutableProjectName, out var executablePath))
+ {
+ return;
+ }
+
+ ShowGlobalException(errorFilePath, executablePath);
+ }
+ finally
+ {
+ // Delete the temporary file
+ File.Delete(errorFilePath);
+ }
+ }
+ catch(Exception)
+ {
+ // Nothing to do here..
+ }
+ }
+
+ private static string GetErrorFileDirectoryAndEnsureCreated(string applicationTempDirectoryName)
+ {
+ var errorDirectoryPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+ applicationTempDirectoryName);
+ if (!Directory.Exists(errorDirectoryPath))
+ {
+ Directory.CreateDirectory(errorDirectoryPath);
+ }
+ return errorDirectoryPath;
+ }
+
+ private static string GenerateErrorFilePath(string errorDirectoryPath)
+ {
+ string errorFilePath;
+ do
+ {
+ var errorGuid = Guid.NewGuid();
+ errorFilePath = Path.Combine(
+ errorDirectoryPath,
+ $"Error-{errorGuid}.err");
+ } while (File.Exists(errorFilePath));
+
+ return errorFilePath;
+ }
+
+ private static void WriteExceptionInfoToFile(
+ Exception exception,
+ IEnumerable? exceptionAnalyzers,
+ string targetFileName)
+ {
+ using var outStream = File.Create(targetFileName);
+
+ var exceptionInfo = new ExceptionInfo(exception, exceptionAnalyzers);
+ JsonSerializer.Serialize(
+ outStream,
+ exceptionInfo,
+ new JsonSerializerOptions(JsonSerializerDefaults.General)
+ {
+ WriteIndented = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+ });
+ }
+
+ private static bool TryFindViewerExecutable(
+ string exceptionViewerExecutableProjectName,
+ out string executablePath)
+ {
+ executablePath = string.Empty;
+
+ var executingAssembly = Assembly.GetExecutingAssembly();
+ var executingAssemblyDirectory = Path.GetDirectoryName(executingAssembly.Location);
+ if (string.IsNullOrEmpty(executingAssemblyDirectory) ||
+ string.IsNullOrEmpty(exceptionViewerExecutableProjectName))
+ {
+ return false;
+ }
+
+ var executablePathCheck = Path.Combine(executingAssemblyDirectory, exceptionViewerExecutableProjectName);
+ if (!File.Exists(executablePathCheck))
+ {
+ executablePathCheck += ".exe";
+ if (!File.Exists(executablePathCheck))
+ {
+ return false;
+ }
+ }
+
+ executablePath = executablePathCheck;
+ return true;
+ }
+
+ private static void ShowGlobalException(string exceptionDetailsFilePath, string executablePath)
+ {
+ var processStartInfo = new ProcessStartInfo(
+ executablePath,
+ $"\"{exceptionDetailsFilePath}\"");
+ processStartInfo.ErrorDialog = false;
+ processStartInfo.UseShellExecute = false;
+
+ var childProcess = Process.Start(processStartInfo);
+ if (childProcess != null)
+ {
+ childProcess.WaitForExit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/RolandK.AvaloniaExtensions.ExceptionHandling.csproj b/src/RolandK.AvaloniaExtensions.ExceptionHandling/RolandK.AvaloniaExtensions.ExceptionHandling.csproj
new file mode 100644
index 0000000..b5d8b57
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/RolandK.AvaloniaExtensions.ExceptionHandling.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net6.0;net7.0;net8.0
+ enable
+ enable
+ True
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/UnexpectedErrorDialog.axaml b/src/RolandK.AvaloniaExtensions.ExceptionHandling/UnexpectedErrorDialog.axaml
new file mode 100644
index 0000000..face98b
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/UnexpectedErrorDialog.axaml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/RolandK.AvaloniaExtensions.ExceptionHandling/UnexpectedErrorDialog.axaml.cs b/src/RolandK.AvaloniaExtensions.ExceptionHandling/UnexpectedErrorDialog.axaml.cs
new file mode 100644
index 0000000..1ec463c
--- /dev/null
+++ b/src/RolandK.AvaloniaExtensions.ExceptionHandling/UnexpectedErrorDialog.axaml.cs
@@ -0,0 +1,20 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace RolandK.AvaloniaExtensions.ExceptionHandling;
+
+public partial class UnexpectedErrorDialog : Window
+{
+ public UnexpectedErrorDialog()
+ {
+ this.InitializeComponent();
+
+ this.Loaded += (sender, eArgs) => this.Activate();
+ }
+
+ private void OnCmdClose_Click(object? sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+}
\ No newline at end of file
diff --git a/src/RolandK.AvaloniaExtensions.TestApp/RolandK.AvaloniaExtensions.TestApp.csproj b/src/RolandK.AvaloniaExtensions.TestApp/RolandK.AvaloniaExtensions.TestApp.csproj
index f8bbc52..2461c58 100644
--- a/src/RolandK.AvaloniaExtensions.TestApp/RolandK.AvaloniaExtensions.TestApp.csproj
+++ b/src/RolandK.AvaloniaExtensions.TestApp/RolandK.AvaloniaExtensions.TestApp.csproj
@@ -17,12 +17,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/RolandK.AvaloniaExtensions.Tests/RolandK.AvaloniaExtensions.Tests.csproj b/src/RolandK.AvaloniaExtensions.Tests/RolandK.AvaloniaExtensions.Tests.csproj
index 4c6ecd7..4e06ad7 100644
--- a/src/RolandK.AvaloniaExtensions.Tests/RolandK.AvaloniaExtensions.Tests.csproj
+++ b/src/RolandK.AvaloniaExtensions.Tests/RolandK.AvaloniaExtensions.Tests.csproj
@@ -8,7 +8,7 @@
-
+
@@ -21,7 +21,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/src/RolandK.AvaloniaExtensions/RolandK.AvaloniaExtensions.csproj b/src/RolandK.AvaloniaExtensions/RolandK.AvaloniaExtensions.csproj
index 96d67ed..82b89a0 100644
--- a/src/RolandK.AvaloniaExtensions/RolandK.AvaloniaExtensions.csproj
+++ b/src/RolandK.AvaloniaExtensions/RolandK.AvaloniaExtensions.csproj
@@ -8,7 +8,7 @@
-
+