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.
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.
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.
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>
Just Add that in App.xaml.cs :
System.Windows.Forms.Integration.WindowsFormsHost.EnableWindowsFormsInterop();
Reference : WindowsFormsIntegration.dll