65.9K
CodeProject 正在变化。 阅读更多。
Home

自定义 WPF TextBox 的插入符

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (23投票s)

2013 年 8 月 19 日

CPOL

4分钟阅读

viewsIcon

97839

downloadIcon

1722

如何为 WPF TextBox 创建自定义插入符

介绍 

本文介绍了 WPF TextBoxCaret 的各种自定义方法。它提供了有关如何更改颜色以及允许您创建自己自定义 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 是为了让 TextBoxCanvasBorder 无缝重叠。这一点很重要,因为我们希望我们的自定义 Caret 看起来像是实际 TextBox 的一部分。
  • TextBox - '内置' Caret 所在的位置。我们将引用此 TextBox 来接收 '内置' Caret 移动时的通知,以便我们的自定义 Caret 的位置也能随之移动。TextBox 还将为我们提供 '内置' Caret 的位置。注意 TextBox 的 'CaretBrush' 设置为透明。这是必需的,以便 '内置' Caret 不会显示并分散用户对自定义 Caret 的注意力。
  • Canvas - Canvas 是自定义 Caret 将在其上移动的区域。由于它位于重叠实际 TextBoxGrid 内,因此自定义 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 事件(SelectionChangedLostFocusGotFocus)。

SelectionChanged 事件很重要,因为它负责在 CaretIndex 更改时进行通知。例如,当用户键入、选择文本或使用左右箭头键时,将触发 SelectionChanged 事件。因此,我们也将收到 '内置' Caret 移动时的通知,这表明我们的自定义 Caret 也应该随之移动。

接下来的两个事件(LostFocusGotFocus)仅用于我们自定义 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 事件,并负责将自定义 CaretBorder 控件)移动到画布上的一个 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 日 - 首次发布。
© . All rights reserved.