I have created a custom control, which inherits TextBox. It basically has an extra property 'Seconds' and set a binding on 'Text', to shown the 'Seconds' formatted, as eg. 2m 5s, using a converter.
I now want to default right-align the text. From other custom controls I know we sometimes will want to set/override values using styles. If I set the value directly in the constructor I will not be able to do this.
I would usually something like this:
TextAlignmentProperty.OverrideMetadata(typeof(DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));
But this does NOT appear to work:
First two have the default alignment, then they are Left, Center and Right aligned directly on the controls. Seconds row has a style setting alignment to Center
I have bound a TextBlock to the TextAlignment of the first DurationTextBox, and this states that the aligment is 'Right', but this is not how it is shown!
Can anyone explain:
A. Why this is not working?
B. How to do this correctly, or something with the same end effect? (default aligned Right, but possible to override from Style)
C# class :
Please note that this is a simplified version. The complete one has Min, Max, option of confirming value changed and option for out of range action, which is the reason for the structure of the class. Please keep focus on the TextAlignment issue! (The SecondsToDurationStringConverter and DurationStringValidator can be removed to make the example compile with the same effect)
public class DurationTextBox : TextBox
{
#region Dependency properties
/// <summary>
/// Property for <see cref="Seconds"/>
/// </summary>
[NotNull] public static readonly DependencyProperty SecondsProperty = DependencyProperty.Register(nameof(Seconds), typeof(double), typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(default(double), SecondsChangedCallback) { BindsTwoWayByDefault = true });
/// <summary>
/// Seconds to show as duration string
/// </summary>
public double Seconds
{
// ReSharper disable once PossibleNullReferenceException
get { return (double)GetValue(SecondsProperty); }
set { SetValue(SecondsProperty, value); }
}
/// <summary>
/// Property for <see cref="EditValue"/>
/// </summary>
[NotNull] public static readonly DependencyProperty EditValueProperty = DependencyProperty.Register(nameof(EditValue), typeof(double), typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(default(double), EditValueChangedCallback) { BindsTwoWayByDefault = true });
/// <summary>
/// Number being edited by the actual text box. Transferred to <see cref="Seconds"/>.
/// <para>Do NOT bind to this property from outside this control</para>
/// </summary>
public double EditValue
{
// ReSharper disable once PossibleNullReferenceException
get { return (double)GetValue(EditValueProperty); }
set { SetValue(EditValueProperty, value); }
}
#endregion Dependency properties
private bool _isLocked;
static DurationTextBox()
{
// TextAlignment
TextAlignmentProperty.OverrideMetadata(typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));
}
/// <inheritdoc />
public DurationTextBox()
{
SecondsToDurationStringConverter secondsToDurationStringConverter = new SecondsToDurationStringConverter();
// Text
Binding binding = new Binding(nameof(EditValue)) { Source = this, Converter = secondsToDurationStringConverter, NotifyOnValidationError = true };
binding.ValidationRules.Add(new DurationStringValidation());
SetBinding(TextProperty, binding);
}
private static void SecondsChangedCallback([CanBeNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Demo.DurationTextBox durationTextBox = d as Demo.DurationTextBox;
if (durationTextBox == null) return;
if (!durationTextBox._isLocked)
{
durationTextBox._isLocked = true;
durationTextBox.SetCurrentValue(EditValueProperty, durationTextBox.Seconds);
durationTextBox._isLocked = false;
}
}
private static void EditValueChangedCallback([CanBeNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Demo.DurationTextBox durationTextBox = d as Demo.DurationTextBox;
if (durationTextBox == null) return;
if (!durationTextBox._isLocked)
{
durationTextBox._isLocked = true;
durationTextBox.SetCurrentValue(SecondsProperty, durationTextBox.EditValue);
durationTextBox._isLocked = false;
}
}
}
XAML code:
<Label Content="Demo.DurationTextBox" FontWeight="Bold"/>
<WrapPanel>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" x:Name="DemoDurationTextBox"/>
<TextBlock Text="{Binding ElementName=DemoDurationTextBox, Path=TextAlignment}"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Left"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Center"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Right"/>
</WrapPanel>
<WrapPanel>
<WrapPanel.Resources>
<Style TargetType="demo:DurationTextBox">
<Setter Property="TextAlignment" Value="Center"/>
</Style>
</WrapPanel.Resources>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Left" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Center" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Right" />
</WrapPanel>
