Set focus to content of ContentPresenter

2.2k views Asked by At

I need to set focus to the content of a ContentPresenter. I can assume the ContentTemplate contains an IInputElement but not anything else about it.

Here is a much simplified example that illustrates the problem:

Main window:

<Window x:Class="FiedControlTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:Esatto.Wpf.CustomControls;assembly=Esatto.Wpf.CustomControls"
    xmlns:local="clr-namespace:FiedControlTest">
    <Window.Resources>
        <Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
            <Setter Property="Margin" Value="5"/>
            <Style.Triggers>
                <Trigger Property="IsFocused" Value="True">
                    <Setter Property="Background" Value="LightBlue"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <StackPanel Orientation="Horizontal">
            <ComboBox ItemsSource="{Binding Path=Options}" Name="cbOptions" DisplayMemberPath="Description"/>
            <Button Content="Set focus" Click="SetFocus"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <Label Content="TextBox:"/>
            <TextBox Name="tbText" Text="A bare text box."/>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <Label Content="ContentPresenter:"/>
            <ContentPresenter Content="TextBox in a ContentPresenter" Name="cpText">
                <ContentPresenter.ContentTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Mode=OneWay}" IsReadOnly="True"/>
                    </DataTemplate>
                </ContentPresenter.ContentTemplate>
            </ContentPresenter>
        </StackPanel>
    </StackPanel>
</Window>

Codebehind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        this.DataContext = this;
        Options = new ObservableCollection<Option>(new[]{
            new Option(){TargetType=typeof(TextBox), Description="Bare Text Box"},
            new Option(){TargetType=typeof(ContentPresenter), Description="Content Presenter"}
        });
        InitializeComponent();
        cbOptions.SelectedIndex = 0;
    }

    private void SetFocus(object sender, RoutedEventArgs e)
    {
        var opt = cbOptions.SelectedItem as Option;
        if (opt.TargetType == typeof(TextBox))
            tbText.Focus();
        if (opt.TargetType == typeof(ContentPresenter))
            cpText.Focus();
    }

    public ObservableCollection<Option> Options { get; set; }

    public class Option
    {
        public Type TargetType { get; set; }
        public string Description { get; set; }
    }
}

There's not much there. The bare TextBox takes focus as expected; the TextBox presented by the ContentPresenter does not.

I have tried adding Focusable="True" to the ContentPresenter but it doesn't have any visible effect. I've tried doing using Keyboard.SetFocus instead of UIElement.Focus but the behavior doesn't change.

How is this done?

1

There are 1 answers

2
King King On BEST ANSWER

In fact what you set focus is the ContentPresenter, not the inner TextBox. So you can use VisualTreeHelper to find the child visual element (the TextBox in this case) and set focus for it. However with IsReadOnly being true, you won't see any caret blinking (which may also be what you want). To show it in readonly mode, we can just set IsReadOnlyCaretVisible to true:

private void SetFocus(object sender, RoutedEventArgs e)
{
    var opt = cbOptions.SelectedItem as Option;
    if (opt.TargetType == typeof(TextBox))
        tbText.Focus();
    if (opt.TargetType == typeof(ContentPresenter)) {
       var child = VisualTreeHelper.GetChild(cpText, 0) as TextBox;
       if(child != null) child.Focus();
    }            
}

Here the edited XAML code with IsReadOnlyCaretVisible added:

<TextBox Text="{Binding Mode=OneWay}" IsReadOnly="True" 
         IsReadOnlyCaretVisible="True"/>

Note that the above code can only be applied in your specific case where you use a TextBox as the root visual of ContentTemplate of a ContentPresenter. In general case, you will need some recursive method to find the child visual (based on VisualTreeHelper), you can search more for this, I don't want to include it here because it's a very well-known problem/code in WPF to find visual child in WPF. (the italic phrase can be used as keywords to search for more).