在 WPF 中将焦点设置在父 TabItem 上

问题描述

有没有办法自动切换到承载我想要关注的控件的 TabItem?

我的示例窗口来举例说明问题。

<Window x:Class="Sample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Sample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>
        <Style targettype="Button">
            <Setter Property="Width" Value="80"/>
            <Setter Property="Height" Value="24"/>
        </Style>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDeFinitions>
            <RowDeFinition Height="*"/>
            <RowDeFinition Height="auto"/>
        </Grid.RowDeFinitions>

        <TabControl Grid.Row="0" Margin="10" Background="White">
            <TabItem Header="TabItem1">
                <Button x:Name="SomeButton" Content="Set Focus on TextBox" Width="120" Click="Button_Click"/>
            </TabItem>
            <TabItem Header="TabItem2">
                <TextBox x:Name="SomeTextBox" Margin="10" Text="Sample text"/>
            </TabItem>
        </TabControl>

        <StackPanel Grid.Row="1" Margin="10">
            <Button Content="Cancel" HorizontalAlignment="Right" Click="Button_Click_1"/>
        </StackPanel>
    </Grid>
</Window>

以及背后的代码

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender,RoutedEventArgs e)
    {
        SomeTextBox.Focus();
    }

    private void Button_Click_1(object sender,RoutedEventArgs e)
    {
        Close();
    }
}

因此,当单击 SomeButton 时,我想将焦点移至 SomeTextBox,但要使其起作用,我必须先选择 TabItem2。在提供的示例中,这显然很容易,但我正在寻找一种自动解决方案,它“知道”SomeTextBox 属于 TabItem2 并切换到它,然后将焦点设置在所需的控件上。不用说,在实际情况下,SomeTextBox 可能位于其他类型窗格中 VisualTree 的其他级别。可能吗?

顺便说一句,如果有帮助,这种功能的应用程序会将焦点设置在错误验证突出显示的控件上和/或切换到包含它的窗格。

解决方法

您可以使用 VisualTreeHelper 类查找特定类型的最近父级。

这是来自 This answer 的一个实现示例。

public static T FindParentOfType<T>(this DependencyObject child) where T : DependencyObject
{
    DependencyObject parentDepObj = child;
    do
    {
        parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
        T parent = parentDepObj as T;
        if (parent != null) return parent;
    }
    while (parentDepObj != null);
    return null;
}

获得对 TabItem 的引用后,您可以设置其 IsSelected 属性以在 TabControl 中选择它。

private void Button_Click(object sender,RoutedEventArgs e)
{
    var tabItem = FindParentOfType<TabItem>(SomeTextBox);
    if (tabItem is null) return;
    (tabItem as TabItem).IsSelected = true;
    SomeTextBox.Focus();
}

编辑

很好地抓住了 TextBox 不在可视化树中而其父 TabItem 未被选中的情况。这是获取不依赖于可视化树的 TextBox 的另一种方法。

private void Button_Click(object sender,RoutedEventArgs e)
{
    FrameworkElement currentItem = SomeTextBox;
    do
    {
        currentItem = (FrameworkElement)currentItem.Parent;
    }
    while (!(currentItem is TabItem)
           && (currentItem as TabItem).Parent != MyTabControl // Optional handling in case you have nested TabControls
           && !(currentItem.Parent is null));

    (currentItem as TabItem).IsSelected = true;
    currentItem.UpdateLayout();
        
    SomeTextBox.Focus();
}