带有附加单元TextBlock的自定义WPF TextBox

时间:2014-11-19 14:01:23

标签: wpf xaml textbox

我的应用程序有大量的TextBox控件,用户可以在其中输入数值。这些值中的大多数都在某个物理单元中。此单位指示符显示在TextBox控件的右侧。

如下图所示:[________] km(单位为“km”)

目前,我已经在StackPanel个实例中完成了此操作。它始终是相同的模式。这使得XAML的可读性低于应有的可读性。

我正在寻找一个TextBox控件,其中已包含TextBlock侧显示该单元。

我的第一次尝试是一个派生自TextBox的类,其中一个XAML文件替换了Template属性,如下所示:

<TextBox
    x:Class="WpfApplication1.UnitTextBox"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="_this"
    KeyboardNavigation.IsTabStop="False"
    Style="{StaticResource {x:Type TextBox}}">
    <TextBox.Template>
        <ControlTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <TextBox
                    Foreground="{TemplateBinding Foreground}"
                    IsEnabled="{TemplateBinding IsEnabled}"
                    IsReadOnly="{Binding IsReadOnly, ElementName=_this}"
                    Style="{TemplateBinding Style}"
                    Text="{Binding Text, ElementName=_this}"
                    Width="{TemplateBinding Width}"
                    ... lots more ...
                    VerticalAlignment="Center"/>
                <TextBlock
                    Grid.Column="1"
                    Text="{Binding Unit, ElementName=_this}"
                    Margin="4,0,0,0"
                    VerticalAlignment="Center"/>
            </Grid>
        </ControlTemplate>
    </TextBox.Template>
</TextBox>

Unit是我的UnitTextBox代码隐藏类中的依赖项属性:

public partial class UnitTextBox : TextBox
{
    public static DependencyProperty UnitProperty = DependencyProperty.Register(
        name: "Unit",
        propertyType: typeof(string),
        ownerType: typeof(UnitTextBox));

    public string Unit
    {
        get { return (string) GetValue(UnitProperty); }
        set { SetValue(UnitProperty, value); }
    }

    public UnitTextBox()
    {
        InitializeComponent();
    }
}

不幸的是,这种方法存在许多问题。我需要将几乎所有属性传递给内部TextBox,如您所见(我在此处缩写)。另外,我希望Width属性像往常一样应用于内部TextBox,而不是外部Grid。我认为我需要一个单独的属性并将内部TextBox实例绑定到该属性。目前,我忽略了使用UnitTextBox类时设置的样式。我甚至不知道如何解决这个问题。

是否有可能使用WPF创建这样的组合控件?它应该像TextBox一样具有所有事件处理程序,可绑定属性等,但已经在其外观中包含该单元字符串,可由其他属性分配。

我是否可以使用自定义Style在某处添加TextBlock(但我认为我需要外部Grid来对齐事物),并声明具有附加属性的单位?

1 个答案:

答案 0 :(得分:1)

扩展模板化控件的烦人之处在于,您通常需要为每个系统主题定义新模板,否则您的自定义TextBox将在常规TextBox旁边看起来不合适。但是,因为你的&#34;增强&#34;相当简单,我们完全可以通过简单地覆盖布局和渲染代码来包含单元文本来避免这种情况:

public class UnitTextBox : TextBox
{
    private FormattedText _unitText;
    private Rect _unitTextBounds;

    public static DependencyProperty UnitTextProperty =
        DependencyProperty.Register(
            name: "UnitText",
            propertyType: typeof(string),
            ownerType: typeof(UnitTextBox),
            typeMetadata: new FrameworkPropertyMetadata(
                default(string),
                FrameworkPropertyMetadataOptions.AffectsMeasure |
                FrameworkPropertyMetadataOptions.AffectsArrange |
                FrameworkPropertyMetadataOptions.AffectsRender));

    public string UnitText
    {
        get { return (string)GetValue(UnitTextProperty); }
        set { SetValue(UnitTextProperty, value); }
    }

