【问题标题】:WPF TextAlignmentProperty.OverrideMetadata not working in inheritor of TextBoxWPF TextAlignmentProperty.OverrideMetadata 在 TextBox 的继承者中不起作用
【发布时间】:2020-12-23 13:35:33
【问题描述】:

我创建了一个自定义控件,它继承了 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


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 控件不会这样使用。 XAML 来自我的“演示控件”,这是我测试控件的地方,而不是我将使用它们的地方。出于同样的原因,您的“B”解决方案可能并不总是可用,因为其他文本框可能需要左对齐,而我们仍然希望时间输入右对齐。我希望这是有道理的......
    • 这不是“我的”解决方案,而是 WPF 的工作方式。如果您希望某些 TextBox 具有左对齐,那么您必须在相关的 TextBox CLR 对象上指定它。你可以以任何你想要的方式做到这一点,但它需要完成。
    【解决方案2】:

    虽然这不是我想要的解决方案,但我找到了一种方法,可以让我的控件默认对齐到 Right,同时能够通过使用样式在本地覆盖它,或者直接在控件上覆盖它, 不影响其他文本框

    我做了一个默认样式:

    <Style TargetType="controls:DurationTextBox">
        <Setter Property="TextAlignment" Value="Right"/>
    </Style>
    

    因为我有其他资源字典必须包含在内,这将适用于我的情况。

    我宁愿简单地设置我的控件的默认值,但根据 Artfunkel 的说法,这很遗憾是不可能的。

    我选择使用默认样式的方法的问题是,如果在项目中为 TextBox 设置了不同的默认样式,则 DurationTextBox 将不会使用/继承此样式,因为它有自己的默认样式,因此有人使用库还必须设置类似的样式/覆盖 DurationTextBox 的样式,以使它们看起来不一样。

    如果 DurationTextBox 不需要包含文本对齐的不同默认样式,则可以让默认样式与 TextBox 相同,但现在不可能。

    由于我们为 TextBox 设置了不同的默认样式,因此我在默认样式中添加了 BasedOn:

    <Style TargetType="controls:DurationTextBox" BasedOn="{StaticResource {x:Type TextBox}}">
        <Setter Property="TextAlignment" Value="Right"/>
    </Style>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-29
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      • 2018-05-24
      • 2015-03-04
      • 2012-06-04
      相关资源
      最近更新 更多