File: winforms\Managed\System\WinForms\ThreadExceptionDialog.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="ThreadExceptionDialog.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
/*
 */
namespace System.Windows.Forms {
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System;
    using System.Text;
    using System.ComponentModel;
    using System.Drawing;
    using System.Reflection;
    using System.Security;
    using System.Security.Permissions;
    using System.Runtime.InteropServices;
    using System.Globalization;
 
    /// <include file='doc\ThreadExceptionDialog.uex' path='docs/doc[@for="ThreadExceptionDialog"]/*' />
    /// <internalonly/>
    /// <devdoc>
    ///    <para>
    ///       Implements a dialog box that is displayed when an unhandled exception occurs in
    ///       a thread.
    ///    </para>
    /// </devdoc>
    [
        ComVisible(true),
        ClassInterface(ClassInterfaceType.AutoDispatch),
        SecurityPermission(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode),
        SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode),
        UIPermission(SecurityAction.Assert, Window=UIPermissionWindow.AllWindows)]
    public class ThreadExceptionDialog : Form {
 
        private const string DownBitmapName = "down.bmp";
        private const string UpBitmapName = "up.bmp";
 
        private const int MAXWIDTH = 440;
        private const int MAXHEIGHT = 325;
        private const int PADDINGWIDTH = 84;
        private const int PADDINGHEIGHT = 26;
        private const int MAXTEXTWIDTH = 180;
        private const int MAXTEXTHEIGHT = 40;
        private const int BUTTONTOPPADDING = 31;
        private const int BUTTONDETAILS_LEFTPADDING = 8;
        private const int MESSAGE_TOPPADDING = 8;
        private const int HEIGHTPADDING = 8;
        private const int BUTTONWIDTH = 100;
        private const int BUTTONHEIGHT = 23;
        private const int BUTTONALIGNMENTWIDTH = 105;
        private const int BUTTONALIGNMENTPADDING = 5;
        private const int DETAILSWIDTHPADDING = 16;
        private const int DETAILSHEIGHT = 154;
        private const int PICTUREWIDTH = 64;
        private const int PICTUREHEIGHT = 64;
        private const int EXCEPTIONMESSAGEVERTICALPADDING = 4;
 
        private int scaledMaxWidth = MAXWIDTH;
        private int scaledMaxHeight = MAXHEIGHT;
        private int scaledPaddingWidth = PADDINGWIDTH;
        private int scaledPaddingHeight = PADDINGHEIGHT;
        private int scaledMaxTextWidth = MAXTEXTWIDTH;
        private int scaledMaxTextHeight = MAXTEXTHEIGHT;
        private int scaledButtonTopPadding = BUTTONTOPPADDING;
        private int scaledButtonDetailsLeftPadding = BUTTONDETAILS_LEFTPADDING;
        private int scaledMessageTopPadding = MESSAGE_TOPPADDING;
        private int scaledHeightPadding = HEIGHTPADDING;
        private int scaledButtonWidth = BUTTONWIDTH;
        private int scaledButtonHeight = BUTTONHEIGHT;
        private int scaledButtonAlignmentWidth = BUTTONALIGNMENTWIDTH;
        private int scaledButtonAlignmentPadding = BUTTONALIGNMENTPADDING;
        private int scaledDetailsWidthPadding = DETAILSWIDTHPADDING;
        private int scaledDetailsHeight = DETAILSHEIGHT;
        private int scaledPictureWidth = PICTUREWIDTH;
        private int scaledPictureHeight = PICTUREHEIGHT;
        private int scaledExceptionMessageVerticalPadding = EXCEPTIONMESSAGEVERTICALPADDING;
 
        private PictureBox pictureBox = new PictureBox();
        private Label message = new Label();
        private Button continueButton = new Button();
        private Button quitButton = new Button();
        private Button detailsButton = new Button();
        private Button helpButton = new Button();
        private TextBox details = new TextBox();
        private Bitmap expandImage = null;
        private Bitmap collapseImage = null;
        private bool detailsVisible = false;
 
        /// <include file='doc\ThreadExceptionDialog.uex' path='docs/doc[@for="ThreadExceptionDialog.ThreadExceptionDialog"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the <see cref='System.Windows.Forms.ThreadExceptionDialog'/> class.
        ///       
        ///    </para>
        /// </devdoc>
        public ThreadExceptionDialog(Exception t) {
 
            if (DpiHelper.EnableThreadExceptionDialogHighDpiImprovements) {
                scaledMaxWidth = LogicalToDeviceUnits(MAXWIDTH);
                scaledMaxHeight = LogicalToDeviceUnits(MAXHEIGHT);
                scaledPaddingWidth = LogicalToDeviceUnits(PADDINGWIDTH);
                scaledPaddingHeight = LogicalToDeviceUnits(PADDINGHEIGHT);
                scaledMaxTextWidth = LogicalToDeviceUnits(MAXTEXTWIDTH);
                scaledMaxTextHeight = LogicalToDeviceUnits(MAXTEXTHEIGHT);
                scaledButtonTopPadding = LogicalToDeviceUnits(BUTTONTOPPADDING);
                scaledButtonDetailsLeftPadding = LogicalToDeviceUnits(BUTTONDETAILS_LEFTPADDING);
                scaledMessageTopPadding = LogicalToDeviceUnits(MESSAGE_TOPPADDING);
                scaledHeightPadding = LogicalToDeviceUnits(HEIGHTPADDING);
                scaledButtonWidth = LogicalToDeviceUnits(BUTTONWIDTH);
                scaledButtonHeight = LogicalToDeviceUnits(BUTTONHEIGHT);
                scaledButtonAlignmentWidth = LogicalToDeviceUnits(BUTTONALIGNMENTWIDTH);
                scaledButtonAlignmentPadding = LogicalToDeviceUnits(BUTTONALIGNMENTPADDING);
                scaledDetailsWidthPadding = LogicalToDeviceUnits(DETAILSWIDTHPADDING);
                scaledDetailsHeight = LogicalToDeviceUnits(DETAILSHEIGHT);
                scaledPictureWidth = LogicalToDeviceUnits(PICTUREWIDTH);
                scaledPictureHeight = LogicalToDeviceUnits(PICTUREHEIGHT);
                scaledExceptionMessageVerticalPadding = LogicalToDeviceUnits(EXCEPTIONMESSAGEVERTICALPADDING);
            }
 
            string messageRes;
            string messageText;
            Button[] buttons;
            bool detailAnchor = false;
 
            WarningException w = t as WarningException;
            if (w != null) {
                messageRes = SR.ExDlgWarningText;
                messageText = w.Message;
                if (w.HelpUrl == null) {
                    buttons = new Button[] {continueButton};
                }
                else {
                    buttons = new Button[] {continueButton, helpButton};
                }
            }
            else {
                messageText = t.Message;
 
                detailAnchor = true;
                
                if (Application.AllowQuit) {
                    if (t is SecurityException) {
                        messageRes = "ExDlgSecurityErrorText";
                    }
                    else {
                        messageRes = "ExDlgErrorText";
                    }
                    buttons = new Button[] {detailsButton, continueButton, quitButton};
                }
                else {
                    if (t is SecurityException) {
                        messageRes = "ExDlgSecurityContinueErrorText";
                    }
                    else {
                        messageRes = "ExDlgContinueErrorText";
                    }
                    buttons = new Button[] {detailsButton, continueButton};
                }
            }
 
            if (messageText.Length == 0) {
                messageText = t.GetType().Name;
            }
            if (t is SecurityException) {
                messageText = SR.GetString(messageRes, t.GetType().Name, Trim(messageText));
            }
            else {
                messageText = SR.GetString(messageRes, Trim(messageText));
            }
 
            StringBuilder detailsTextBuilder = new StringBuilder();
            string newline = "\r\n";
            string separator = SR.GetString(SR.ExDlgMsgSeperator);
            string sectionseparator = SR.GetString(SR.ExDlgMsgSectionSeperator);
            if (Application.CustomThreadExceptionHandlerAttached) {
                detailsTextBuilder.Append(SR.GetString(SR.ExDlgMsgHeaderNonSwitchable));
            }
            else {
                detailsTextBuilder.Append(SR.GetString(SR.ExDlgMsgHeaderSwitchable));
            }
            detailsTextBuilder.Append(string.Format(CultureInfo.CurrentCulture, sectionseparator, SR.GetString(SR.ExDlgMsgExceptionSection)));
            detailsTextBuilder.Append(t.ToString());
            detailsTextBuilder.Append(newline);
            detailsTextBuilder.Append(newline);
            detailsTextBuilder.Append(string.Format(CultureInfo.CurrentCulture, sectionseparator, SR.GetString(SR.ExDlgMsgLoadedAssembliesSection)));
            new FileIOPermission(PermissionState.Unrestricted).Assert();
            try {
                foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) {
                    AssemblyName name = asm.GetName();
                    string fileVer = SR.GetString(SR.NotAvailable);
 
                    try {
                        
                        // bug 113573 -- if there's a path with an escaped value in it 
                        // like c:\temp\foo%2fbar, the AssemblyName call will unescape it to
                        // c:\temp\foo\bar, which is wrong, and this will fail.   It doesn't look like the 
                        // assembly name class handles this properly -- even the "CodeBase" property is un-escaped
                        // so we can't circumvent this.
                        //
                        if (name.EscapedCodeBase != null && name.EscapedCodeBase.Length > 0) {
                            Uri codeBase = new Uri(name.EscapedCodeBase);
                            if (codeBase.Scheme == "file") {
                                fileVer = FileVersionInfo.GetVersionInfo(NativeMethods.GetLocalPath(name.EscapedCodeBase)).FileVersion;
                            }
                        }
                    }
                    catch(System.IO.FileNotFoundException){
                    }
                    detailsTextBuilder.Append(SR.GetString(SR.ExDlgMsgLoadedAssembliesEntry, name.Name, name.Version, fileVer, name.EscapedCodeBase));
                    detailsTextBuilder.Append(separator);
                }
            }
            finally {
                CodeAccessPermission.RevertAssert();
            }
            
            detailsTextBuilder.Append(string.Format(CultureInfo.CurrentCulture, sectionseparator, SR.GetString(SR.ExDlgMsgJITDebuggingSection)));
            if (Application.CustomThreadExceptionHandlerAttached) {
                detailsTextBuilder.Append(SR.GetString(SR.ExDlgMsgFooterNonSwitchable));
            }
            else {
                detailsTextBuilder.Append(SR.GetString(SR.ExDlgMsgFooterSwitchable));
            }
 
            detailsTextBuilder.Append(newline);
            detailsTextBuilder.Append(newline);
 
            string detailsText = detailsTextBuilder.ToString();
 
            Graphics g = message.CreateGraphicsInternal();
 
            Size textSize = new Size(scaledMaxWidth - scaledPaddingWidth, int.MaxValue);
 
            if (DpiHelper.EnableThreadExceptionDialogHighDpiImprovements && (Label.UseCompatibleTextRenderingDefault == false)) {
                // we need to measure string using API that matches the rendering engine - TextRenderer.MeasureText for GDI
                textSize = Size.Ceiling(TextRenderer.MeasureText(messageText, Font, textSize, TextFormatFlags.WordBreak));
            }
            else {
                // if HighDpi improvements are not enabled, or rendering mode is GDI+, use Graphics.MeasureString
                textSize = Size.Ceiling(g.MeasureString(messageText, Font, textSize.Width));
            }
 
            textSize.Height += scaledExceptionMessageVerticalPadding;
            g.Dispose();
 
            if (textSize.Width < scaledMaxTextWidth) textSize.Width = scaledMaxTextWidth;
            if (textSize.Height > scaledMaxHeight) textSize.Height = scaledMaxHeight;
 
            int width = textSize.Width + scaledPaddingWidth;
            int buttonTop = Math.Max(textSize.Height, scaledMaxTextHeight) + scaledPaddingHeight;
 
            // SECREVIEW : We must get a hold of the parent to get at it's text
            //           : to make this dialog look like the parent.
            //
            IntSecurity.GetParent.Assert();
            try {
                Form activeForm = Form.ActiveForm;
                if (activeForm == null || activeForm.Text.Length == 0) {
                    Text = SR.GetString(SR.ExDlgCaption);
                }
                else {
                    Text = SR.GetString(SR.ExDlgCaption2, activeForm.Text);
                }
            }
            finally {
                CodeAccessPermission.RevertAssert();
            }
            AcceptButton = continueButton;
            CancelButton = continueButton;
            FormBorderStyle = FormBorderStyle.FixedDialog;
            MaximizeBox = false;
            MinimizeBox = false;
            StartPosition = FormStartPosition.CenterScreen;
            Icon = null;
            ClientSize = new Size(width, buttonTop + scaledButtonTopPadding);
            TopMost = true;
 
            pictureBox.Location = new Point(scaledPictureWidth/8, scaledPictureHeight/8);
            pictureBox.Size = new Size(scaledPictureWidth*3/4, scaledPictureHeight*3/4);
            pictureBox.SizeMode = PictureBoxSizeMode.StretchImage;
            if (t is SecurityException) {
                pictureBox.Image = SystemIcons.Information.ToBitmap();
            }
            else {
                pictureBox.Image = SystemIcons.Error.ToBitmap();
            }
            Controls.Add(pictureBox);
            message.SetBounds(scaledPictureWidth,
                              scaledMessageTopPadding + (scaledMaxTextHeight - Math.Min(textSize.Height, scaledMaxTextHeight)) / 2,
                              textSize.Width, textSize.Height);
            message.Text = messageText;
            Controls.Add(message);
 
            continueButton.Text = SR.GetString(SR.ExDlgContinue);
            continueButton.FlatStyle = FlatStyle.Standard;
            continueButton.DialogResult = DialogResult.Cancel;
 
            quitButton.Text = SR.GetString(SR.ExDlgQuit);
            quitButton.FlatStyle = FlatStyle.Standard;
            quitButton.DialogResult = DialogResult.Abort;
 
            helpButton.Text = SR.GetString(SR.ExDlgHelp);
            helpButton.FlatStyle = FlatStyle.Standard;
            helpButton.DialogResult = DialogResult.Yes;
 
            detailsButton.Text = SR.GetString(SR.ExDlgShowDetails);
            detailsButton.FlatStyle = FlatStyle.Standard;
            detailsButton.Click += new EventHandler(DetailsClick);
 
            Button b = null;
            int startIndex = 0;
            
            if (detailAnchor) {
                b = detailsButton;
 
                expandImage = new Bitmap(this.GetType(), DownBitmapName);
                expandImage.MakeTransparent();
                collapseImage = new Bitmap(this.GetType(), UpBitmapName);
                collapseImage.MakeTransparent();
 
                if (DpiHelper.EnableThreadExceptionDialogHighDpiImprovements) {
                    ScaleBitmapLogicalToDevice(ref expandImage);
                    ScaleBitmapLogicalToDevice(ref collapseImage);
                }
 
                b.SetBounds(scaledButtonDetailsLeftPadding, buttonTop, scaledButtonWidth, scaledButtonHeight);
                b.Image = expandImage;
                b.ImageAlign = ContentAlignment.MiddleLeft;
                Controls.Add(b);
                startIndex = 1;
            }
            
            int buttonLeft = (width - scaledButtonDetailsLeftPadding - ((buttons.Length - startIndex) * scaledButtonAlignmentWidth - scaledButtonAlignmentPadding));
            
            for (int i = startIndex; i < buttons.Length; i++) {
                b = buttons[i];
                b.SetBounds(buttonLeft, buttonTop, scaledButtonWidth, scaledButtonHeight);
                Controls.Add(b);
                buttonLeft += scaledButtonAlignmentWidth;
            }
 
            details.Text = detailsText;
            details.ScrollBars = ScrollBars.Both;
            details.Multiline = true;
            details.ReadOnly = true;
            details.WordWrap = false;
            details.TabStop = false;
            details.AcceptsReturn = false;
            
            details.SetBounds(scaledButtonDetailsLeftPadding, buttonTop + scaledButtonTopPadding, width - scaledDetailsWidthPadding, scaledDetailsHeight);
            details.Visible = detailsVisible;
            Controls.Add(details);
            if (DpiHelper.EnableThreadExceptionDialogHighDpiImprovements) {
                DpiChanged += ThreadExceptionDialog_DpiChanged;
            }
        }
 
        private void ThreadExceptionDialog_DpiChanged(object sender, DpiChangedEventArgs e) {
            if (expandImage != null) {
                expandImage.Dispose();
            }
            expandImage = new Bitmap(this.GetType(), DownBitmapName);
            expandImage.MakeTransparent();
 
            if (collapseImage != null) {
                collapseImage.Dispose();
            }
            collapseImage = new Bitmap(this.GetType(), UpBitmapName);
            collapseImage.MakeTransparent();
 
            ScaleBitmapLogicalToDevice(ref expandImage);
            ScaleBitmapLogicalToDevice(ref collapseImage);
 
            detailsButton.Image = detailsVisible ? collapseImage : expandImage;
        }
 
        /// <include file='doc\ThreadExceptionDialog.uex' path='docs/doc[@for="ThreadExceptionDialog.AutoSize"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Hide the property
        ///    </para>
        /// </devdoc>
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override bool AutoSize
        {
            [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
            get
            {
                return base.AutoSize;
            }
            [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
            set
            {
                base.AutoSize = value;
            }
        }
 
        /// <include file='doc\ThreadExceptionDialog.uex' path='docs/doc[@for="ThreadExceptionDialog.AutoSizeChanged"]/*' />
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        new public event EventHandler AutoSizeChanged
        {
            add
            {
                base.AutoSizeChanged += value;
            }
            remove
            {
                base.AutoSizeChanged -= value;
            }
        }                
 
        /// <include file='doc\ThreadExceptionDialog.uex' path='docs/doc[@for="ThreadExceptionDialog.DetailsClick"]/*' />
        /// <devdoc>
        ///     Called when the details button is clicked.
        /// </devdoc>
        private void DetailsClick(object sender, EventArgs eventargs) {
            int delta = details.Height + scaledHeightPadding;
            if (detailsVisible) delta = -delta;
            Height = Height + delta;
            detailsVisible = !detailsVisible;
            details.Visible = detailsVisible;
            detailsButton.Image = detailsVisible ? collapseImage : expandImage;
        }
 
        private static string Trim(string s) {
            if (s == null) return s;
            int i = s.Length;
            while (i > 0 && s[i - 1] == '.') i--;
            return s.Substring(0, i);
        }
    }
}