Focus on a TextBox in a DataTemplate
Asked Answered
C

4

10

I have DataTemplate containing a TextBox. I'm setting this template to a listbox item on a selection.

I'm unable to set focus to textbox in the template. I tried to call MyTemplate.FindName, but it ends up with an Invalid Operation Exception: This operation is valid only on elements that have this template applied.

How can I access it?

Counterstatement answered 30/11, 2008 at 22:59 Comment(0)
S
10

Since you know the name of the TextBox you want to focus, this becomes relatively easy. The idea is to get hold of the template as it's applied to the ListBoxItem itself.

First thing you want to do is get the selected item:

var item = listBox1.ItemContainerGenerator.ContainerFromItem(listBox1.SelectedItem) as ListBoxItem;

Then you can pass that into this little helper function which focuses a control based on its name:

public void FocusItem(ListBoxItem item, string name)
{
    if (!item.IsLoaded)
    {
        // wait for the item to load so we can find the control to focus
        RoutedEventHandler onload = null;
        onload = delegate
        {
            item.Loaded -= onload;
            FocusItem(item, name);
        };
        item.Loaded += onload;
        return;
    }

    try
    {
        var myTemplate = FindResource("MyTemplateKey") as FrameworkTemplate; // or however you get your template right now

        var ctl = myTemplate.FindName(name, item) as FrameworkElement;
        ctl.Focus();
    }
    catch
    {
        // focus something else if the template/item wasn't found?
    }
}

I guess the tricky bit is making sure you wait for the item to load. I had to add that code because I was calling this from the ItemContainerGenerator.StatusChanged event and sometimes the ListBoxItem hadn't been fully initialized by the time we entered the method.

Shana answered 30/11, 2008 at 23:10 Comment(1)
FindResource returns an object, so be sure to cast it to a FrameworkTemplate.Scolopendrid
H
13

I know this is old, but I ran across this issue today and in the end came up with this resolution:

Since the TextBox is only being loaded when an item is selected, and that is when you want focus to be set, you can simply handle the TextBox.Load event and call Focus().

There are two ways to achieve this.

1. Replace the TextBox in the DataTemplate with an AutoFocusTextBox.

public class AutoFocusTextBox : TextBox
{
    public AutoFocusTextBox()
    {
        Loaded += delegate { Focus(); }; 
    }
}

Don't forget you'll need to reference the namespace in which AutoFocusTextBox is defined in your .xaml file.

2. Add a handler in the codebehind of the file where the DataTemplate is defined.

SomeResourceDictionary.xaml

<TextBox Text="{Binding Something, Mode=TwoWay}" Style={StaticResource ...
        Loaded="FocusTextBoxOnLoad" />

SomeResourceDictionary.xaml.cs

    private void FocusTextBoxOnLoad(object sender, RoutedEventArgs e)
    {
        var textbox = sender as TextBox;
        if(textbox == null) return;
        textbox.Focus();
    }

With either option, you can always add other behaviour in the handler, such as selecting all text.

Hallway answered 25/6, 2010 at 21:43 Comment(0)
S
10

Since you know the name of the TextBox you want to focus, this becomes relatively easy. The idea is to get hold of the template as it's applied to the ListBoxItem itself.

First thing you want to do is get the selected item:

var item = listBox1.ItemContainerGenerator.ContainerFromItem(listBox1.SelectedItem) as ListBoxItem;

Then you can pass that into this little helper function which focuses a control based on its name:

public void FocusItem(ListBoxItem item, string name)
{
    if (!item.IsLoaded)
    {
        // wait for the item to load so we can find the control to focus
        RoutedEventHandler onload = null;
        onload = delegate
        {
            item.Loaded -= onload;
            FocusItem(item, name);
        };
        item.Loaded += onload;
        return;
    }

    try
    {
        var myTemplate = FindResource("MyTemplateKey") as FrameworkTemplate; // or however you get your template right now

        var ctl = myTemplate.FindName(name, item) as FrameworkElement;
        ctl.Focus();
    }
    catch
    {
        // focus something else if the template/item wasn't found?
    }
}

I guess the tricky bit is making sure you wait for the item to load. I had to add that code because I was calling this from the ItemContainerGenerator.StatusChanged event and sometimes the ListBoxItem hadn't been fully initialized by the time we entered the method.

Shana answered 30/11, 2008 at 23:10 Comment(1)
FindResource returns an object, so be sure to cast it to a FrameworkTemplate.Scolopendrid
W
8

Ok. So I think I have the best solution. It worked for me anyways. I have a simple data template where I want to give focus to the text box. The FocusManager hands off focus to the text box.

<DataTemplate x:Key="MyDataTemplate" DataType="ListBoxItem">
    <Grid>
        <WrapPanel Orientation="Horizontal" FocusManager.FocusedElement="{Binding ElementName=tbText}">
            <CheckBox IsChecked="{Binding Path=Completed}" Margin="5" />
            <Button Style="{StaticResource ResourceKey=DeleteButtonTemplate}" Margin="5" Click="btnDeleteItem_Click" />
            <TextBox Name="tbText" 
                     Text="{Binding Path=Text}" 
                     Width="200" 
                     TextWrapping="Wrap" 
                     AcceptsReturn="True" 
                     Margin="5" 
                     Focusable="True"/>
            <DatePicker Text="{Binding Path=Date}" Margin="5"/>
        </WrapPanel>
    </Grid>
</DataTemplate>
Wattenberg answered 10/8, 2012 at 0:33 Comment(0)
L
2

Jay's 2nd suggestion is neat - and can be more generalised by using UIElement rather than TextBox, so that any control can easily be made the default:

private void FocusControlOnLoad(object sender, RoutedEventArgs e)
{
    var uiElement = sender as UiElement;
    if(uiElement == null) return;
    uiElement.Focus();
}
Leathaleather answered 9/9, 2010 at 8:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.