Replacing the Default Error Dialog in the Isolated Shell

Background:

Recently one of our VSIP partners asked if it was possible to customize the default error dialog that the Isolated Shell displays when encountering an error. Let's suppose you have a package command handler that might throw an error with code that looks like the following:

private void OnTestErrorDialog(object sender, EventArgs e)

{

// throw an exception which will invoke the shells error dialog

ErrorHandler.ThrowOnFailure(DoSomeWork());

}

If 'DoSomeWork()' happens to return an error code like E_ACCESSDENIED for example, the shell will catch the thrown exception, and display a default error dialog that looks like the following:

Solution:

As it turns out, there is support for doing just this in the isolated shell. The problem is that we never documented or published the interface. This blog entry is an 'interim' solution, until we can get the documentation updated, and perhaps the interface and service definitions into an interop assembly.

In the Isolated Shell, if you have a package that proffers an IVsMessageBoxService, you can supply your own dialog to display error messages. The IVsMessageBoxService is defined as follows

[
   uuid(1DD71F22-C880-46be-A462-A0A5542BC939),
   version(1.0),
   pointer_default(unique)
]
interface IVsMessageBoxService: IUnknown
{
   //contains information used to display the message box.
   // (same as Win32API MessageBoxIndirect)
   HRESULT ShowMessageBox( [in] HWND hwndOwner,
      [in] HINSTANCE hInstance,
      [in] LPCOLESTR lpszText,
      [in] LPCOLESTR lpszCaption,
      [in] DWORD dwStyle,
      [in] LPCOLESTR lpszIcon,
      [in] DWORD_PTR dwContextHelpId,
      [in] DWORD_PTR lpfnMsgBoxCallback,
      [in] DWORD dwLanguageId,
      [out, retval] int* pidButton // id of the button return value
);
}

cpp_quote("#define SID_SVsMessageBoxService IID_IVsMessageBoxService")

Because the above interface is currently not defined in any of our interop assemblies, you will need to define it yourself. For example:

 [PreserveSig]
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[GuidAttribute("1DD71F22-C880-46be-A462-A0A5542BC939")]
public interface IVsMessageBoxService
{
    int ShowMessageBox(int hWndOwner,
    IntPtr hInstance,
    [MarshalAs(UnmanagedType.LPWStr)] string lpszText,
    [MarshalAs(UnmanagedType.LPWStr)] string lpszCaption,
    uint dwStyle,
    IntPtr lpszIcon,
    IntPtr dwContextHelpId,
    IntPtr pfnMessageBoxCallback,
    uint dwLangID,
    out int pidButton);
}

The next step is to implement the interface. For example:

    class CustomMessageBoxService : IVsMessageBoxService

    {

        internal CustomMessageBoxService() {}

        public int ShowMessageBox(int hWndOwner, IntPtr hInstance, string lpszText,

           string lpszCaption, uint dwStyle, string szIcon, IntPtr dwContextHelpId,

           IntPtr pfnMessageBoxCallback, uint dwLangID, out int pidButton)

        {

            IVsUIShell vsShell = (IVsUIShell)Package.GetGlobalService(typeof(SVsUIShell));

     vsShell.EnableModeless(0);

            // TODO: Display a customized dialog

            System.Windows.Forms.MessageBox.Show(lpszText, "Oops");

            vsShell.EnableModeless(-1);

            // NOTE: Returning a 0 or -1 value for pidButton will result in the shell

            // displaying its default error dialog

            pidButton = 1;

            return VSConstants.S_OK;

        }

    }

 

And finally, you need to proffer the service from a package running in your isolated shell. First, apply the following attribute to your Package object to ensure the service is registered with the shell.

    [ProvideService(typeof(IVsMessageBoxService))]

 

Then add the following code to your Package.Initialize implementation (after calling base.Initialize()):

Caveat:

   // Proffer a custom IVsMessageBoxService service to replace the shells default error dialog.

   IServiceContainer svcContainer = (IServiceContainer)GetService(typeof(IServiceContainer));

   svcContainer.AddService(typeof(IVsMessageBoxService), new CustomMessageBoxService(), true);

 

 

Note this feature is only available with the Isolated Shell. The Integrated Shell does not look for, or attempt to utilize the IVsMessageBoxService.