首页 文章

WPF托管WinForm,Tab导航问题

提问于
浏览
12

WindowsFormsHost 和选项卡导航中托管WinForms表单时遇到了问题 . 要解决我已经做了这个简单的例子:

  • 创建了WPF Window (应用的起点)

  • 创建了WinForms Form ,上面有两个 TextBox

  • WPF窗口:已添加 WindowsFormsHost

  • WPF窗口:添加了 OnLoaded 处理程序

  • WPF窗口:已添加 Textbox 位于 WindowsFormsHost

OnLoaded 处理程序中,我得到了:

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

当我现在运行应用程序时:

  • 什么都没有集中注意力(确定)

  • 我点击 WindowsFormsHost 中的第一个 TextBox ,它获得焦点(确定)

  • 我按Tab键,焦点转到 WindowsFormsHost 中的第 TextBox (ok)

  • 我再次按Tab键,焦点返回到 WindowsFormsHost 中的第 TextBoxnot ok;应该离开 WindowsFormsHost 并将焦点放在WPF窗口底部的文本框中)

  • 我点击wpf中的文本框(放在 WindowsFormsHost 之后和之下),它获得焦点(确定)

  • 我按Tab键,焦点转到 WindowsFormsHost 中的第一个文本框 - 因为它应该在结束后开始 . 所以这也没关系

  • 我再次单击wpf文本框并按shift键,焦点转到 WindowsFormsHost 中的第二个文本框(ok)

  • 我按Tab键,焦点转到 WindowsFormsHost 中的第一个文本框(以WFH开头)( not ok)

如果我只有一种类型的控件,我如何使焦点行为?在这种情况下,意味着WFH-1st-Textbox,WFH-2nd-Textbox,WPF-Textbox的Tab键顺序 .

4 回答

  • 4

    根据我发现的文章,这似乎无法实现 . 根据MSDN Blog Entry(部分Hwnds),Windows窗体控件始终位于层次结构中的WPF控件之上 . MSDN article(从WPF消息循环获取消息部分)指出,在WPF甚至意识到它们之前,将处理WindowsFormsHost元素中发生的事件 .

    所以我假设通过按TAB键触发的事件由WindowsFormsHost元素处理(导致其他文本框的焦点) . 在封闭的WPF窗口中,永远不会遇到该事件,因为“它已被处理” . 另一方面,当您按下WPF文本框中的TAB键时,WPF正在处理事件本身并正常处理控制链 . 有了这个,焦点将转到WindowsFormsHost元素中的文本框,从那里你不能使用键盘离开它 .

    我知道这不会对你当前的问题有所帮助,但我希望它能解释一些问题 .


    ADDENDUM 如果您不依赖于使用表单控件,则可以将其更改为WinForms用户控件,其中包含相同的控件元素 . 之后,您可以通过以下方式更改WindowsFormsHost元素的初始化:

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

    WinFormUC类是我的WinForms用户控件,包含上面提到的文本框 . 在我的测试中,无论是Winforms还是WPF文本框,按下TAB键都会一个接一个地聚焦文本框 .

  • 4

    你可以用一个小技巧来做到这一点 . 假设您的主机wpf表单如下所示:

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

    在第一个文本框的LostFocus事件中,您将焦点设置为winform上的第一个按钮 . 通过这种方式,您可以确保焦点始终从第一个按钮开始 .

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

    在winform中,您必须按如下方式编写EnableTabStops代码:

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

    接下来,您可以通过winform的按钮进行选项卡 . 在输入winform上的最后一个按钮后,您禁用/删除所有tabstops,以便下一个选项卡只能跳转到其父wpf表单,如下所示:

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

    这应该做的工作 .

  • 7

    这是我实现这个的方式:

    我创建了一个继承自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));              
            }                 
        } 
    }
    

    对我来说很完美

    使用相同的方法,您还可以使您的Windows控件具有所需的wpf数据绑定:

    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));
    

    这是XAML:

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

    只需在App.xaml.cs中添加:

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

    参考:WindowsFormsIntegration.dll

相关问题