自定义 WPF TextBox 的插入符
如何为 WPF TextBox 创建自定义插入符
介绍
本文介绍了 WPF TextBox
Caret 的各种自定义方法。它提供了有关如何更改颜色以及允许您创建自己自定义 Caret 的技术 - 让您在形状和大小上自由发挥。
背景
什么是 Caret?
Caret 是表示接受文本输入的控件当前光标位置的垂直“闪烁”线。
示例:
默认情况下,窗口的 Caret 仅为 1 像素宽,并且当前无法使用 .NET 修改其形状。尽管可以通过 Windows 辅助功能设置更改其粗细,但这会影响整个操作系统,并且如果您希望它与应用程序相关,则不一定总是希望如此。
更改 Caret 颜色
从 .NET 4.0 开始,Microsoft 通过提供 TextBox
控件的 CaretBrush
属性,提供了自定义 Caret 颜色的功能。CaretBrush
允许轻松自定义颜色。
使用样式,可以设置 CaretBrush
属性来指定颜色(本例中为蓝色)。
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type TextBox}" x:Key="TextBoxWithColoredCaretStyle">
<Setter Property="CaretBrush">
<Setter.Value>
<SolidColorBrush Color="Blue"/>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<TextBox Height="47" FontSize="30" Name="textBox1" Width="289" Text="Code Project"
Style="{StaticResource TextBoxWithColoredCaretStyle}"/>
</Grid>
也可以直接通过 TextBox
属性本身设置。
<Grid>
<TextBox Height="47" FontSize="30" Name="textBox1" Width="289" Text="Code Project"
Style="{StaticResource TextBoxWithColoredCaretStyle}" CaretBrush="Blue"/>
</Grid>
结果:
CaretBrush
属性也可以与渐变画笔一起使用。
<Grid>
<TextBox Height="47" FontSize="30" Name="textBox1" Width="289" Text="Code Project">
<TextBox.CaretBrush>
<LinearGradientBrush MappingMode="RelativeToBoundingBox"
StartPoint="0,0"
EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="Magenta" Offset="0.5" />
<GradientStop Color="Green" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextBox.CaretBrush>
</TextBox>
</Grid>
结果:
创建自定义 Caret
为了创建我们自己的自定义 Caret,我们需要了解两件事:TextBox
内置 Caret 的当前位置,以及自定义 Caret 何时需要移动/更新。幸运的是,这些信息可以通过 WPF TextBox
的内置属性和事件获得。
首先,让我们看一下 XAML。
<UserControl x:Class="CustomCaret.CustomCaretTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox x:Name="CustomTextBox"
FontFamily="Gesta"
FontSize="28"
AcceptsReturn="True"
TextWrapping="Wrap"
CaretBrush="Transparent"
Padding="0"
Margin="0"/>
<Canvas>
<Border x:Name="Caret"
Visibility="Collapsed"
Canvas.Left="0"
Canvas.Top="0"
Width="5"
Height="30"
Background="Red">
<Border.Triggers>
<EventTrigger RoutedEvent="Border.Loaded">
<BeginStoryboard>
<Storyboard x:Name="CaretStoryBoard"
RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetProperty="Background.Color"
Duration="0:0:0:1"
FillBehavior="HoldEnd">
<ColorAnimationUsingKeyFrames.KeyFrames >
<DiscreteColorKeyFrame KeyTime="0:0:0.750"
Value="Transparent" />
<DiscreteColorKeyFrame KeyTime="0:0:0.000"
Value="red"/>
</ColorAnimationUsingKeyFrames.KeyFrames>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
</Canvas>
</Grid>
</UserControl>
正如您所见,我在 UserControl
中添加了以下 4 个控件。
Grid
- 这是TextBox
和其他控件的父“容器”。使用Grid
是为了让TextBox
、Canvas
和Border
无缝重叠。这一点很重要,因为我们希望我们的自定义 Caret 看起来像是实际TextBox
的一部分。TextBox
- '内置' Caret 所在的位置。我们将引用此TextBox
来接收 '内置' Caret 移动时的通知,以便我们的自定义 Caret 的位置也能随之移动。TextBox
还将为我们提供 '内置' Caret 的位置。注意TextBox
的 'CaretBrush
' 设置为透明。这是必需的,以便 '内置' Caret 不会显示并分散用户对自定义 Caret 的注意力。Canvas
-Canvas
是自定义 Caret 将在其上移动的区域。由于它位于重叠实际TextBox
的Grid
内,因此自定义 Caret 将看起来像是TextBox
本身的一部分。Border
-Border
是实际的自定义 Caret。这将是在Canvas
上移动/更新的对象。我还为Border
添加了一个时间线,以模拟 '内置' Caret 的闪烁效果。尽管这可以通过多种方式实现,但我认为时间线更容易实现,因为它可以在纯 XAML 中实现。
现在,让我们看一下代码隐藏,
/// <summary>
/// Initializes a new instance of the <see cref="CustomCaretTextBox"/> class.
/// </summary>
public CustomCaretTextBox()
{
InitializeComponent();
this.CustomTextBox.SelectionChanged += (sender, e) => MoveCustomCaret();
this.CustomTextBox.LostFocus += (sender, e) => Caret.Visibility = Visibility.Collapsed;
this.CustomTextBox.GotFocus += (sender, e) => Caret.Visibility = Visibility.Visible;
}
在 UserControl 的构造函数中,我订阅了三个 TextBox
事件(SelectionChanged、LostFocus 和 GotFocus)。
SelectionChanged 事件很重要,因为它负责在 CaretIndex
更改时进行通知。例如,当用户键入、选择文本或使用左右箭头键时,将触发 SelectionChanged 事件。因此,我们也将收到 '内置' Caret 移动时的通知,这表明我们的自定义 Caret 也应该随之移动。
接下来的两个事件(LostFocus 和 GotFocus)仅用于我们自定义 Caret 的视觉效果。我们希望在 TextBox
未获得焦点时“隐藏”自定义 Caret,仅在获得焦点时显示它。
最后是 MoveCustomCaret 方法。
/// <summary>
/// Moves the custom caret on the canvas.
/// </summary>
private void MoveCustomCaret()
{
var caretLocation = CustomTextBox.GetRectFromCharacterIndex(CustomTextBox.CaretIndex).Location;
if (!double.IsInfinity(caretLocation.X))
{
Canvas.SetLeft(Caret, caretLocation.X);
}
if (!double.IsInfinity(caretLocation.Y))
{
Canvas.SetTop(Caret, caretLocation.Y);
}
}
此方法已连接到 SelectionChanged TextBox
事件,并负责将自定义 Caret(Border
控件)移动到画布上的一个 Point
。该 Point
是使用 '内置' Caret 的位置获得的。通过使用 GetRectFromCharacterIndex
,我们可以获得位于 '内置' Caret 索引处的字符的前导边缘的 Rectangle
。调用 Rectangle
的 Location,我们得到 x 和 y 坐标,然后用它们将自定义 Caret 移动到画布上的该位置。
结果:
通过简单地将 Border
控件替换为 Image
控件,也可以使用图像来创建 Caret。
<Grid>
<TextBox x:Name="CustomTextBox"
FontFamily="Gesta"
FontSize="35"
AcceptsReturn="True"
TextWrapping="Wrap"
CaretBrush="Transparent"
Padding="0"
Margin="0"/>
<Canvas>
<Image x:Name="Caret"
Visibility="Collapsed"
Height="40"
Width="10"
Canvas.Left="0"
Canvas.Top="0" Source="/CustomCaret;component/Carrot.png"/>
</Canvas>
</Grid>
结果
历史
- 2013 年 8 月 18 日 - 首次发布。