WPF hosting a WinForm, Tab Navigation problems
Asked Answered
D

4

14

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.

Dominican answered 2/3, 2011 at 10:56 Comment(1)
In the real project which led me to this minimized problem the situation is even a bit different. There the tab-key switches between all WPF controls (including WindowsFormsHost). But there a tab-press in the WindowsFormsHost does not go to one of the other WinForms controls within WindowsFormsHost, it simply leaves WindowsFormsHost continueing with the next WPF control.Dominican
G
8

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.

Grenada answered 13/5, 2011 at 23:29 Comment(5)
Might there be a way to teach the first and last control in the WinForms part to behave differently? Like "release focus to higher control"?Dominican
Your appendum sounds interesting. In short: It works as it should if you embedded a UserControl instead of a form?Dominican
Correct. At least for my little example.Grenada
You are right, I can reproduce this. I will no check if I can change to UserControl or try to manipulate the form behaviour as statet by Henk van Dijken.Dominican
Accepted as this is the best way to handle changing form content.Dominican
V
4

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.

Vacuva answered 14/5, 2011 at 12:2 Comment(1)
I will give it a try on Monday.Dominican
O
4

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>
Osgood answered 24/11, 2013 at 16:48 Comment(1)
This looks interesting, I will have to give it a try!Dominican
T
1

Just Add that in App.xaml.cs :

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

Reference : WindowsFormsIntegration.dll

Transudate answered 12/9, 2018 at 8:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.