WPF hosting a WinForm, Tab Navigation problems

2019-06-15 06:48发布

问题:

I have run into issues when hosting a WinForms form within a WindowsFormsHost and the tab navigation. To solve I have made this simple example:

  • Created WPF Window (starting point of app)
  • Created WinForms Form with two TextBox on it
  • WPF window: Added WindowsFormsHost to it
  • WPF window: Added OnLoaded handler
  • WPF window: Added Textbox positioned under the WindowsFormsHost

In the OnLoaded handler I got:

System.Windows.Forms.Form f = new WinFormsForm();
f.TopLevel = false;
f.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.windowsFormsHost1.Child = f;

When I now run the application:

  • Nothing is focussed (ok)
  • I click on the first TextBox in the WindowsFormsHost, it gets focus (ok)
  • I press tab, focus goes to 2nd TextBox in the WindowsFormsHost (ok)
  • I press tab again, focus goes back to 1st TextBox in the WindowsFormsHost (not ok; should have left WindowsFormsHost and given focus to the textbox at the bottom of the WPF window)
  • I click on the textbox in the wpf (placed after and under the WindowsFormsHost), it gets focus (ok)
  • I press tab, focus goes to 1st textbox in WindowsFormsHost - as it should go to beginning after the end. So this is ok too
  • I click the wpf textbox again and press shift+tab, focus goes to 2nd textbox in WindowsFormsHost (ok)
  • I press tab, focus goes to 1st textbox in WindowsFormsHost (goes to beginning in WFH) (not ok)

How do I make the focus behave like if I had only controls of one type? Meaning a tab order of WFH-1st-Textbox, WFH-2nd-Textbox, WPF-Textbox in this case.

回答1:

According to the articles I have found, that seems not possible to accomplish. According to a MSDN Blog Entry (section Hwnds) the Windows Forms controls always are on top of WPF controls in the hierarchy. The MSDN article (section Acquiring Messages from the WPF Message Loop) states that events occurring in a WindowsFormsHost element will be processed before WPF is even aware of them.

So I assume that the event fired by pressing the TAB key is processed by the WindowsFormsHost element (resulting in the focus of the other textbox). In the enclosing WPF window the event will never be encountered because "it has already been processed". On the other side when you press the TAB key in the WPF textbox, WPF is handling the event itself and the control chain is processed normally. With this the focus will get to a textbox in the WindowsFormsHost element and from there you can't leave it using the keyboard.

I know this will not help your current problem but I hope it explains some things.


ADDENDUM If you are not dependent on using a form control, you could change it into a WinForms user-control with the same control elements in it. After that you change the initialization of the WindowsFormsHost element in the following way:

System.Windows.Forms.UserControl control = new WinFormUC();
windowsFormsHost1.Child = control;

The class WinFormUC is my WinForms user-control containing the mentioned textboxes. In my test the pressing of the TAB key focused the textboxes one after another regardless whether its a Winforms or a WPF textbox.



回答2:

You could do this with a little trick. Suppose that your wpf form with host looks like this:

<StackPanel>
    <TextBox LostFocus="TextBox_LostFocus" />
    <wf:WindowsFormsHost Name="host" />
    <TextBox/>
</StackPanel>

In the LostFocus event of the first textbox you set the focus to the first button on the winform. In this way you assure that focus always starts at the first button.

private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
    Form1 f = (Form1)host.Child;
    f.EnableTabStops(true);
}

In the winform you have to code EnableTabStops as follows:

public void EnableTabStops(bool IsEnabled)
{
    this.button1.TabStop = IsEnabled;
    this.button2.TabStop = IsEnabled;
    if (IsEnabled) button1.Focus();
}

Next, you tab through the buttons of the winform. Upon entering the last button on the winform you disable/remove all tabstops so that the next tab only can jump to its parent wpf form, like this:

private void button2_Enter(object sender, EventArgs e)
{
    EnableTabStops(false);
}

This should do the job.



回答3:

This is the way i implemented this:

I created a control that inherits from WindowsFormsHost

public class MyWpfControl: WindowsFormsHost
{
    private MyWindowsFormsControl _winControl = new MyWindowsFormsControl ();

    public MyWpfControl()
    {
        _winControl.KeyDown += _winControl_KeyDown;
    }

    void _winControl_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Tab && e.Shift)
        {
            MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));                          
        }
        else if (e.KeyCode == Keys.Tab)
        {
            MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));              
        }                 
    } 
}

worked perfectly for me

using the same approach you can also make your windows control have the needed wpf data bindings:

public static readonly RoutedEvent SelectionChangedEvent =    EventManager.RegisterRoutedEvent("SelectionChanged", RoutingStrategy.Bubble,  typeof(RoutedEventHandler), typeof(MyWpfControl));

    public event RoutedEventHandler SelectionChanged
    {
        add { AddHandler(SelectionChangedEvent, value); }
        remove { RemoveHandler(SelectionChangedEvent, value); }
    }

    void RaiseSelectionChangedEvent()
    {
        var newEventArgs = new RoutedEventArgs(SelectionChangedEvent);
        RaiseEvent(newEventArgs);
    }

    private void InitDependencyProperties()
    {
        _winControl.EditValueChanged += (sender, e) =>
        {
            SetValue(SelectedValueProperty, _winControl.EditValue);

            if (!_disabledSelectionChangedEvent)
            {
                RaiseSelectionChangedEvent();
            }
        };

    }

public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(string), typeof(MyWpfControl),
   new PropertyMetadata("",
     (d, e) =>
     {
         var myControl = d as MyWpfControl;
         if (myControl != null && myControl._brokersCombo != null)
         {
             var val = myControl.GetValue(e.Property) ?? string.Empty; 
             myControl._winControl.EditValue = val;                              
         }
     }, null));

Here is the XAML:

<u:MyWpfControl x:Name="myWpfControl" Margin="5,0,0,0" DataSource="{Binding BindingData,     UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding SelectedPropertyNameOnViewModel, Mode=TwoWay}">
</u:MyWpfControl>


回答4:

Just Add that in App.xaml.cs :

System.Windows.Forms.Integration.WindowsFormsHost.EnableWindowsFormsInterop();

Reference : WindowsFormsIntegration.dll