Hello @水 知 ,
To summarize this question. Currently, there is no public API in UWP that can achieve caret with rounded edges on the top and bottom, only simple modifications can be implemented in UWP.
Thank you.
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
I want to make customized TextBox and I don't know how to customize the caret in UWP.
I thought of these ways to make it:
Please help!
Hello,
Welcome to Microsoft Q&A!
What kind of caret do you want to achieve?
In this document Custom text input, it seems that only simple modifications can be achieved in UWP.
Thank you.
Hello Junjie Zhu,
I'm considering using a vertical line as the caret but it's wider and has round edges at the top and bottom, I still can't achive it! Is that possible in UWP?
Thanks,
Shimizu
Sorry, there is no public API in UWP that can achieve your needs.
Thanks, is that possible to use a ContentPresenter to get input from keyboard and IME, and locate the position of a character? Now I'm considering to remake a TextBox.
I think it is difficult to achieve caret with rounded edges on the top and bottom in UWP. I searched the official documentation and couldn't find any clues.
Hello Junjie Zhu,
Sorry for disturbing you again, I've just wrote a comment but I can't found it, so I rewrite it. since you mentioned there's no public API, is the way 2.(Use a rectangle and let it blink) or 3.(Remake a TextBox) possible? I was working on way 2 and finally can make a fake caret not blinking, it can move with the system caret. But how can we keep it syncing blink with system caret? I found the system caret dosen't just blink, it will keep highlighting in some cases such as typing. I don't know how to make it.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CustomCaret">
<Style TargetType="local:CustomCaretTextBox" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomCaretTextBox">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Canvas>
<TextBox x:Name="MyTextBox"
TextChanged="MyTextBox_TextChanged"
GotFocus="MyTextBox_GotFocus"
LostFocus="MyTextBox_LostFocus"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
AcceptsReturn="True"/>
<Rectangle x:Name="CaretRectangle"
Fill="Black"
Width="4"
Height="20"
Visibility="Collapsed"
VerticalAlignment="Center"
Margin="10,5,0,0"
RadiusX="2"
RadiusY="2"/>
</Canvas>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
// The Templated Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234235
namespace CustomCaret
{
public sealed class CustomCaretTextBox : Control
{
private Rectangle caretRectangle;
private TextBox textBox;
private bool isCaretVisible;
public CustomCaretTextBox()
{
DefaultStyleKey = typeof(CustomCaretTextBox);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
caretRectangle = GetTemplateChild("CaretRectangle") as Rectangle;
textBox = GetTemplateChild("MyTextBox") as TextBox;
// Add event handlers
this.GotFocus += OnGotFocus;
this.LostFocus += OnLostFocus;
textBox.TextChanged += OnTextChanged;
this.KeyDown += OnKeyDown;
}
private void OnGotFocus(object sender, RoutedEventArgs e)
{
StartCaretBlinking();
UpdateCaretPosition();
}
private void OnLostFocus(object sender, RoutedEventArgs e)
{
StopCaretBlinking();
}
private void StartCaretBlinking()
{
isCaretVisible = true;
caretRectangle.Visibility = Visibility.Visible;
// Implement a timer for blinking if desired
}
private void StopCaretBlinking()
{
isCaretVisible = false;
caretRectangle.Visibility = Visibility.Collapsed;
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Update caret position logic here
UpdateCaretPosition();
}
private void OnKeyDown(object sender, KeyRoutedEventArgs e)
{
// Update caret position on key press
UpdateCaretPosition();
}
private void UpdateCaretPosition()
{
if (textBox.SelectionStart > 0 && textBox.SelectionStart <= textBox.Text.Length)
{
Rect rectLast;
int lastIndex = textBox.SelectionStart + textBox.SelectionLength;
if (lastIndex == textBox.Text.Length)
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex - 1, true);
}
else
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex, false);
}
// Set the caret rectangle's position
Canvas.SetLeft(caretRectangle, rectLast.X);
Canvas.SetTop(caretRectangle, rectLast.Y);
// Show the caret
caretRectangle.Visibility = Visibility.Visible;
}
else
{
// Hide the caret if the index is invalid
caretRectangle.Visibility = Visibility.Collapsed;
}
}
}
}
And... as for the way 3, is there any possible ways to make a control get input from an IME?
Thanks!
Hello @水 知 ,
To summarize this question. Currently, there is no public API in UWP that can achieve caret with rounded edges on the top and bottom, only simple modifications can be implemented in UWP.
Thank you.
Hello Junjie Zhu,
Thanke for that and finnally I got this, if we can't modify the caret directly, how about we use a rectangle to make a fake caret to cover that? If that's a possible solvation, how can we make the caret sync blinking with the system caret? Since the system caret is not just simply blinking, it will keep highlighting while typing and keeping highlighting after blinked 6 (or 7?) times.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CustomCaret">
<Style TargetType="local:CustomCaretTextBox" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomCaretTextBox">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Canvas>
<TextBox x:Name="MyTextBox"
TextChanged="MyTextBox_TextChanged"
GotFocus="MyTextBox_GotFocus"
LostFocus="MyTextBox_LostFocus"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"/>
<Rectangle x:Name="CaretRectangle"
Fill="Black"
Width="4"
Height="20"
Visibility="Collapsed"
VerticalAlignment="Center"
Margin="10,5,0,0"
RadiusX="2"
RadiusY="2"/>
</Canvas>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
// The Templated Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234235
namespace CustomCaret
{
public sealed class CustomCaretTextBox : Control
{
private Rectangle caretRectangle;
private TextBox textBox;
private bool isCaretVisible;
public CustomCaretTextBox()
{
DefaultStyleKey = typeof(CustomCaretTextBox);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
caretRectangle = GetTemplateChild("CaretRectangle") as Rectangle;
textBox = GetTemplateChild("MyTextBox") as TextBox;
// Add event handlers
this.GotFocus += OnGotFocus;
this.LostFocus += OnLostFocus;
textBox.TextChanged += OnTextChanged;
this.KeyDown += OnKeyDown;
}
private void OnGotFocus(object sender, RoutedEventArgs e)
{
StartCaretBlinking();
UpdateCaretPosition();
}
private void OnLostFocus(object sender, RoutedEventArgs e)
{
StopCaretBlinking();
}
private void StartCaretBlinking()
{
isCaretVisible = true;
caretRectangle.Visibility = Visibility.Visible;
// Implement a timer for blinking if desired
}
private void StopCaretBlinking()
{
isCaretVisible = false;
caretRectangle.Visibility = Visibility.Collapsed;
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Update caret position logic here
UpdateCaretPosition();
}
private void OnKeyDown(object sender, KeyRoutedEventArgs e)
{
// Update caret position on key press
UpdateCaretPosition();
}
private void UpdateCaretPosition()
{
if (textBox.SelectionStart > 0 && textBox.SelectionStart <= textBox.Text.Length)
{
Rect rectLast;
int lastIndex = textBox.SelectionStart + textBox.SelectionLength;
if (lastIndex == textBox.Text.Length)
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex - 1, true);
}
else
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex, false);
}
// Set the caret rectangle's position
Canvas.SetLeft(caretRectangle, rectLast.X);
Canvas.SetTop(caretRectangle, rectLast.Y);
// Show the caret
caretRectangle.Visibility = Visibility.Visible;
}
else
{
// Hide the caret if the index is invalid
caretRectangle.Visibility = Visibility.Collapsed;
}
}
}
}
```Thank you.
Hello @水 知 ,
This is a great workaround.
I added a blinking animation that repeats for 7 seconds to your code, hope that helps.
<Style TargetType="local:CustomCaretTextBox" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomCaretTextBox">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Canvas>
<Canvas.Resources>
<Storyboard x:Name="CaretRectangleStoryboard" BeginTime="00:00:00"
RepeatBehavior="00:00:07">
<DoubleAnimation
Storyboard.TargetName="CaretRectangle"
Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:1"/>
</Storyboard>
</Canvas.Resources>
<TextBox x:Name="MyTextBox"
TextChanged="MyTextBox_TextChanged"
GotFocus="MyTextBox_GotFocus"
LostFocus="MyTextBox_LostFocus"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"/>
<Rectangle x:Name="CaretRectangle"
Fill="Black"
Width="4"
Height="20"
Visibility="Collapsed"
VerticalAlignment="Center"
Margin="10,5,0,0"
RadiusX="2"
RadiusY="2"/>
</Canvas>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public sealed class CustomCaretTextBox : Control
{
private Rectangle caretRectangle;
private TextBox textBox;
private bool isCaretVisible;
private Storyboard storyboard;
public CustomCaretTextBox()
{
DefaultStyleKey = typeof(CustomCaretTextBox);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
caretRectangle = GetTemplateChild("CaretRectangle") as Rectangle;
textBox = GetTemplateChild("MyTextBox") as TextBox;
storyboard= GetTemplateChild("CaretRectangleStoryboard") as Storyboard;
// Add event handlers
textBox.TextChanged += OnTextChanged;
textBox.SelectionChanged += TextBox_SelectionChanged;
}
private void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
UpdateCaretPosition();
storyboard.Begin();
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Update caret position logic here
UpdateCaretPosition();
storyboard.Begin();
}
private void UpdateCaretPosition()
{
if (textBox.SelectionStart > 0 && textBox.SelectionStart <= textBox.Text.Length)
{
Rect rectLast;
int lastIndex = textBox.SelectionStart + textBox.SelectionLength;
if (lastIndex == textBox.Text.Length)
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex - 1, true);
}
else
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex, false);
}
// Set the caret rectangle's position
Canvas.SetLeft(caretRectangle, rectLast.X);
Canvas.SetTop(caretRectangle, rectLast.Y);
// Show the caret
caretRectangle.Visibility = Visibility.Visible;
}
else
{
// Hide the caret if the index is invalid
caretRectangle.Visibility = Visibility.Collapsed;
}
}
}
Hi Junjie Zhu,
That inspired me on modifying the caret! and I made this. It looks woring perfectry while in a single line, but whem I set AcceptsReturn true and press enter at the end of the Text, the position of customized caret is not sync with the system caret. here's my code:
Hi Junjie Zhu,
Thanks for your inspiring code! I modified the caret more and met 2 new problems:
1. When AcceptReturn=True and the caret is at the end of the string, if press Enter, the position of the moified caret is not same with the system caret.
2. When AccrptReturn=True, if we press Enter too quickly, the position of the caret will be out of the TextBox.
Here's my code:
xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CustomCaret">
<Style TargetType="local:CustomCaretTextBox" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomCaretTextBox">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Canvas>
<Canvas.Resources>
<Storyboard x:Name="CaretRectangleStoryboard" BeginTime="00:00:00"
RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames
Duration="0:0:1"
Storyboard.TargetName="CaretRectangle"
Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame KeyTime="0:0:0.7" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.99" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<QuarticEase EasingMode="EaseOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<DiscreteDoubleKeyFrame KeyTime="0:0:1" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Canvas.Resources>
<TextBox x:Name="MyTextBox"
TextChanged="MyTextBox_TextChanged"
GotFocus="MyTextBox_GotFocus"
LostFocus="MyTextBox_LostFocus"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
CornerRadius="4"
AcceptsReturn="True"/>
<Rectangle x:Name="CaretCoverRectangle"
IsHitTestVisible="False"
Fill="White"
Height="21"
Width="2"
Visibility="Collapsed"
VerticalAlignment="Center"
Margin="11,4,0,0"
RadiusX="2"
RadiusY="2"/>
<Rectangle x:Name="CaretRectangle"
IsHitTestVisible="False"
Fill="Black"
Width="3"
Height="16"
Visibility="Collapsed"
VerticalAlignment="Center"
Margin="12,8,0,0"
RadiusX="2"
RadiusY="2"/>
</Canvas>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Shapes;
// The Templated Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234235
namespace CustomCaret
{
public sealed class CustomCaretTextBox : Control
{
private Rectangle caretRectangle;
private Rectangle caretCoverRectangle;
private TextBox textBox;
private bool isCaretVisible;
private Storyboard storyboard;
public CustomCaretTextBox()
{
DefaultStyleKey = typeof(CustomCaretTextBox);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
caretRectangle = GetTemplateChild("CaretRectangle") as Rectangle;
caretCoverRectangle = GetTemplateChild("CaretCoverRectangle") as Rectangle;
textBox = GetTemplateChild("MyTextBox") as TextBox;
storyboard = GetTemplateChild("CaretRectangleStoryboard") as Storyboard;
// Add event handlers
textBox.TextChanged += OnTextChanged;
textBox.SelectionChanged += TextBox_SelectionChanged;
textBox.GettingFocus += TextBox_GettingFocus;
textBox.LostFocus += TextBox_LostFocus;
textBox.KeyDown += MyTextBox_KeyDown;
}
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
caretRectangle.Visibility = Visibility.Collapsed;
caretCoverRectangle.Visibility = Visibility.Collapsed;
storyboard?.Stop();
}
private void TextBox_GettingFocus(UIElement sender, GettingFocusEventArgs args)
{
caretRectangle.Visibility = Visibility.Visible;
caretCoverRectangle.Visibility = Visibility.Visible;
UpdateCaretPosition();
storyboard?.Stop();
storyboard.Begin();
}
private void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
//Selecting characters
if (textBox.SelectedText.Length > 0)
{
UpdateCaretPosition();
if (textBox.SelectedText.Length < textBox.Text.Length)
{
caretRectangle.Visibility = Visibility.Collapsed;
storyboard?.Stop();
}
else
{
caretCoverRectangle.Visibility = Visibility.Collapsed;
caretRectangle.Visibility = Visibility.Collapsed;
storyboard?.Stop();
}
}
else
{
caretRectangle.Visibility = Visibility.Visible;
caretCoverRectangle.Visibility = Visibility.Visible;
UpdateCaretPosition();
storyboard?.Stop();
storyboard.Begin();
}
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Update caret position logic here
caretRectangle.Visibility = Visibility.Visible;
caretCoverRectangle.Visibility = Visibility.Visible;
UpdateCaretPosition();
storyboard?.Stop();
storyboard.Begin();
}
private void MyTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
UpdateCaretPosition();
}
private void UpdateCaretPosition()
{
if (textBox.Text.Length > 0)
{
if (textBox.SelectionStart > 0 && textBox.SelectionStart <= textBox.Text.Length)
{
Rect rectLast;
int lastIndex = textBox.SelectionStart + textBox.SelectionLength;
if (lastIndex == textBox.Text.Length)
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex - 1, true);
}
else
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex, false);
}
// Set the caret rectangle's position
Canvas.SetLeft(caretCoverRectangle, rectLast.X);
Canvas.SetTop(caretCoverRectangle, rectLast.Y);
Canvas.SetLeft(caretRectangle, rectLast.X);
Canvas.SetTop(caretRectangle, rectLast.Y);
// Show the caret
caretRectangle.Visibility = Visibility.Visible;
}
else
{
// Hide the caret if the index is invalid
caretRectangle.Visibility = Visibility.Collapsed;
}
}
else
{
// Set the caret rectangle's position
Canvas.SetLeft(caretCoverRectangle, 0);
Canvas.SetTop(caretCoverRectangle, 0);
Canvas.SetLeft(caretRectangle, 0);
Canvas.SetTop(caretRectangle, 0);
// Show the caret
caretRectangle.Visibility = Visibility.Visible;
}
}
}
}
Thanks!
Hello @水 知 ,
Try this, force the rectangle to the next line when the last character is a carriage return.
private void UpdateCaretPosition()
{
if (textBox.Text.Length > 0)
{
if (textBox.SelectionStart >= 0 && textBox.SelectionStart <= textBox.Text.Length)
{
Rect rectLast;
int lastIndex = textBox.SelectionStart + textBox.SelectionLength;
powershell
if (lastIndex > 0)
{
if (textBox.Text[lastIndex - 1] == 13) // Special treatment for return characters
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex - 1, false);
Canvas.SetLeft(caretCoverRectangle, 0);
Canvas.SetTop(caretCoverRectangle, rectLast.Y + 19);
Canvas.SetLeft(caretRectangle, 0);
Canvas.SetTop(caretRectangle, rectLast.Y + 19);
}
else
{
if (lastIndex == textBox.Text.Length)
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex - 1, true);
}
else
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex, false);
}
// Set the caret rectangle's position
Canvas.SetLeft(caretCoverRectangle, rectLast.X);
Canvas.SetTop(caretCoverRectangle, rectLast.Y);
Canvas.SetLeft(caretRectangle, rectLast.X);
Canvas.SetTop(caretRectangle, rectLast.Y);
}
}
else
{
// Set the caret rectangle's position
Canvas.SetLeft(caretCoverRectangle, 0);
Canvas.SetTop(caretCoverRectangle, 0);
Canvas.SetLeft(caretRectangle, 0);
Canvas.SetTop(caretRectangle, 0);
// Show the caret
caretRectangle.Visibility = Visibility.Visible;
}
caretRectangle.Visibility = Visibility.Visible;
}
else
{
// Hide the caret if the index is invalid
caretRectangle.Visibility = Visibility.Collapsed;
}
}
else
{
// Set the caret rectangle's position
Canvas.SetLeft(caretCoverRectangle, 0);
Canvas.SetTop(caretCoverRectangle, 0);
Canvas.SetLeft(caretRectangle, 0);
Canvas.SetTop(caretRectangle, 0);
// Show the caret
caretRectangle.Visibility = Visibility.Visible;
}
}
Hello Junjie Zhu,
Thanks, That works! But when I further tested the control, I found these problems, when I tried to solve them, I failed to solve these problems
Plus, since Canvas.SetTop(caretCoverRectangle, rectLast.Y + 19);
is using a constant value, it won't fit when the FontSize is not the defalut value.
protected override void OnApplyTemplate()
{
//...
textBox.Loaded += TextBox_Loaded;
}
private void TextBox_Loaded(object sender, RoutedEventArgs e)
{
textBox.FontSize = FontSize;
}
And here's my new code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Shapes;
// The Templated Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234235
namespace CustomCaret
{
public sealed class CustomCaretTextBox : Control
{
private Rectangle caretRectangle;
private Rectangle caretCoverRectangle;
private TextBox textBox;
private bool isCaretVisible;
private Storyboard storyboard;
public CustomCaretTextBox()
{
DefaultStyleKey = typeof(CustomCaretTextBox);
}
public static new readonly DependencyProperty FontSizeProperty
=DependencyProperty.Register(
nameof(FontSize),
typeof(CustomCaretTextBox),
typeof(double),
new PropertyMetadata(null));
public new double FontSize
{
get => (double)GetValue(FontSizeProperty);
set
{
UpdateElementSize();
SetValue(FontSizeProperty, value);
}
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
caretRectangle = GetTemplateChild("CaretRectangle") as Rectangle;
caretCoverRectangle = GetTemplateChild("CaretCoverRectangle") as Rectangle;
textBox = GetTemplateChild("MyTextBox") as TextBox;
storyboard = GetTemplateChild("CaretRectangleStoryboard") as Storyboard;
// Add event handlers
textBox.TextChanged += OnTextChanged;
textBox.SelectionChanged += TextBox_SelectionChanged;
textBox.GettingFocus += TextBox_GettingFocus;
textBox.LostFocus += TextBox_LostFocus;
textBox.KeyDown += MyTextBox_KeyDown;
}
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
caretRectangle.Visibility = Visibility.Collapsed;
caretCoverRectangle.Visibility = Visibility.Collapsed;
storyboard?.Stop();
}
private void TextBox_GettingFocus(UIElement sender, GettingFocusEventArgs args)
{
UpdateElementSize();
caretRectangle.Visibility = Visibility.Visible;
caretCoverRectangle.Visibility = Visibility.Visible;
UpdateCaretPosition();
storyboard?.Stop();
storyboard.Begin();
}
private void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
//Selecting characters
if (textBox.SelectedText.Length > 0)
{
UpdateCaretPosition();
if (textBox.SelectedText.Length < textBox.Text.Length)
{
caretRectangle.Visibility = Visibility.Collapsed;
storyboard?.Stop();
}
else
{
caretCoverRectangle.Visibility = Visibility.Collapsed;
caretRectangle.Visibility = Visibility.Collapsed;
storyboard?.Stop();
}
}
else
{
caretRectangle.Visibility = Visibility.Visible;
caretCoverRectangle.Visibility = Visibility.Visible;
UpdateCaretPosition();
storyboard?.Stop();
storyboard.Begin();
}
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Update caret position logic here
caretRectangle.Visibility = Visibility.Visible;
caretCoverRectangle.Visibility = Visibility.Visible;
UpdateCaretPosition();
storyboard?.Stop();
storyboard.Begin();
}
private void MyTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
UpdateCaretPosition();
}
private void UpdateCaretPosition()
{
if (textBox.Text.Length > 0)
{
if (textBox.SelectionStart >= 0 && textBox.SelectionStart <= textBox.Text.Length)
{
Rect rectLast;
int lastIndex = textBox.SelectionStart + textBox.SelectionLength;
if (lastIndex > 0)
{
if (textBox.Text[lastIndex - 1] == 13) // Special treatment for return characters
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex - 1, false);
Canvas.SetLeft(caretCoverRectangle, 0);
Canvas.SetTop(caretCoverRectangle, rectLast.Y + 19);
Canvas.SetLeft(caretRectangle, 0);
Canvas.SetTop(caretRectangle, rectLast.Y + 19);
}
else
{
if (lastIndex == textBox.Text.Length)
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex - 1, true);
}
else
{
rectLast = textBox.GetRectFromCharacterIndex(lastIndex, false);
}
// Set the caret rectangle's position
Canvas.SetLeft(caretCoverRectangle, rectLast.X);
Canvas.SetTop(caretCoverRectangle, rectLast.Y);
Canvas.SetLeft(caretRectangle, rectLast.X);
Canvas.SetTop(caretRectangle, rectLast.Y);
}
}
else
{
// Set the caret rectangle's position
Canvas.SetLeft(caretCoverRectangle, 0);
Canvas.SetTop(caretCoverRectangle, 0);
Canvas.SetLeft(caretRectangle, 0);
Canvas.SetTop(caretRectangle, 0);
// Show the caret
caretRectangle.Visibility = Visibility.Visible;
}
caretRectangle.Visibility = Visibility.Visible;
}
else
{
// Hide the caret if the index is invalid
caretRectangle.Visibility = Visibility.Collapsed;
}
}
else
{
// Set the caret rectangle's position
Canvas.SetLeft(caretCoverRectangle, 0);
Canvas.SetTop(caretCoverRectangle, 0);
Canvas.SetLeft(caretRectangle, 0);
Canvas.SetTop(caretRectangle, 0);
// Show the caret
caretRectangle.Visibility = Visibility.Visible;
}
}
private void UpdateElementSize()
{
if (textBox != null)
{
textBox.FontSize = FontSize;
}
if (caretRectangle != null && caretCoverRectangle != null)
{
caretRectangle.Height = 1.35 * FontSize;
caretCoverRectangle.Height = 1.38 * FontSize;
}
}
}
}
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CustomCaret">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="TextBoxDefaultBackground" Color="#202020"/>
<SolidColorBrush x:Key="TextBoxPointerOverBackground" Color="#303030"/>
<SolidColorBrush x:Key="TextBoxEditingBackground" Color="#252525"/>
<SolidColorBrush x:Key="TextBoxDisabledBackground" Color="#101010"/>
<SolidColorBrush x:Key="TextBoxEnabledForeground" Color="#FEFEFE"/>
<SolidColorBrush x:Key="textBoxDisabledForeground" Color="#AEAEAE"/>
<SolidColorBrush x:Key="TextBoxNotOnFocusInnerBorderColor" Color="Transparent"/>
<SolidColorBrush x:Key="TextBoxNotOnFocusOuterBorderBackground" Color="Transparent"/>
<SolidColorBrush x:Key="TextBoxOnFocusInnerBorderBrush" Color="#FFAAAAAA"/>
<SolidColorBrush x:Key="TextBoxOnFocusOuterBorderBrush" Color="Transparent"/>
<SolidColorBrush x:Key="TextBoxCaretBrush" Color="#FFFFFF"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="TextBoxDefaultBackground" Color="#202020"/>
<SolidColorBrush x:Key="TextBoxPointerOverBackground" Color="#303030"/>
<SolidColorBrush x:Key="TextBoxEditingBackground" Color="#252525"/>
<SolidColorBrush x:Key="TextBoxDisabledBackground" Color="#101010"/>
<SolidColorBrush x:Key="TextBoxEnabledForeground" Color="#FEFEFE"/>
<SolidColorBrush x:Key="textBoxDisabledForeground" Color="#AEAEAE"/>
<SolidColorBrush x:Key="TextBoxNotOnFocusInnerBorderColor" Color="Transparent"/>
<SolidColorBrush x:Key="TextBoxNotOnFocusOuterBorderBackground" Color="Transparent"/>
<SolidColorBrush x:Key="TextBoxOnFocusInnerBorderBrush" Color="#FFAAAAAA"/>
<SolidColorBrush x:Key="TextBoxOnFocusOuterBorderBrush" Color="Transparent"/>
<SolidColorBrush x:Key="TextBoxCaretBrush" Color="#FFFFFF"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="TextBoxDefaultBackground" Color="#F5F5F5"/>
<SolidColorBrush x:Key="TextBoxPointerOverBackground" Color="#EAEAEA"/>
<SolidColorBrush x:Key="TextBoxEditingBackground" Color="#F0F0F0"/>
<SolidColorBrush x:Key="TextBoxDisabledBackground" Color="#E0E0E0"/>
<SolidColorBrush x:Key="TextBoxEnabledForeground" Color="#000000"/>
<SolidColorBrush x:Key="textBoxDisabledForeground" Color="#2E2E2E"/>
<SolidColorBrush x:Key="TextBoxNotOnFocusInnerBorderColor" Color="Transparent"/>
<SolidColorBrush x:Key="TextBoxNotOnFocusOuterBorderBackground" Color="Transparent"/>
<SolidColorBrush x:Key="TextBoxOnFocusInnerBorderBrush" Color="#FF3B3B3B"/>
<SolidColorBrush x:Key="TextBoxOnFocusOuterBorderBrush" Color="Transparent"/>
<SolidColorBrush x:Key="TextBoxCaretBrush" Color="#000000"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Style TargetType="local:CustomCaretTextBox">
<Setter Property="Background" Value="{ThemeResource TextBoxDefaultBackground}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomCaretTextBox">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Canvas>
<Canvas.Resources>
<Storyboard x:Name="CaretRectangleStoryboard" BeginTime="00:00:00"
RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames
Duration="0:0:1"
Storyboard.TargetName="CaretRectangle"
Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame KeyTime="0:0:0.7" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.99" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<QuarticEase EasingMode="EaseOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<DiscreteDoubleKeyFrame KeyTime="0:0:1" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Canvas.Resources>
<TextBox x:Name="MyTextBox"
FontSize="{Binding FontSize,RelativeSource={RelativeSource TemplatedParent}}"
TextChanged="MyTextBox_TextChanged"
GotFocus="MyTextBox_GotFocus"
LostFocus="MyTextBox_LostFocus"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
CornerRadius="4"
AcceptsReturn="True"
Background="#202020"/>
<Rectangle x:Name="CaretCoverRectangle"
IsHitTestVisible="False"
Fill="White"
Height="21"
Width="2"
Visibility="Collapsed"
VerticalAlignment="Center"
Margin="11,4,0,0"
RadiusX="2"
RadiusY="2"/>
<Rectangle x:Name="CaretRectangle"
IsHitTestVisible="False"
Fill="Black"
Width="3"
Height="16"
Visibility="Collapsed"
VerticalAlignment="Center"
Margin="12,8,0,0"
RadiusX="2"
RadiusY="2"/>
</Canvas>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Please help me solve these problems
Thanks a lot.
Plus, since Canvas.SetTop(caretCoverRectangle, rectLast.Y + 19); is using a constant value, it won't fit when the FontSize is not the defalut value.
This problem can be solved by collecting multiple fontsize values(X) to find the appropriate offset value(Y), and then using the XY function to express it. like Y=aX+b, use functions to express the change curve of fontsize and offset.
When the caret is at the end of the string, if we input spaces, the TextBox won't resize to contain them in. This problem dosen't appear in original TextBox.
As for the space problem, we can only add another check to handle the situation where both Space and Carriage return characters appear at the same time.
I have no idea about other problems, because we cannot directly get the cursor object or its position in UWP. This will be a difficult project.
Thanks Junjie Zhu,
I'll have a try to fix that!