首页 > 解决方案 > WPF TextAlignmentProperty.OverrideMetadata 在 TextBox 的继承者中不起作用

问题描述

我创建了一个自定义控件,它继承了 TextBox。它基本上有一个额外的属性'Seconds'并在'Text'上设置绑定,以显示'Seconds'格式化,例如。2m 5s,使用转换器。

我现在想默认右对齐文本。从其他自定义控件我知道我们有时会想要使用样式设置/覆盖值。如果我直接在构造函数中设置值,我将无法做到这一点。

我通常会这样:

TextAlignmentProperty.OverrideMetadata(typeof(DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));

但这似乎不起作用:

前两个具有默认对齐方式,然后它们在控件上直接左对齐、居中对齐和右对齐。 秒行有一个样式设置对齐到中心

前两个具有默认对齐方式,然后它们在控件上直接左对齐、居中对齐和右对齐。秒行有一个样式设置对齐到中心

我已经将一个 TextBlock 绑定到第一个 DurationTextBox 的 TextAlignment,这表明对齐是“右”,但这不是它的显示方式!

谁能解释一下:

A. 为什么这不起作用?

B.如何正确地做到这一点,或者具有相同的最终效果?(默认右对齐,但可以从样式中覆盖)

C#类:

请注意,这是一个简化版本。完整的有Min,Max,确认值更改的选项和超出范围操作的选项,这是类结构的原因。请继续关注 TextAlignment 问题!(可以去掉 SecondsToDurationStringConverter 和 DurationStringValidator 使示例编译效果相同)

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 代码:

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

标签: c#wpf

解决方案


A:这不起作用,因为TextBox.TextAlignment 是继承的,所以默认情况下,活动值由父控件在运行时确定。DependencyProperty类覆盖静态对象本身的默认值是没有意义的。

您添加的调试绑定显示“正确”的事实很奇怪,并且可能是内部 WPF 优化故障。

B:正确的解决方案是在父控件上设置TextBox.TextAlignment="Right"(注意类型名称限定符),例如您的包装面板。然后该值将自动应用于所有子文本块,除非它们或中间父级进一步覆盖它,包括通过样式。

C:我要补充一点,您发布的代码似乎是一种重新发明的尝试DataGrid。该控件支持开箱即用的事务编辑,如果您切换到它,可能会为您节省大量时间!


推荐阅读