none
WinForms control hosted in WPF Window loses focus when menu is accessed by Alt-F

    Question

  • Hello everybody!

    I have a problem with focus management in WPF/WinForms hybrid application. Here it is:
    - WPF window hosts a WinForms UserControl using WindowsFormsHost class;
    - WinForms UserControl is a composite control that also has some very trivial stuff like EditBox, ComboBox, ListBox etc;
    - WPF window has some other WPF content alongside with the WinForms content;
    - WPF window has a main menu with some usual stuff File, Edit etc;

    Now I do the following:
    - Tab to focus the Edit Box inside the WinForms User Control;
    - Press Alt-F so that File popup menu is open in the main menu;
    - Press Esc to get out of the menu and get back to editing;

    Expected:
    - Focus jumps back to the Edit Box inside of the WinForms User Control;

    Actual:
    - Focus does NOT get back to the WinForms content but instead get to the WPF content;

    I read about some limitations with focus management for hybrid applications. However that use case appears to be a major thing common to 90% of desktop applications. Let's say I have a plugin system and some plugins are WPF, some WinForms and some are ActiveX. I work in one of the plugins, then go to the menu, run something, then back.... I hope it can be solved some way or the other. Please help!

    Thanks,
    Andrey.

    Saturday, January 03, 2009 4:54 AM

