I am creating a Custom Control to create a particular TextBox style for my application:

Here is the resource dictionary which I have created to get the expected layout:
<Style TargetType="{x:Type ctrl:CustomTextBox}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource TextBox_Border_Empty}" />
<Setter Property="Foreground" Value="{DynamicResource TextBox_Label_Empty}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ctrl:CustomTextBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
Focusable="False"
CornerRadius="4"
Cursor="IBeam"
Margin="16,8"
MinHeight="{DynamicResource TextBox_Height}"
Padding="20,10,3,10"
x:Name="bdrBorder">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Background="{TemplateBinding Background}"
Grid.Column="0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
VerticalAlignment="Center">
<Label Background="{TemplateBinding Background}"
Content="{TemplateBinding Label}"
Focusable="False"
FontFamily="{DynamicResource Inter600}"
FontSize="12"
Foreground="{TemplateBinding Foreground}"
IsHitTestVisible="False"
Margin="3,0,0,0"
Padding="0,3"
Visibility="{TemplateBinding LabelVisibility}"
x:Name="lblHeading"/>
<TextBox Background="Transparent"
BorderBrush="{TemplateBinding Background}"
BorderThickness="0"
Cursor="IBeam"
Focusable="True"
FontFamily="{DynamicResource Inter400}"
FontSize="16"
Foreground="{DynamicResource Theme_Foreground}"
Margin="0"
MaxLength="{TemplateBinding MaxLength}"
Text="{TemplateBinding Text}"
TextWrapping="Wrap"
x:Name="txtContent" />
</StackPanel>
<ContentControl Focusable="False"
Grid.Column="1"
HorizontalAlignment="Center"
Margin="5,0"
Style="{DynamicResource ErrorIcon_Canvas}"
ToolTip="{TemplateBinding ValidationMessage}"
VerticalAlignment="Center"
x:Name="cvsError" />
<Button Command="{TemplateBinding ButtonCommand}"
Content="{TemplateBinding ButtonLabel}"
Cursor="Hand"
Grid.Column="2"
IsEnabled="True"
Margin="5,0"
Padding="10,7"
Style="{DynamicResource TextBoxWithBorder_Button}"
VerticalAlignment="Center"
Visibility="{TemplateBinding ButtonVisibility}"
x:Name="btnAction"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="HasBorder" Value="False">
<Setter TargetName="bdrBorder" Property="BorderBrush" Value="Transparent" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="HasBorder" Value="True"/>
<Condition Property="IsValid" Value="False" />
</MultiTrigger.Conditions>
<Setter TargetName="bdrBorder" Property="BorderBrush" Value="{DynamicResource Theme_ErrorBrush}" />
</MultiTrigger>
<Trigger Property="IsValid" Value="False">
<Setter TargetName="cvsError" Property="Visibility" Value="Visible" />
<Setter TargetName="lblHeading" Property="Foreground" Value="{DynamicResource Theme_ErrorBrush}" />
</Trigger>
<Trigger Property="IsValid" Value="True">
<Setter TargetName="cvsError" Property="Visibility" Value="Collapsed" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="ShowText" Value="False" />
<Condition Property="IsMouseOver" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="lblHeading" Property="FontSize" Value="12" />
<Setter TargetName="txtContent" Property="Visibility" Value="Visible" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="ShowText" Value="False" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
<Setter TargetName="lblHeading" Property="FontSize" Value="14" />
<Setter TargetName="txtContent" Property="Visibility" Value="Collapsed" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
Here is the code for the CustomTextBox (c#):
public class CustomTextBox : ContentControl
{
#region - Border -
public static readonly DependencyProperty HasBorderProperty =
DependencyProperty.Register(nameof(HasBorder), typeof(bool), typeof(CustomTextBox),
new PropertyMetadata(true, OnHasBorderChanged));
public bool HasBorder
{
get => (bool)GetValue(HasBorderProperty);
set => SetValue(HasBorderProperty, value);
}
private static void OnHasBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
ctrl.HasBorder = (bool)e.NewValue;
}
#endregion - Border -
#region - Button -
public static readonly DependencyProperty ButtonCommandProperty =
DependencyProperty.Register(nameof(ButtonCommand), typeof(ICommand), typeof(CustomTextBox),
new PropertyMetadata(null!, OnButtonCommandChanged));
public ICommand ButtonCommand
{
get => (ICommand)GetValue(ButtonCommandProperty);
set => SetValue(ButtonCommandProperty, value);
}
private static void OnButtonCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
ctrl.ButtonCommand = (ICommand)e.NewValue;
}
public static readonly DependencyProperty ButtonLabelProperty =
DependencyProperty.Register(nameof(ButtonLabel), typeof(string), typeof(CustomTextBox),
new PropertyMetadata(string.Empty, OnButtonLabelChanged));
public string ButtonLabel
{
get => (string)GetValue(ButtonLabelProperty);
set => SetValue(ButtonLabelProperty, value);
}
private static void OnButtonLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
{
ctrl.ButtonLabel = (string)e.NewValue;
ctrl.SetButtonVisibility();
}
}
public static readonly DependencyProperty ButtonVisibilityProperty =
DependencyProperty.Register(nameof(ButtonVisibility), typeof(Visibility), typeof(CustomTextBox),
new PropertyMetadata(Visibility.Collapsed, OnButtonVisibilityChanged));
public Visibility ButtonVisibility
{
get => (Visibility)GetValue(ButtonVisibilityProperty);
private set => SetValue(ButtonVisibilityProperty, value);
}
private static void OnButtonVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
ctrl.ButtonVisibility = (Visibility)e.NewValue;
}
private void SetButtonVisibility() => ButtonVisibility = string.IsNullOrWhiteSpace(ButtonLabel) ? Visibility.Collapsed : Visibility.Visible;
#endregion - Button -
#region - Label -
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register(nameof(Label), typeof(string), typeof(CustomTextBox),
new PropertyMetadata(string.Empty, OnLabelChanged));
public string Label
{
get => (string)GetValue(LabelProperty);
set => SetValue(LabelProperty, value);
}
private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
{
ctrl.Label = (string)e.NewValue;
ctrl.SetLabelVisibility();
}
}
public static readonly DependencyProperty LabelVisibilityProperty =
DependencyProperty.Register(nameof(LabelVisibility), typeof(Visibility), typeof(CustomTextBox),
new PropertyMetadata(Visibility.Collapsed, OnLabelVisibilityChanged));
public Visibility LabelVisibility
{
get => (Visibility)GetValue(LabelVisibilityProperty);
private set => SetValue(LabelVisibilityProperty, value);
}
private static void OnLabelVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
ctrl.LabelVisibility = (Visibility)e.NewValue;
}
private void SetLabelVisibility() => LabelVisibility = string.IsNullOrWhiteSpace(Label) ? Visibility.Collapsed : Visibility.Visible;
#endregion
#region - Text -
public static readonly DependencyProperty MaxLengthProperty =
DependencyProperty.Register(nameof(MaxLength), typeof(int), typeof(CustomTextBox),
new PropertyMetadata(int.MaxValue, OnMaxLengthChanged));
public int MaxLength
{
get => (int)GetValue(MaxLengthProperty);
set => SetValue(MaxLengthProperty, value);
}
private static void OnMaxLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
ctrl.MaxLength = (int)e.NewValue;
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text), typeof(string), typeof(CustomTextBox),
new PropertyMetadata(string.Empty, OnTextChanged));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
{
ctrl.Text = (string)e.NewValue;
ctrl.SetShowText();
}
}
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register(nameof(ShowText), typeof(bool), typeof(CustomTextBox),
new PropertyMetadata(false, OnShowTextChanged));
public bool ShowText
{
get => (bool)(GetValue(ShowTextProperty));
private set => SetValue(ShowTextProperty, value);
}
private static void OnShowTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
ctrl.ShowText = (bool)e.NewValue;
}
private void SetShowText() => ShowText = !string.IsNullOrWhiteSpace(Text);
#endregion
#region - Validation -
public static readonly DependencyProperty IsValidProperty =
DependencyProperty.Register(nameof(IsValid), typeof(bool), typeof(CustomTextBox),
new PropertyMetadata(true, OnIsValidChanged));
public bool IsValid
{
get => (bool)GetValue(IsValidProperty);
set => SetValue(IsValidProperty, value);
}
private static void OnIsValidChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
ctrl.IsValid = (bool)e.NewValue;
}
public static readonly DependencyProperty ValidationMessageProperty =
DependencyProperty.Register(nameof(ValidationMessage), typeof(string), typeof(CustomTextBox),
new PropertyMetadata(string.Empty, OnValidationMessageChanged));
public string ValidationMessage
{
get => (string)(GetValue(ValidationMessageProperty));
set => SetValue(ValidationMessageProperty, value);
}
private static void OnValidationMessageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomTextBox ctrl)
{
ctrl.ValidationMessage = (string)e.NewValue;
ctrl.SetIsValid();
}
}
private void SetIsValid() => IsValid = string.IsNullOrWhiteSpace(ValidationMessage);
#endregion - Validation -
}
I have a couple of issues with this code and I am looking for help resolving:
When I mouse over the TextBox (txtContent) I do not see the IBeam, instead the mouse is set to the default Windows Arrow.
My CustomTextBox I have had to make it implement a ContentControl rather than a TextBox, this is because I need to fire an event when Text is changed, but using a TextBox it seems that event will not fire, if anyone can give me advice on that I would be very happy too. The reason for this is when the TextBox.Text is empty, I want to hide the Texbox element (unless the control has focus or the mouse is over)
When I click anywhere within the Border (bdrBorder) I want to set focus to the TextBox (txtContent) but the only way I can set focus is to click on the actual TextBox, is there a way to achieve this?
I have tried a number of different approaches, but am unable to achieve these things