    public static DependencyProperty UnitPaddingProperty =
        DependencyProperty.Register(
            name: "UnitPadding",
            propertyType: typeof(Thickness),
            ownerType: typeof(UnitTextBox),
            typeMetadata: new FrameworkPropertyMetadata(
                new Thickness(5d, 0d, 0d, 0d),
                FrameworkPropertyMetadataOptions.AffectsMeasure |
                FrameworkPropertyMetadataOptions.AffectsArrange |
                FrameworkPropertyMetadataOptions.AffectsRender));

    public Thickness UnitPadding
    {
        get { return (Thickness)GetValue(UnitPaddingProperty); }
        set { SetValue(UnitPaddingProperty, value); }
    }

    public static DependencyProperty TextBoxWidthProperty =
        DependencyProperty.Register(
            name: "TextBoxWidth",
            propertyType: typeof(double),
            ownerType: typeof(UnitTextBox),
            typeMetadata: new FrameworkPropertyMetadata(
                double.NaN,
                FrameworkPropertyMetadataOptions.AffectsMeasure));

    public double TextBoxWidth
    {
        get { return (double)GetValue(TextBoxWidthProperty); }
        set { SetValue(TextBoxWidthProperty, value); }
    }

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);

        if (e.Property == ForegroundProperty)
            EnsureUnitText(invalidate: true);
    }

    protected override Size MeasureOverride(Size constraint)
    {
        var textBoxWidth = this.TextBoxWidth;
        var unit = EnsureUnitText(invalidate: true);
        var padding = this.UnitPadding;

        if (unit != null)
        {
            var unitWidth = unit.Width + padding.Left + padding.Right;
            var unitHeight = unit.Height + padding.Top + padding.Bottom;

            constraint = new Size(
                constraint.Width - unitWidth,
                Math.Max(constraint.Height, unitHeight));
        }

        var hasFixedTextBoxWidth = !double.IsNaN(textBoxWidth) &&
                                   !double.IsInfinity(textBoxWidth);

        if (hasFixedTextBoxWidth)
            constraint = new Size(textBoxWidth, constraint.Height);

        var baseSize = base.MeasureOverride(constraint);
        var baseWidth = hasFixedTextBoxWidth ? textBoxWidth : baseSize.Width;

        if (unit != null)
        {
            var unitWidth = unit.Width + padding.Left + padding.Right;
            var unitHeight = unit.Height + padding.Top + padding.Bottom;

            return new Size(
                baseWidth + unitWidth,
                Math.Max(baseSize.Height, unitHeight));
        }

        return new Size(baseWidth, baseSize.Height);
    }

    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        var textSize = arrangeBounds;
        var unit = EnsureUnitText(invalidate: false);
        var padding = this.UnitPadding;

        if (unit != null)
        {
            var unitWidth = unit.Width + padding.Left + padding.Right;
            var unitHeight = unit.Height + padding.Top + padding.Bottom;

            textSize.Width -= unitWidth;

            _unitTextBounds = new Rect(
                textSize.Width + padding.Left,
                (arrangeBounds.Height - unitHeight) / 2 + padding.Top,
                textSize.Width,
                textSize.Height);
        }

        var baseSize = base.ArrangeOverride(textSize);

        if (unit != null)
        {
            var unitWidth = unit.Width + padding.Left + padding.Right;
            var unitHeight = unit.Height + padding.Top + padding.Bottom;

            return new Size(
                baseSize.Width + unitWidth,
                Math.Max(baseSize.Height, unitHeight));
        }

        return baseSize;
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        var unitText = EnsureUnitText(invalidate: false);
        if (unitText != null)
            drawingContext.DrawText(unitText, _unitTextBounds.Location);
    }

    private FormattedText EnsureUnitText(bool invalidate = false)
    {
        if (invalidate)
            _unitText = null;

        if (_unitText != null)
            return _unitText;

        var unit = this.UnitText;

        if (!string.IsNullOrEmpty(unit))
        {
            _unitText = new FormattedText(
                unit,
                CultureInfo.InvariantCulture,
                this.FlowDirection,
                new Typeface(
                    this.FontFamily,
                    this.FontStyle,
                    this.FontWeight,
                    this.FontStretch),
                this.FontSize,
                this.Foreground);
        }

        return _unitText;
    }
}

TextBoxWidth属性可让您为TextBox设置固定宽度。 Width的行为保持不变,因为它应该:它控制整个控件的大小,例如TextBox 单位文本。

无需自定义样式或模板。