UI Elements to Intercept Exceptions?

245
1
04-08-2024 02:59 PM
DanNarsavage_IDWR
New Contributor III

I'm working on a Module for Pro v3.3 that I would like to catch and handle (as gracefully as possible) all exceptions that are thrown from within the module.  A primary goal is to ensure that exceptions that result from my mistakes get logged to my logging system not Esri's (my users never see the "Send Esri an Error Report" dialog).  To that end, I've made an abstract class that inherits from `Button`, and from which all button classes in our module should inherit (pertinent excerpt below)...

 

/// <summary>
/// Base from which all buttons should inherit
/// </summary>
/// <remarks>
/// The primary reason for the existence of this class and <see cref="Tools.WrEditTool"/> is to ensure no exceptions thrown inside
/// WREdit bubble up to ArcMap because that's when people are prompted to send exception information to Esri.All buttons should
/// inherit from this class, which executes the "ClickAction" in its OnClick event and wraps it in a try/catch that catches & logs
/// everything that it cannot handle more gracefully.
/// </remarks>
public abstract class WrEditButton : Button
{
    /// <summary>
    /// Message to be shown to user in the event of an unhandled exception
    /// </summary>
    protected virtual string UnhandledExceptionUserMessage => "Unhandled exception encountered while using this command";


    /// <summary>
    /// Action to be taken when the button is clicked.
    /// </summary>
    /// <remarks>This will be wrapped in a try/catch statement and possibly other stuff too</remarks>
    protected abstract void ClickAction();

    /// <inheritdoc cref="Button.OnClick"/>
    protected sealed override void OnClick()
    {
        Cursor preExistingCursor = Mouse.OverrideCursor; // This is supposed to get the current mouse cursor. Not sure if it does that.
        FrameworkApplication.SetCursor(Cursors.Wait); // This is supposed to set the application's mouse cursor to "wait", but it doesn't work.
        try
        {
            ClickAction();
        }
        catch (Exception exception)
        {
            WrEditModule.HandleException(exception, UnhandledExceptionUserMessage);
        }
        finally
        {
            FrameworkApplication.SetCursor(preExistingCursor); // This is supposed to set the application's mouse cursor back to previous, but it doesn't work.
        }
    }
}

 

... and ...

internal class WrEditModule : Module
{
    /// <summary>
    /// This is the general exception handler for WREdit's UI.  If there is a reaction to a particular exception
    /// that should be globally applied, this is the place to apply it.
    /// </summary>
    /// <param name="exception">Exception to be handled</param>
    /// <param name="userMessage">Message to be shown to the user in the case of an unexpected exception</param>
    internal static void HandleException(   // TODO: Should this be placed somewhere else?
        Exception exception,
        string userMessage)
    {
        try
        {
            var activeMapView = MapView.Active;
            if (activeMapView != null 
                && activeMapView.Map != null 
                && activeMapView.Map.ContainsDuplicateLayerNames(out string[] names))
            {
                var commaSeparatedLayerNames = string.Join(", ", names);
                userMessage =
                    $"{userMessage.Trim()}\n\nSay, we noticed that there are multiple layers in the " +
                    $"map with the following name(s): [{commaSeparatedLayerNames}]. " +
                    "Can you please try renaming those layers to have unique names?";
            }

            var logLevel = LogEventLevel.Error;
            string logMessage;
            try
            {
                var userProcessManager = GetDependency<IUserProcessManager>();
                logMessage = userProcessManager.DatabaseState.ToString();
            }
            catch
            {
                logMessage = "Unable to retrieve user's WrEditInterface record.";
            }
            switch (exception)
            {
                case UserActionException userActionException:
                    logMessage = $"{logMessage}\n\n{userActionException.LogMessage}";
                    userMessage = userActionException.UserActionMessage;
                    logLevel = LogEventLevel.Warning;
                    break;
                case InvalidGeometryException invalidGeometryException:
                    logMessage = $"{logMessage}\n\n{exception.Message}:  \n\n{invalidGeometryException.WellKnownText}\n\n";
                    break;
            }

            var innerException = exception;
            while (innerException is not null)
            {
                if (//innerException is System.Data.SqlClient.SqlException &&  TODO: Figure out what exception type this is through EF Core
                    innerException.Message.Contains("Timeout") &&
                    innerException.Message.Contains("Expired"))
                {
                    userMessage = $"{userMessage.Trim()}\n\nWREdit experienced a 'network hiccup' that prevented it from writing to the database. Please try again later.";
                    break;
                }
                innerException = innerException.InnerException;
            }

            Log.Logger.Write(logLevel, exception, logMessage);
            MessageBox.Show(userMessage);
            //MessageBox.Show(      TODO: Figure out how to specify buttons & icons (hafta add a bunch more parameter values)
            //    caption:userMessage,
            //    button: MessageBoxButton.OK,
            //    icon:MessageBoxImage.Exclamation);
        }
        catch (Exception ex)
        {
            Log.Logger.Error(ex, "Fatal error handling an exception");
        }
    }
}

Has anyone else embarked upon anything like this? I'm new to MVVM & WPF (this is a port from ArcMap), so I'm especially keen to know if anyone's tried anything similar with an abstract ViewModel class or extending RelayCommand or . . . anything. 

1 Reply
CharlesMacleod
Esri Regular Contributor

i think you are trying to do something like this: https://stackoverflow.com/questions/1472498/wpf-global-exception-handler

I, myself, have never tried it.

0 Kudos