Search code examples
c#wpfsetfocus

Set Focus on parent TabItem in WPF


Is there a way to automatically switch to a TabItem that hosts a control I want to focus?

My sample window to exemplify the issue.

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

And the code behind:

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

So, when clicking SomeButton, I want to move the focus to SomeTextBox but, for that to work, I have to select TabItem2 first. That's obviously easy in the provided example, but I'm looking for an automated solution that 'knows' that SomeTextBox belongs to TabItem2 and switches to it and then sets the focus on the desired control. Needless to say that in the real case scenario SomeTextBox might be in some other level in the VisualTree in some other kind of pane. Is it possible?

BTW, if it helps, the application of such feature would be setting the focus on a control highlighted by error validation and/or switching to the pane that holds it.


Solution

  • You can use the VisualTreeHelper class to find the nearest parent of a specific type.

    Here's an implementation example from 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;
    }
    

    Once you have a reference to your TabItem, you can set its IsSelected property to select it in the 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();
    }
    

    Edit

    Good catch on the TextBox not being in the visual tree while its parent TabItem is not selected. Here's another way to get at the TextBox that isn't dependent on the visual tree.

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