Click here to Skip to main content
15,881,568 members
Articles / Desktop Programming / WPF

Customizing the Caret of a WPF TextBox

Rate me:
Please Sign up or sign in to vote.
4.86/5 (24 votes)
26 Dec 2013CPOL4 min read 91.6K   1.6K   33   21
How to create a custom caret for a WPF TextBox

Introduction 

This article describes the different ways a WPF TextBox Caret can be customized. It provides information on how to change the color, as well as a technique that will allow you to create your own custom Caret - providing you freedom with shape and size. 

Background  

What is a Caret? 

A Caret is the vertical 'blinking' line that represents the current cursor position of a control that accepts text input.  

Example:   

By default, the Window's Caret is only 1-pixel wide and the shape is currently not modifiable using .NET. Even-though the thickness can be changed through the Windows Accessibility settings, it is an OS wide change, and not always desired if you want it to be application dependent. 

Changing the Caret color  

Starting in .NET 4.0, Microsoft has provided the capability to customize the Caret's color by providing the CaretBrush property of a TextBox control. The CaretBrush allows for easy color customization.

Using a style, the CaretBrush property can be set to specify the color (in this case blue). 

XML
 <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> 

It can also be set simply through the TextBox property itself.

XML
<Grid>
    <TextBox Height="47" FontSize="30" Name="textBox1" Width="289" Text="Code Project" 
             Style="{StaticResource TextBoxWithColoredCaretStyle}" CaretBrush="Blue"/>
</Grid>  

Result: 

The CaretBrush property can be used with a gradient brush as well.

XML
 <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>  

Result: 

 

Creating your own Custom Caret 

In order to create our own custom Caret, we'll need to know two main things; the current position of the TextBox's built in Caret, and when the custom Caret needs to be moved/updated. Luckily, this information is available using the built in properties and events of the WPF TextBox

First, lets take a look at the XAML. 

XML
<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> 

As you can see, I've added the following 4 controls to a UserControl.    

  • Grid - This is the parent "container" of the TextBox and the other controls. A Grid is used so that the TextBox, Canvas, and Border overlap seamlessly. This is important because we want our custom Caret to appear as if it was part of the actual TextBox.  
  • TextBox - The TextBox where the 'built-in' Caret lives. We'll be referencing this TextBox to get notified when the 'built-in' Caret moves so that the position of our custom Caret can move as well. The TextBox will also provide us with the 'built-in' Caret's position. Notice the 'CaretBrush' of the TextBox is set to Transparent. This is required so that the 'built-in' Caret is not displayed and distracting the user from the custom Caret
  • Canvas - The Canvas is the area that the custom Caret will move on. Since it lives inside a Grid that is overlapping the actual TextBox, the custom Caret will appear as if it was part of the TextBox itself. 
  • Border - The Border is the actual custom Caret. This is what will be moving/updating across the Canvas. I've also added a Storyboard to the Border to simulate the blinking effect of a 'built-in' Caret. Even though this can be achieved in various ways, I felt a Storyboard was a bit easier as it can be implemented in pure XAML.  

Now, lets look at the code behind, 

C#
/// <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;
}

Inside the UserControl's constructor, I subscribe to three TextBox events (SelectionChanged, LostFocus, and GotFous).

The SelectionChanged event is important as it is responsible for notification of when the CaretIndex has changed. For example, when the user types, selects text, or uses the left and right arrow keys, the SelectionChanged event will get fired. Because of this, we'll also be notified of when the 'built-in' Caret has moved, which indicates that our custom Caret should be moved as well.

The next two events (LostFocus and GotFocus) are used simply for visual purposes of our custom Caret. We want to 'hide' the custom Caret when the TextBox is not focused and only show it when it is.

Lastly, the MoveCustomCaret method.  

C#
/// <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);
    }
}

This method is hooked up to the SelectionChanged TextBox event and is responsible for moving the custom Caret (the Border control) to a Point on the canvas. The Point is obtained using the Location of the 'built-in' Caret. By using GetRectFromCharacterIndex, we can get the Rectangle of the leading edge for the character that's at the index of the 'built-in' Caret. Calling Location on the Rectangle, we get the x and y-coordinates which are then used to move the custom Caret to that location on the canvas.  

Result: 

The Caret can also be made using an image by simply replacing the Border control with an Image control. 

XML
 <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> 

Result:

   

History    

  • 2013, August 18  - Initial publication.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Software Engineer - San Diego, CA

Comments and Discussions

 
BugDoubleclick Word Selection Pin
garzooma19-Sep-19 9:26
garzooma19-Sep-19 9:26 
GeneralRe: Doubleclick Word Selection Pin
garzooma23-Sep-19 6:49
garzooma23-Sep-19 6:49 
QuestionFlowDirection Pin
M.H. Shojaei13-Mar-19 19:07
M.H. Shojaei13-Mar-19 19:07 
QuestionTextWrapping = "Wrap" causes issues Pin
VishalDPrajapati30-Oct-18 2:01
VishalDPrajapati30-Oct-18 2:01 
QuestionTextWrapping="NoWrap" Pin
daryl.fish.uk10-Jul-18 23:51
daryl.fish.uk10-Jul-18 23:51 
QuestionCool Pin
Marco Bertschi7-Apr-16 2:26
protectorMarco Bertschi7-Apr-16 2:26 
AnswerRe: Cool Pin
d.moncada8-Apr-16 5:08
d.moncada8-Apr-16 5:08 
GeneralMy vote of 5 Pin
fredatcodeproject26-Dec-13 13:00
professionalfredatcodeproject26-Dec-13 13:00 
GeneralRe: My vote of 5 Pin
d.moncada26-Dec-13 13:04
d.moncada26-Dec-13 13:04 
GeneralCool Pin
Herbert Lausmann26-Dec-13 9:57
professionalHerbert Lausmann26-Dec-13 9:57 
GeneralRe: Cool Pin
d.moncada5-Jan-14 9:05
d.moncada5-Jan-14 9:05 
GeneralMy vote of 5 Pin
Jerameel11-Sep-13 22:06
Jerameel11-Sep-13 22:06 
GeneralRe: My vote of 5 Pin
d.moncada11-Sep-13 22:11
d.moncada11-Sep-13 22:11 
GeneralMy vote of 5 Pin
fredatcodeproject19-Aug-13 10:44
professionalfredatcodeproject19-Aug-13 10:44 
much better with a source file downloadable
GeneralRe: My vote of 5 Pin
d.moncada19-Aug-13 17:11
d.moncada19-Aug-13 17:11 
GeneralMy vote of 4 Pin
fredatcodeproject19-Aug-13 3:51
professionalfredatcodeproject19-Aug-13 3:51 
GeneralRe: My vote of 4 Pin
d.moncada19-Aug-13 4:17
d.moncada19-Aug-13 4:17 
GeneralRe: My vote of 4 Pin
fredatcodeproject19-Aug-13 5:40
professionalfredatcodeproject19-Aug-13 5:40 
GeneralRe: My vote of 4 Pin
d.moncada19-Aug-13 5:48
d.moncada19-Aug-13 5:48 
GeneralRe: My vote of 4 Pin
d.moncada19-Aug-13 7:27
d.moncada19-Aug-13 7:27 
GeneralRe: My vote of 4 Pin
fredatcodeproject19-Aug-13 10:44
professionalfredatcodeproject19-Aug-13 10:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.