WPF自定义水印TextBox调用Focus()获焦后不显示输入光标问题
问题原因
这个光标不显示的问题核心是自定义模板的焦点路由配置错误:
你的模板在目标TextBox内部又嵌套了一层名为inputText的TextBox作为实际输入载体,外层Border上设置的FocusManager.FocusedElement绑定不会在代码调用外层Focus()方法时,自动将键盘焦点传递给内部的输入框。
你看到背景变黄,是因为外层作为样式目标的TextBox拿到了逻辑焦点,触发了IsFocused=True对应的样式触发器,但实际承载输入逻辑的内部inputText没有拿到键盘焦点,自然不会显示输入光标。此时能输入内容只是WPF路由文本输入事件冒泡到内部文本框导致的异常表现,不是正常行为。
存在的配置问题
- 模板结构冗余:在TextBox的ControlTemplate中嵌套另一层TextBox作为输入元素,本身就不符合WPF控件模板的设计规范,原生TextBox的模板预留了
PART_ContentHost作为输入内容宿主,不需要额外嵌套输入控件 - 焦点配置失效:
FocusManager.FocusedElement附加属性设置在Border上,仅能在Border首次通过交互获得焦点时尝试转移焦点,无法响应代码主动调用外层TextBox.Focus()的场景 - 触发器写法不规范:你写的焦点触发器里
Property=" IsFocused"和Value=" true"前面多了无意义的空格,虽然WPF类型转换器能容错识别,但长期来看容易引发匹配异常 - 焦点同步缺失:外层TextBox和内部嵌套的
inputText没有做焦点联动,外层拿到焦点时没有主动将键盘焦点推给实际承载输入的内部控件
修复方法
最小改动修复
如果不想调整现有模板结构,只需要给模板根Border添加GotFocus事件处理,在外层获得焦点时主动把键盘焦点设置到内部输入框即可,同时修正触发器的多余空格:
首先修改XAML模板部分:
<ControlTemplate TargetType="{x:Type TextBox}"> <!-- 移除Border上无效的FocusManager.FocusedElement绑定,添加GotFocus事件 --> <Border x:Name="bg" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" GotFocus="Bg_GotFocus"> <Grid> <TextBlock Text="{TemplateBinding Tag}" VerticalAlignment="Center" Margin="8,0,0,0"> <TextBlock.Visibility> <MultiBinding Converter="{StaticResource InputToVisibility}"> <Binding ElementName="inputText" Path="Text.IsEmpty" /> <Binding ElementName="inputText" Path="IsFocused" /> </MultiBinding> </TextBlock.Visibility> </TextBlock> <TextBox Name="inputText" Background="Transparent" BorderThickness="0" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" /> </Grid> </Border> <ControlTemplate.Triggers> <!-- 修正属性和值前的多余空格 --> <Trigger Property="IsFocused" Value="True"> <Setter TargetName="bg" Property="BorderThickness" Value="5" /> <Setter TargetName="bg" Property="Background" Value="Yellow" /> </Trigger> <Trigger Property="IsFocused" Value="False"> <Setter TargetName="bg" Property="Background" Value="red" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
添加后台事件处理代码:
private void Bg_GotFocus(object sender, RoutedEventArgs e) { if (sender is not Border border) return; if (border.FindName("inputText") is not TextBox innerTextBox) return; // 主动将键盘焦点设置到内部输入框,光标即可正常显示 innerTextBox.Focus(); Keyboard.Focus(innerTextBox); e.Handled = true; }
推荐规范写法
建议移除嵌套的TextBox,按照WPF TextBox的模板约定使用PART_ContentHost作为输入宿主,从根源上避免焦点异常,性能也更好:
<ControlTemplate TargetType="{x:Type TextBox}"> <Border x:Name="bg" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Grid> <TextBlock Text="{TemplateBinding Tag}" VerticalAlignment="Center" Margin="8,0,0,0"> <TextBlock.Visibility> <MultiBinding Converter="{StaticResource InputToVisibility}"> <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Text.IsEmpty" /> <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="IsFocused" /> </MultiBinding> </TextBlock.Visibility> </TextBlock> <!-- 用约定的PART_ContentHost替代嵌套TextBox,原生输入逻辑自动生效 --> <ScrollViewer x:Name="PART_ContentHost" Margin="8,0,0,0" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsFocused" Value="True"> <Setter TargetName="bg" Property="BorderThickness" Value="5" /> <Setter TargetName="bg" Property="Background" Value="Yellow" /> </Trigger> <Trigger Property="IsFocused" Value="False"> <Setter TargetName="bg" Property="Background" Value="red" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
这种写法不需要额外写后台焦点处理代码,调用Focus()方法时光标会正常显示,水印逻辑也能正常工作。
内容的提问来源于stack exchange,提问作者HimorriveL
相关产品推荐
相关产品推荐

