简体   繁体   English

WPF TextAlignmentProperty.OverrideMetadata 在 TextBox 的继承者中不起作用

[英]WPF TextAlignmentProperty.OverrideMetadata not working in inheritor of TextBox

I have created a custom control, which inherits TextBox.我创建了一个自定义控件,它继承了 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. 2m 5s,使用转换器。

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!我已将 TextBlock 绑定到第一个 DurationTextBox 的 TextAlignment,这表明该对齐是“正确的”,但这不是它的显示方式!

Can anyone explain:谁能解释一下:

A. Why this is not working? A. 为什么这不起作用?

B. How to do this correctly, or something with the same end effect? B. 如何正确地做到这一点,或者具有相同的最终效果? (default aligned Right, but possible to override from Style) (默认对齐右,但可以从样式覆盖)

C# class : C# 类:

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.完整的有Min,Max,确认值改变的选项和超出范围动作的选项,这是类结构的原因。 Please keep focus on the TextAlignment issue!请继续关注 TextAlignment 问题! (The SecondsToDurationStringConverter and DurationStringValidator can be removed to make the example compile with the same effect) (可以删除 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 code: 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>

A: This isn't working because TextBox.TextAlignment is inherited , so by default the active value is determined by the parent control at runtime.答:这不起作用,因为TextBox.TextAlignment 是继承的,因此默认情况下,活动值由父控件在运行时确定。 It doesn't make sense for a class to override the default value of the static DependencyProperty object itself.类重写静态DependencyProperty对象本身的默认值是没有意义的。

The fact that the debug binding you added displays "Right" is strange, and probably an internal WPF optimisation glitch.您添加的调试绑定显示“正确”这一事实很奇怪,可能是内部 WPF 优化故障。

B: The correct solution is to set TextBox.TextAlignment="Right" (note the type name qualifier) on a parent control, eg your wrap panel. B:正确的解决方案是在父控件上设置TextBox.TextAlignment="Right" (注意类型名称限定符),例如您的环绕面板。 That value will then be automatically applied to all child text blocks unless they or an intermediate parent override it further, including via a style.然后该值将自动应用于所有子文本块,除非它们或中间父级进一步覆盖它,包括通过样式。

C: I would add that the code you posted seems to be an attempt to re-invent DataGrid . C:我想补充一点,您发布的代码似乎是尝试重新发明DataGrid That control supports transactional editing out of the box and might save you a lot of time if you switch to it!该控件支持开箱即用的事务编辑,如果您切换到它,可能会为您节省大量时间!

While it is not the solution I would have preferred, I have found a way that allows me to have the default alignment of my control to Right, while being able to overwrite it locally by using a style, or directly on the control, without affecting other TextBoxes虽然这不是我想要的解决方案,但我找到了一种方法,允许我将控件的默认对齐方式设置为 Right,同时能够通过使用样式在本地或直接在控件上覆盖它,而不会影响其他文本框

I made a default style:我做了一个默认样式:

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

As I have other resource dictionaries which must be included anyway this will work in my situation.由于我有其他资源词典,无论如何都必须包含在内,这将适用于我的情况。

I would have preferred simply setting the default value of my control, but according to Artfunkel that is sadly not possible.我宁愿简单地设置我的控件的默认值,但根据 Artfunkel 的说法,遗憾的是这是不可能的。

The issue with my chosen approach of using a default style is if a different default style is set for TextBox in a project, the DurationTextBox will not use/inherit this style, because it has its own default style, thus someone using the library has to also set a similar style/override the style for the DurationTextBox for them not to appear different.我选择的使用默认样式的方法的问题是,如果在项目中为 TextBox 设置了不同的默认样式,则 DurationTextBox 将不会使用/继承此样式,因为它有自己的默认样式,因此使用该库的人必须还为 DurationTextBox 设置类似的样式/覆盖样式,以使它们看起来不不同。

If the DurationTextBox had not needed a different default style, containing the text alignment, it would have been possible to have the default style be the same as a TextBox, but this is not a possibility now.如果 DurationTextBox 不需要包含文本对齐方式的其他默认样式,则可以将默认样式与 TextBox 相同,但现在不可能了。

As we do have a different default style for TextBox I have added a BasedOn to my default style:由于我们为 TextBox 设置了不同的默认样式,因此我在默认样式中添加了一个 BasedOn:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM