我原本以为可以使用ListView.ContainerFromItem检索新添加的项目,然后使用VisualTreeHelper在模板中搜索TextBox并聚焦。然而事实证明,这个解决方案不起作用。添加项目后,列表项的容器不会立即具体化(这是合乎逻辑的,因为控件刚刚收到有关集合更改的通知,但还没有时间构建控件层次结构,就像我们的代码一样仍在执行中)。
事实上,这个问题有一个更简单的解决方案。您可以在模板内的TextBox 上使用Loaded 事件。这只会在模板第一次具体化时调用一次。 但这不是一个完美的解决方案,请参阅下面的更新。
在我的示例中,我有以下模板:
<DataTemplate>
<Grid>
<TextBox Loaded="InputTextBox_Loaded" />
</Grid>
</DataTemplate>
在代码隐藏中:
private void InputTextBox_Loaded(object sender, RoutedEventArgs e)
{
var textBox = (TextBox)sender;
textBox.Focus(FocusState.Programmatic);
}
更新:虚拟化
原来有一个问题 - 虚拟化会在内存中创建多个模板副本(取决于窗口大小)以允许舒适的滚动,但之后它只会重用现有控件 - 以及 Loaded事件将永远不会被再次调用 - 这是一个问题。幸运的是,我们也可以解决这个问题 - 我们将使用 DataContextChanged 来代替 Loaded 事件。
更新的 XAML:
<DataTemplate>
<Grid >
<TextBox DataContextChanged="TextBox_DataContextChanged" />
</Grid>
</DataTemplate>
更新的代码隐藏:
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);
}
}
好的,那就更好了,我们快到了!只剩下一件事让它变得完美。当前的配置意味着一旦我们将最后一个项目滚动到视图中,它将始终获得焦点,这可能不是我们想要的。相反,我们可能希望这种情况只发生一次 - 当新添加项目时。我们可以通过添加一个bool 标志来做到这一点,当我们将新项目添加到集合中时我们将其设置为true,并在我们第一次关注它时翻转回false:
//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;
}
}