Answers

  • This is a bug in WindowsFormsHost implementation, the WindowsFormsHost does include the focus management implementation which handles focus returned from Menu/MenuItem, to workaround this issue, please try the following method:

    public class MyWindowsFormsHost : WindowsFormsHost
    {
        protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
        {
            if (e.NewFocus == this && ((e.OldFocus is MenuItem) || (e.OldFocus is Menu)))
            {
                var controlAdaptor = FindWindowsFormsAdaptor(this.Child);
                if (controlAdaptor != null)
                {
                    var containerControl = controlAdaptor as System.Windows.Forms.ContainerControl;
                    if (containerControl != null)
                    {
                        if (containerControl.ActiveControl != null)
                        {
                            FocusActiveControl(containerControl.ActiveControl);
                        }
                        else
                        {
                            Focus(containerControl);
                        }
                    }
                    else
                    {
                        Focus(controlAdaptor);
                    }
                }
            }
        }

        private void Focus(System.Windows.Forms.Control control)
        {
            if (control.CanFocus && !control.ContainsFocus)
            {
                control.BeginInvoke(new Action(delegate
                {
                    control.Focus();
                }));
            }
        }

        private void FocusActiveControl(System.Windows.Forms.Control control)
        {
            var activeControl = control;
            while (activeControl != null)
            {
                var containerControl = activeControl as System.Windows.Forms.ContainerControl;
                if (containerControl != null)
                {
                    activeControl = containerControl.ActiveControl;
                }
                else
                {
                    if (activeControl.CanFocus && !activeControl.ContainsFocus)
                    {
                        Focus(activeControl);
                        return;
                    }
                }
            }

            if (activeControl != null)
            {
                activeControl.Focus();
            }
        }

        private System.Windows.Forms.Control FindWindowsFormsAdaptor(System.Windows.Forms.Control control)
        {
            var child = control;
            while (child != null)
            {
                var parent = child.Parent;
                if (parent == null)
                {
                    return child;
                }

                child = parent;
            }

            return null;
        }
    }


    I would greatly appreciate it if you could file a bug on this at the connect site:
    https://connect.microsoft.com/feedback/default.aspx?SiteID=212&wa=wsignin1.0

    Thanks

    Another Paradigm Shift
    http://shevaspace.blogspot.com
    • Marked as answer by Andrey Kozyrev Thursday, January 08, 2009 6:36 PM
    • Unmarked as answer by Andrey Kozyrev Thursday, January 08, 2009 6:36 PM
    • Marked as answer by Andrey Kozyrev Thursday, January 08, 2009 8:51 PM
    • Marked as answer by Tao Liang Friday, January 09, 2009 1:50 AM
    Thursday, January 08, 2009 9:53 AM
  • I spend nearly a working day to figure out what's the best method to manipulate focus in Windows Forms, and finally got something like the following:

    using System;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Input;
    using System.Windows.Controls;
    using System.Runtime.InteropServices;
    using System.Windows.Forms.Integration;

    namespace Interop
    {
        public class WorkaroundWindowsFormsHost : WindowsFormsHost
        {
            internal System.Windows.Forms.Control windowsFormsAdapter;
            private DialogCharMessageFilter messageFilter;
            protected override HandleRef BuildWindowCore(HandleRef hwndParent)
            {
                var handleRef = base.BuildWindowCore(hwndParent);
                this.ChildChanged += delegate
                {
                    if (this.Child != null)
                    {
                        windowsFormsAdapter = FindWindowsFormsAdapter(this.Child);
                        messageFilter = new DialogCharMessageFilter(this);
                        System.Windows.Forms.Application.AddMessageFilter(messageFilter);
                    }
                };
                return handleRef;
            }

            public override bool TabInto(TraversalRequest request)
            {
                var result = base.TabInto(request);
                if (result && request.FocusNavigationDirection == FocusNavigationDirection.First)
                {
                    //This could workaroud the bug that tabbing into hosted WF controls doesn't take focus from Avalon's perspective.
                    NotifyFocusWithinHost();
                }

                return base.TabInto(request);
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    System.Windows.Forms.Application.RemoveMessageFilter(messageFilter);
                }
                base.Dispose(disposing);
            }

            protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
            {
                //This could workaround the bug that focus returned from WPF's Menu doesn't get correctly set back to
                // hosted WF control if a WF control is previsouly focused.
                // Note that you might also need to consider if the focus is returned from somewhere within a closed Popup
                // if you have Popup control in the visual tree.
                if (e.NewFocus == this && ((e.OldFocus is MenuItem) || (e.OldFocus is Menu)))
                {
                    if (windowsFormsAdapter != null)
                    {
                        var containerControl = windowsFormsAdapter as System.Windows.Forms.ContainerControl;
                        if (containerControl != null)
                        {
                            if (containerControl.ActiveControl != null)
                            {
                                FocusActiveControl(containerControl.ActiveControl);
                            }
                            else
                            {
                                FocusInternal(containerControl);
                            }
                        }
                        else
                        {
                            FocusInternal(windowsFormsAdapter);
                        }
                    }
                }
            }

            // This method is used to set the Avalon focus on Avalon element aka WFH.
            internal void NotifyFocusWithinHost()
            {
                DependencyObject focusScope = GetFocusScopeForElement(this);
                if (null != focusScope && FocusManager.GetFocusedElement(this) != this)
                {
                    System.Windows.Input.FocusManager.SetFocusedElement(focusScope, this);
                }
            }

            private static DependencyObject GetFocusScopeForElement(DependencyObject element)
            {
                while (null != element && !FocusManager.GetIsFocusScope(element))
                {
                    element = VisualTreeHelper.GetParent(element);
                }
                return element;
            }

            private void FocusInternal(System.Windows.Forms.Control control)
            {
                // Note clear why BeginInvoke is needed here but it seems that directly call Focus method
                // cannot do the trick here.
                control.BeginInvoke(new Action(delegate
                {
                    if (control.CanFocus && !control.ContainsFocus)
                    {
                        control.Focus();
                    }
                }));
            }

            private void FocusActiveControl(System.Windows.Forms.Control control)
            {
                var activeControl = control;
                while (activeControl != null)
                {
                    var containerControl = activeControl as System.Windows.Forms.ContainerControl;
                    if (containerControl != null)
                    {
                        activeControl = containerControl.ActiveControl;
                    }
                    else
                    {
                        if (activeControl.CanFocus && !activeControl.ContainsFocus)
                        {
                            FocusInternal(activeControl);
                            return;
                        }
                    }
                }

                if (activeControl != null)
                {
                    FocusInternal(activeControl);
                }
            }

            private System.Windows.Forms.Control FindWindowsFormsAdapter(System.Windows.Forms.Control control)
            {
                var child = control;
                while (child != null)
                {
                    var parent = child.Parent;
                    if (parent == null)
                    {
                        return child;
                    }

                    child = parent;
                }

                return null;
            }
        }

        // This message filter is needed to get hooked into the mnemonic key manipulatation in WF.
        public class DialogCharMessageFilter : System.Windows.Forms.IMessageFilter
        {
            const Int32 WM_SYSCHAR = 0x0106;
            private readonly IntPtr WA_ACTIVE = new IntPtr(1);
            private WeakReference hostWeakRef;
            public DialogCharMessageFilter(WorkaroundWindowsFormsHost host)
            {
                // This could avoid WFH leak when you forget to remove the message filter.
                hostWeakRef = new WeakReference(host);
            }

            public Boolean PreFilterMessage(ref System.Windows.Forms.Message m)
            {
                System.Windows.Forms.Control control = System.Windows.Forms.Control.FromHandle(m.HWnd);
                if (control == null && m.Msg == WM_SYSCHAR)
                {
                    if (hostWeakRef.IsAlive)
                    {
                        var host = hostWeakRef.Target as WorkaroundWindowsFormsHost;
                        if (host != null && host.windowsFormsAdapter != null)
                        {
                            var result = host.windowsFormsAdapter.PreProcessMessage(ref m);
                            if (result)
                            {
                                //If the WM_SYSCHAR message is preprocessed, we'd expect that the WF control 
                                //will be activated or focused, so we set the Avalon focus on the WFH.
                                //This could introduce another bug in some of other rare scenarios where preproecessing message
                                // doesn't mean taking focus or activating something.
                                host.NotifyFocusWithinHost();
                            }

                            //return true to bypass the default WF message preprocessing logic since we've already done that here.
                            return result;
                        }
                    }
                }

                return false;
            }
        }
    }

    Please pay attention to the comments because I've said the above code could break in some "rare scenarios", although I still don't have clear idea of what those "rare scenarios" are.

    Hope this helps

    Another Paradigm Shift
    http://shevaspace.blogspot.com
    • Marked as answer by Andrey Kozyrev Saturday, January 10, 2009 1:02 AM
    Friday, January 09, 2009 9:45 AM

