I originally thought it could be possible to use ListView.ContainerFromItem
to retrieve the newly added item and then use VisualTreeHelper
to search for the TextBox
within the template and focus it. It turns out however, that this solution does not work. After adding the item, the container for the list item is not materialized immediately (which is logical, as the control just got the notification about the collection change, but didn't have any time to build up the control hierarchy yet, as our code is still executing).
In fact, the problem has a far simpler solution. You can use the Loaded
event on the TextBox
within the template. This will be called only once, when the template is first materialized. This is not a perfect solution however, see update below.
In my example I have the following template:
<DataTemplate>
<Grid>
<TextBox Loaded="InputTextBox_Loaded" />
</Grid>
</DataTemplate>
And in the code-behind:
private void InputTextBox_Loaded(object sender, RoutedEventArgs e)
{
var textBox = (TextBox)sender;
textBox.Focus(FocusState.Programmatic);
}
Update: Virtualization
Turns out there is a catch - virtualization creates a number of copies of the template in memory (depending on the window size) to allow for comfortable scrolling, but after that it will just reuse the existing controls - and the Loaded
event will never be called again - that's a problem. Luckily, we can solve this as well - instead of the Loaded
event we will use DataContextChanged
.
Updated XAML:
<DataTemplate>
<Grid >
<TextBox DataContextChanged="TextBox_DataContextChanged" />
</Grid>
</DataTemplate>
Updated code-behind:
private void TextBox_DataContextChanged(
FrameworkElement sender,
DataContextChangedEventArgs args)
{
var textBox = (TextBox)sender;
if ( args.NewValue == Items.Last())
{
//last item, focus it
textBox.Focus(FocusState.Programmatic);
}
}
Ok, that is better, we are getting there! Only one thing left to make it perfect. The current configuration means that once we scroll the last item into view, it will always get focus, which might not be what we want. Instead, we probably want this to happen only once - when the item is newly added. We can do so by adding a bool
flag which we set to true
when adding a new item into the collection and flip back to false
when we focus it the first time:
//set this to true when a new item is being added to the collection
private bool _focusItem = true;
private void TextBox_DataContextChanged(
FrameworkElement sender,
DataContextChangedEventArgs args)
{
var textBox = (TextBox)sender;
if (args.NewValue == Items[Items.Count - 1] && _focusItem)
{
//last item, focus it
textBox.Focus(FocusState.Programmatic);
_focusItem = false;
}
}
