System.ArgumentException Occurs With WindowsFormsHost in WPF Application


Not sure if this is the right fix? We've added this issue to our memory dump diagnostic which can confirm.

Symptoms


You have developed a Microsoft .NET 4.0 application which uses WPF and the WindowsFormsHost element in order to host Windows Forms controls. When using the application, you receive the following exception.

System.ArgumentException: Invisible or disabled control cannot be activated
   at System.Windows.Forms.ContainerControl.SetActiveControlInternal(Control value)
   at System.Windows.Forms.ContainerControl.SetActiveControl(Control ctl)
   at System.Windows.Forms.ContainerControl.set_ActiveControl(Control value)
   at System.Windows.Forms.Integration.WindowsFormsHost.RestoreFocusedChild()
   at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme)
   at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj)
   at System.Threading.ExecutionContext.runTryCode(Object userData)
   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry tme)
   at System.Windows.Forms.Control.InvokeMarshaledCallbacks()


Cause


This is due to a confirmed bug in WPF in the .NET 4.0 Framework.

Resolution


You can work around this behavior in these ways.

1. Do not programmatically remove controls from the WindowsFormsHost element. Instead, hide them by setting their Visible property.

2. Wire an eventhandler to the System.Windows.Forms.Application.ThreadException event. This will allow you to suppress the Windows Forms exception dialog. The code can check specifically to see if the exception is a System.ArgumentException from System.Windows.Forms, that contains "System.Windows.Forms.Integration.WindowsFormsHost.RestoreFocusedChild" in its call stack. If not, then display the standard Windows Forms exception dialog box.


        public MainWindow()
        {
            System.Windows.Forms.Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
        }

        void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            if (!FailureFromFocusedChild(e.Exception))
            {
                System.Windows.Forms.Application.ThreadExceptionDialog dlg;
                dlg = new System.Windows.Forms.Application.ThreadExceptionDialog(e.Exception);
                dlg.ShowDialog();
            }

        private bool FailureFromFocusedChild(Exception e)
        {
            bool result = false;
            string stackTrace = e.StackTrace;
           
            result = (e is System.ArgumentException) && (e.Source == "System.Windows.Forms")
                    && (stackTrace.IndexOf("System.Windows.Forms.Integration.WindowsFormsHost.RestoreFocusedChild")>=0);


            return result;
        }

More Information


WindowsFormsHost uses an overridden window procedure to monitor for WM_ACTIVATEAPP window messages in order to determine if the host applicaiton is being activated or deactivated.

If the host application is being deactivated, then WindowsFormsHost checks to see if the window with the input focus is a child window of the WindowsFormsHost element. If it is, then WindowsFormsHost caches a reference to that Control within an internal field. WindowsFormsHost will use this field to set as the active control once the application is activated at a later time.

If the host application is being activated, then WindowsFormsHost checks to see if that internal field is non-null. If it is non-null, it will asynchronously execute the System.Windows.Forms.Integration.WindowsFormsHost.RestoreFocusedChild method. This method will set Control.ActiveControl to the internal field cached earlier.

The problem occurs when the Control which had been cached has been removed from the WindowsFormsHost element after the application was deactivated. For example, this can occur if the application uses WPF's Application.Deactivated event in order to remove controls from the WindowsFormsHost element.