All replies

  • Can you paste a demo source code of your project?
    Then I can debug and modify it.
    Thank you.
    Monday, January 05, 2009 2:56 AM
  • Have you tried on 3.5 sp1? It is worth trying because there were several fixes in the WinFormsHost focus area shipped in sp1.
    Tuesday, January 06, 2009 2:10 AM
  • I uploaded my project to rapidshare, please let me know if it is not appropriate and you prefer me to copy/paste my xaml here.
    http://rapidshare.com/files/180226838/Interop.zip.html

    Here is the use case:
    1. Start Application
    2. Press TAB to go over all the controls
    3. Press Alt-C to focus the "WinForms CheckBox"
    4. Press Alt-F to expand File menu
    5. Press Enter to as if you want to trigger "New..." command

    Expected:
    - focus gets back to the "WinForms CheckBox"

    Actual:
    - focus jumps to the WPF section.

    It looks like the focus always returns to the WPF "logically" focused item, i.e. the item that was focused before the step 3. Is it possible to tweak WindowsFormsHost to simulate the logical focus thing and participate in the WPF focus management?

    Tuesday, January 06, 2009 2:23 AM
  • Yes, I'm on 3.5 SP1 sorry for not mentioning it.

    Thanks,
    Andrey.
    Tuesday, January 06, 2009 2:26 AM
  • I can not download your file.

    http://rs386gc2.rapidshare.com/files/180226838/3513358/Interop.zip

    ///
    You want to download the following file:

    The download session has expired. Please click here to start the download again.


    If it is just 49KB, you can send it to my email box:
    liangtom@gmail.com
    Tuesday, January 06, 2009 7:23 AM
  • I have sent zip with sources to your address.

    Thanks,
    Andrey.

    Tuesday, January 06, 2009 6:03 PM
  • I received it and will look it.
    Thank you.
    Wednesday, January 07, 2009 1:46 AM
  • Hi, I noticed that all the tab indexes are same,so can you set the tab index in the order what you want

    right now it is 2147483647 for all controls. when the project is opend XAML Form in Visual Studio.

    WinFormHost accepts the tab index set it to 0 and continue 1,2 for other wpf tab control.

    inside UserControl Checkbox Tab index is 2, and textbox index is 1
    when you get WinFormHost focus, if you want tab index by defult to be on checkbox in the control. then set it to 0.




    Prasad - www.beautifulmind.blog.co.in
    • Proposed as answer by prasad22 Wednesday, January 07, 2009 4:49 AM
    • Unproposed as answer by Andrey Kozyrev Wednesday, January 07, 2009 6:19 PM
    Wednesday, January 07, 2009 4:39 AM
  • You can use IKeyboardInputSink interface to manage focus in the interoperability of  Winform and WPF.
    I build a demo project (may be not the best solution).
    In this project the Winform Usercontrol is always focused.
    You can refer to:
    Windows Forms and WPF Interoperability Input Architecture
    XAML:
    <Window x:Class="Interop.Window1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
            xmlns:local="clr-namespace:Interop" 
        Title="Window1" Height="500" Width="500"
        <Grid> 
            <Grid.ColumnDefinitions> 
                <ColumnDefinition Width="*"/> 
                <ColumnDefinition Width="*"/> 
            </Grid.ColumnDefinitions> 
            <Grid.RowDefinitions> 
                <RowDefinition Height="Auto"/> 
                <RowDefinition Height="Auto"/> 
                <RowDefinition Height="*"/> 
            </Grid.RowDefinitions> 
            <Menu Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
                <MenuItem  Header="_File"
                    <MenuItem Click="MenuItem_Click" Header="_New..."/> 
                    <MenuItem Click="MenuItem_Click" Header="_Open..."/> 
                    <MenuItem Click="MenuItem_Click" Header="_Save"/> 
                </MenuItem> 
            </Menu> 
            <TextBlock FontWeight="Bold" Text="WinForms" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center"></TextBlock> 
            <TextBlock FontWeight="Bold" Text="WPF" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center"></TextBlock> 
            <WindowsFormsHost   x:Name="_host" Grid.Row="2" Grid.Column="0" Margin="10" TabIndex="0" > 
                <local:UserControl1></local:UserControl1> 
            </WindowsFormsHost> 
            <DockPanel Grid.Row="2" Grid.Column="1" Margin="10"
                <Label Content="WPF Text Box" DockPanel.Dock="Top"/> 
                <TextBox DockPanel.Dock="Top" MaxLines="1" /> 
                <CheckBox Content="WPF C_heckBox" DockPanel.Dock="Top" TabIndex="2" /> 
                <Button Content="WPF B_utton" DockPanel.Dock="Top" TabIndex="3" /> 
                <ListBox TabIndex="4"
                    <ListBoxItem>one</ListBoxItem> 
                    <ListBoxItem>two</ListBoxItem> 
                    <ListBoxItem>three</ListBoxItem> 
                    <ListBoxItem>four</ListBoxItem> 
                    <ListBoxItem>five</ListBoxItem> 
                </ListBox> 
            </DockPanel> 
        </Grid> 
    </Window> 
     

    C#:

    using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Text; 
    using System.Windows; 
    using System.Windows.Controls; 
    using System.Windows.Data; 
    using System.Windows.Documents; 
    using System.Windows.Input; 
    using System.Windows.Media; 
    using System.Windows.Media.Imaging; 
    using System.Windows.Navigation; 
    using System.Windows.Shapes; 
    using System.Windows.Interop; 
     
    namespace Interop 
        /// <summary> 
        /// Interaction logic for Window1.xaml 
        /// </summary> 
        public partial class Window1 : Window 
        { 
            public Window1() 
            { 
                InitializeComponent();  
            } 
      
              
            private void MenuItem_Click(object sender, RoutedEventArgs e) 
            { 
               ((IKeyboardInputSink)_host).TabInto(new TraversalRequest(FocusNavigationDirection.Right)); 
            } 
        } 
     


    • Proposed as answer by Tao Liang Wednesday, January 07, 2009 7:21 AM
    • Marked as answer by Tao Liang Wednesday, January 07, 2009 7:26 AM
    • Unmarked as answer by Andrey Kozyrev Wednesday, January 07, 2009 8:11 PM
    • Unproposed as answer by Andrey Kozyrev Wednesday, January 07, 2009 8:11 PM
    Wednesday, January 07, 2009 7:21 AM
  • namespace Interop 
        partial class UserControl1 
        { 
            /// <summary>  
            /// Required designer variable. 
            /// </summary> 
            private System.ComponentModel.IContainer components = null
     
            /// <summary>  
            /// Clean up any resources being used. 
            /// </summary> 
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> 
            protected override void Dispose(bool disposing) 
            { 
                if (disposing && (components != null)) 
                { 
                    components.Dispose(); 
                } 
                base.Dispose(disposing); 
            } 
     
            #region Component Designer generated code 
     
            /// <summary>  
            /// Required method for Designer support - do not modify  
            /// the contents of this method with the code editor. 
            /// </summary> 
            private void InitializeComponent() 
            { 
                this.label1 = new System.Windows.Forms.Label(); 
                this.textBox1 = new System.Windows.Forms.TextBox(); 
                this.checkBox1 = new System.Windows.Forms.CheckBox(); 
                this.button1 = new System.Windows.Forms.Button(); 
                this.listBox1 = new System.Windows.Forms.ListBox(); 
                this.SuspendLayout(); 
                //  
                // label1 
                //  
                this.label1.AutoSize = true
                this.label1.Dock = System.Windows.Forms.DockStyle.Top; 
                this.label1.Location = new System.Drawing.Point(0, 0); 
                this.label1.Name = "label1"
                this.label1.Size = new System.Drawing.Size(99, 13); 
                this.label1.TabIndex = 0
                this.label1.Text = "WinForms Text Box"
                //  
                // textBox1 
                //  
                this.textBox1.Dock = System.Windows.Forms.DockStyle.Top; 
                this.textBox1.Location = new System.Drawing.Point(0, 13); 
                this.textBox1.Margin = new System.Windows.Forms.Padding(10); 
                this.textBox1.Name = "textBox1"
                this.textBox1.Size = new System.Drawing.Size(228, 20); 
                this.textBox1.TabIndex = 1; 
                //  
                // checkBox1 
                //  
                this.checkBox1.AutoSize = true
                this.checkBox1.Dock = System.Windows.Forms.DockStyle.Top; 
                this.checkBox1.Location = new System.Drawing.Point(0, 33); 
                this.checkBox1.Name = "checkBox1"
                this.checkBox1.Size = new System.Drawing.Size(228, 17); 
                this.checkBox1.TabIndex = 2
                this.checkBox1.Text = "WinForms &CheckBox"
                this.checkBox1.UseVisualStyleBackColor = true
                //  
                // button1 
                //  
                this.button1.Dock = System.Windows.Forms.DockStyle.Top; 
                this.button1.Location = new System.Drawing.Point(0, 50); 
                this.button1.Name = "button1"
                this.button1.Size = new System.Drawing.Size(228, 23); 
                this.button1.TabIndex = 3
                this.button1.Text = "WinForms Button"
                this.button1.UseVisualStyleBackColor = true
                //  
                // listBox1 
                //  
                this.listBox1.Dock = System.Windows.Forms.DockStyle.Fill; 
                this.listBox1.FormattingEnabled = true
                this.listBox1.Items.AddRange(new object[] { 
                "one ", 
                "two", 
                "three", 
                "four ", 
                "five"}); 
                this.listBox1.Location = new System.Drawing.Point(0, 73); 
                this.listBox1.Name = "listBox1"
                this.listBox1.Size = new System.Drawing.Size(228, 173); 
                this.listBox1.TabIndex = 4
                //  
                // UserControl1 
                //  
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 
                this.Controls.Add(this.listBox1); 
                this.Controls.Add(this.button1); 
                this.Controls.Add(this.checkBox1); 
                this.Controls.Add(this.textBox1); 
                this.Controls.Add(this.label1); 
                this.Name = "UserControl1"
                this.Size = new System.Drawing.Size(228, 248); 
                this.ResumeLayout(false); 
                this.PerformLayout(); 
     
            } 
     
            #endregion 
     
            private System.Windows.Forms.Label label1; 
            private System.Windows.Forms.TextBox textBox1; 
            private System.Windows.Forms.CheckBox checkBox1; 
            private System.Windows.Forms.Button button1; 
            private System.Windows.Forms.ListBox listBox1; 
        } 
     

    Wednesday, January 07, 2009 7:21 AM
  • Thanks for your answers! I will try to follow your recommendations and will let you know the results later today!

    Andrey.
    Wednesday, January 07, 2009 6:11 PM
  • Prasad, 

    I'm not sure I understood you suggestion. 

    - TabIndex property of the controls within a winforms UserControl is set correctly: 0, 1, 2 etc. 
    - I do not want focus to be on checkbox or anything else by default, I want focus to return to the same winforms control after triggering wpf menu command. For example winforms checkbox is focues, Alt-F, New -> focus gets back to checkbox. The same for any other control within the winforms UserControl.

    Please let me know if I misuderstood you.

    Thanks,
    Andrey.

    Wednesday, January 07, 2009 6:23 PM

  •  

    Expected:
    - Focus jumps back to the Edit Box inside of the WinForms User Control;



    Oops, by mistake instead of edit box/textbox i mentiond checkbox,  No need to alter inside the UserControl TabIndexes are correct.

    you have to set TabIndexes only in WPF form. WinFormHost  will accept the TabIndex from WPF Form. when Tab Navigates to this WinFormHost , what is set inside the WinFormHost  from assending order it will start focusing...



    Prasad - www.beautifulmind.blog.co.in
    Wednesday, January 07, 2009 6:47 PM
  • Checkbox in my use case was just an example to provide a clear sequence of steps to reproduce the problem. And the problem is that the focus does not return to the previously focused control within winforms UserControl after triggering the action in menu with the keyboard.

    If I set the TabIndex property for all the controls in my xaml nothing changes. I believe this problem has nothing to do with the tab order but with the interoperation of two different focus management systems of WPF and Win32. 
    Wednesday, January 07, 2009 6:54 PM
  • liangtom,

    Thank you for you suggestion, it can amend for some simple case but does not solve the problem in general. Calling TabInto puts the focus to the first control within the winforms UserControl. I can substitute this call with _host.Child.Focus() that works better because UserControl seems to track the focus inside. However this trick does not scale well to the real life application. Our product is a large desktop editor similar to Visual Studio. It is going to have tens of WindowsFormsHosts with WinForms and ActiveX controls each of which can have focus inside that should be preserved after issuing command through the main menu.

    The only solution I can imagine for now is some focus tracking mechanism that would remember that last focused window handle (means non wpf window) and forcefully bring the focus back to it at some key moments like main menu acccess or main window deactivation/activation (which also wipe away winapi focus). It will either plug into several WPF points like TabInto, GotKeyboardFocus etc, or, if possible, install some hook to monitor WM_SETFOCUS messages in surrogate messgae queue.

    If you have any additional ideas or hints regarding solving this issue please let me know. For example is there an event or something to hook to in order to know when user input switches between WPF and WinForms?

    I will keep updating the thread with my progress.

    Thanks,
    Andrey.
    Wednesday, January 07, 2009 8:28 PM
  • This is a bug in WindowsFormsHost implementation, the WindowsFormsHost does include the focus management implementation which handles focus returned from Menu/MenuItem, to workaround this issue, please try the following method:

    public class MyWindowsFormsHost : WindowsFormsHost
    {
        protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
        {
            if (e.NewFocus == this && ((e.OldFocus is MenuItem) || (e.OldFocus is Menu)))
            {
                var controlAdaptor = FindWindowsFormsAdaptor(this.Child);
                if (controlAdaptor != null)
                {
                    var containerControl = controlAdaptor as System.Windows.Forms.ContainerControl;
                    if (containerControl != null)
                    {
                        if (containerControl.ActiveControl != null)
                        {
                            FocusActiveControl(containerControl.ActiveControl);
                        }
                        else
                        {
                            Focus(containerControl);
                        }
                    }
                    else
                    {
                        Focus(controlAdaptor);
                    }
                }
            }
        }

        private void Focus(System.Windows.Forms.Control control)
        {
            if (control.CanFocus && !control.ContainsFocus)
            {
                control.BeginInvoke(new Action(delegate
                {
                    control.Focus();
                }));
            }
        }

        private void FocusActiveControl(System.Windows.Forms.Control control)
        {
            var activeControl = control;
            while (activeControl != null)
            {
                var containerControl = activeControl as System.Windows.Forms.ContainerControl;
                if (containerControl != null)
                {
                    activeControl = containerControl.ActiveControl;
                }
                else
                {
                    if (activeControl.CanFocus && !activeControl.ContainsFocus)
                    {
                        Focus(activeControl);
                        return;
                    }
                }
            }

            if (activeControl != null)
            {
                activeControl.Focus();
            }
        }

        private System.Windows.Forms.Control FindWindowsFormsAdaptor(System.Windows.Forms.Control control)
        {
            var child = control;
            while (child != null)
            {
                var parent = child.Parent;
                if (parent == null)
                {
                    return child;
                }

                child = parent;
            }

            return null;
        }
    }


    I would greatly appreciate it if you could file a bug on this at the connect site:
    https://connect.microsoft.com/feedback/default.aspx?SiteID=212&wa=wsignin1.0

    Thanks

    Another Paradigm Shift
    http://shevaspace.blogspot.com
    • Marked as answer by Andrey Kozyrev Thursday, January 08, 2009 6:36 PM
    • Unmarked as answer by Andrey Kozyrev Thursday, January 08, 2009 6:36 PM
    • Marked as answer by Andrey Kozyrev Thursday, January 08, 2009 8:51 PM
    • Marked as answer by Tao Liang Friday, January 09, 2009 1:50 AM
    Thursday, January 08, 2009 9:53 AM
  • Marco,

    That looks close to the point, thank you for the help! I will try it out and provide feedback later today.

    Sure, I will log a bug as you say.

    Thanks,
    Andrey.
    Thursday, January 08, 2009 6:38 PM
  •  Marco,

    Your solution seems to solve the main problem in a proper way. Thank you! Here are some minor problems left through. If I set the focus inside a WinForms UserControl by moush click - all works great. However, if I set focus inside a WinForms UserControl by tabbing or by typing a mnemonic (say Alt-C) - it does not work. OnPreviewGotKeyboardFocus just does not get called in this case. My wild guess is that it is because WPF focus management system fails to track WindowsFormsHost as keyboard focused element or something and does not return focus to it after the menu or toolbar button click. Can you give me a hint what the best solution would be if any?

    Thanks,
    Andrey.
    Thursday, January 08, 2009 8:51 PM
  • I spend nearly a working day to figure out what's the best method to manipulate focus in Windows Forms, and finally got something like the following:

    using System;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Input;
    using System.Windows.Controls;
    using System.Runtime.InteropServices;
    using System.Windows.Forms.Integration;

    namespace Interop
    {
        public class WorkaroundWindowsFormsHost : WindowsFormsHost
        {
            internal System.Windows.Forms.Control windowsFormsAdapter;
            private DialogCharMessageFilter messageFilter;
            protected override HandleRef BuildWindowCore(HandleRef hwndParent)
            {
                var handleRef = base.BuildWindowCore(hwndParent);
                this.ChildChanged += delegate
                {
                    if (this.Child != null)
                    {
                        windowsFormsAdapter = FindWindowsFormsAdapter(this.Child);
                        messageFilter = new DialogCharMessageFilter(this);
                        System.Windows.Forms.Application.AddMessageFilter(messageFilter);
                    }
                };
                return handleRef;
            }

            public override bool TabInto(TraversalRequest request)
            {
                var result = base.TabInto(request);
                if (result && request.FocusNavigationDirection == FocusNavigationDirection.First)
                {
                    //This could workaroud the bug that tabbing into hosted WF controls doesn't take focus from Avalon's perspective.
                    NotifyFocusWithinHost();
                }

                return base.TabInto(request);
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    System.Windows.Forms.Application.RemoveMessageFilter(messageFilter);
                }
                base.Dispose(disposing);
            }

            protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
            {
                //This could workaround the bug that focus returned from WPF's Menu doesn't get correctly set back to
                // hosted WF control if a WF control is previsouly focused.
                // Note that you might also need to consider if the focus is returned from somewhere within a closed Popup
                // if you have Popup control in the visual tree.
                if (e.NewFocus == this && ((e.OldFocus is MenuItem) || (e.OldFocus is Menu)))
                {
                    if (windowsFormsAdapter != null)
                    {
                        var containerControl = windowsFormsAdapter as System.Windows.Forms.ContainerControl;
                        if (containerControl != null)
                        {
                            if (containerControl.ActiveControl != null)
                            {
                                FocusActiveControl(containerControl.ActiveControl);
                            }
                            else
                            {
                                FocusInternal(containerControl);
                            }
                        }
                        else
                        {
                            FocusInternal(windowsFormsAdapter);
                        }
                    }
                }
            }

            // This method is used to set the Avalon focus on Avalon element aka WFH.
            internal void NotifyFocusWithinHost()
            {
                DependencyObject focusScope = GetFocusScopeForElement(this);
                if (null != focusScope && FocusManager.GetFocusedElement(this) != this)
                {
                    System.Windows.Input.FocusManager.SetFocusedElement(focusScope, this);
                }
            }

            private static DependencyObject GetFocusScopeForElement(DependencyObject element)
            {
                while (null != element && !FocusManager.GetIsFocusScope(element))
                {
                    element = VisualTreeHelper.GetParent(element);
                }
                return element;
            }

            private void FocusInternal(System.Windows.Forms.Control control)
            {
                // Note clear why BeginInvoke is needed here but it seems that directly call Focus method
                // cannot do the trick here.
                control.BeginInvoke(new Action(delegate
                {
                    if (control.CanFocus && !control.ContainsFocus)
                    {
                        control.Focus();
                    }
                }));
            }

            private void FocusActiveControl(System.Windows.Forms.Control control)
            {
                var activeControl = control;
                while (activeControl != null)
                {
                    var containerControl = activeControl as System.Windows.Forms.ContainerControl;
                    if (containerControl != null)
                    {
                        activeControl = containerControl.ActiveControl;
                    }
                    else
                    {
                        if (activeControl.CanFocus && !activeControl.ContainsFocus)
                        {
                            FocusInternal(activeControl);
                            return;
                        }
                    }
                }

                if (activeControl != null)
                {
                    FocusInternal(activeControl);
                }
            }

            private System.Windows.Forms.Control FindWindowsFormsAdapter(System.Windows.Forms.Control control)
            {
                var child = control;
                while (child != null)
                {
                    var parent = child.Parent;
                    if (parent == null)
                    {
                        return child;
                    }

                    child = parent;
                }

                return null;
            }
        }

        // This message filter is needed to get hooked into the mnemonic key manipulatation in WF.
        public class DialogCharMessageFilter : System.Windows.Forms.IMessageFilter
        {
            const Int32 WM_SYSCHAR = 0x0106;
            private readonly IntPtr WA_ACTIVE = new IntPtr(1);
            private WeakReference hostWeakRef;
            public DialogCharMessageFilter(WorkaroundWindowsFormsHost host)
            {
                // This could avoid WFH leak when you forget to remove the message filter.
                hostWeakRef = new WeakReference(host);
            }

            public Boolean PreFilterMessage(ref System.Windows.Forms.Message m)
            {
                System.Windows.Forms.Control control = System.Windows.Forms.Control.FromHandle(m.HWnd);
                if (control == null && m.Msg == WM_SYSCHAR)
                {
                    if (hostWeakRef.IsAlive)
                    {
                        var host = hostWeakRef.Target as WorkaroundWindowsFormsHost;
                        if (host != null && host.windowsFormsAdapter != null)
                        {
                            var result = host.windowsFormsAdapter.PreProcessMessage(ref m);
                            if (result)
                            {
                                //If the WM_SYSCHAR message is preprocessed, we'd expect that the WF control 
                                //will be activated or focused, so we set the Avalon focus on the WFH.
                                //This could introduce another bug in some of other rare scenarios where preproecessing message
                                // doesn't mean taking focus or activating something.
                                host.NotifyFocusWithinHost();
                            }

                            //return true to bypass the default WF message preprocessing logic since we've already done that here.
                            return result;
                        }
                    }
                }

                return false;
            }
        }
    }

    Please pay attention to the comments because I've said the above code could break in some "rare scenarios", although I still don't have clear idea of what those "rare scenarios" are.

    Hope this helps

    Another Paradigm Shift
    http://shevaspace.blogspot.com
    • Marked as answer by Andrey Kozyrev Saturday, January 10, 2009 1:02 AM
    Friday, January 09, 2009 9:45 AM
  • Marco, 

    Thank you very much for your effort and time! That is a working solution and good technological survey for me. Another possible trick I was considering is to install a CBT_HOOK / HBCT_SETFOCUS hook and then set logical focus on the corresponding WPF host. Hook solution does not have an uncertaintly of assumption that each processed mnemonic results in focusing, but is much more crude in terms of hacking. Anyway I got it moving. Thank you!

    Andrey.
    Saturday, January 10, 2009 1:29 AM
  • Andrey, would you please email me your repro project? I'd like to take a closer look at this issue too. Thanks.

    Thursday, February 19, 2009 10:07 PM
  • I've written a working example based on the CBT_HOOK idea Andrey mentioned above, and it works pretty well, here is the link:

    http://oxju0g.blu.livefilestore.com/y1pG5yqtARpL1N_A_1yViMLfuegOvlU_w6K9oBmR5LyN-SPBk3p7-BY-xpVYXOc4rF8KRca5LKfEoy5dNTP19iesA/FocusInterop.zip?download

    Thanks

    Another Paradigm Shift
    http://shevaspace.blogspot.com
    • Edited by Marco Zhou Thursday, April 16, 2009 3:53 AM fix link
    Monday, March 23, 2009 5:33 AM