Пользовательский WPF TextBox с подключенным модулем TextBlock
Мое приложение имеет тонны TextBox
контролирует, где пользователь может вводить числовые значения. Большинство из этих значений находятся в какой-то физической единице. Этот индикатор блока отображается справа от TextBox
контроль.
Это выглядит как следующий эскиз: [________] км (где единица "км")
В настоящее время я сделал это с 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 ответ
Раздражающим расширением шаблонного элемента управления является то, что вам обычно нужно определить новый шаблон для каждой системной темы или для вашей TextBox
будет выглядеть неуместно рядом с обычным TextBox
, Однако, поскольку ваше "улучшение" довольно простое, мы можем полностью избежать этого, просто переопределив макет и код рендеринга, чтобы включить текст модуля:
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
и текст блока.
Никакой пользовательский стиль или шаблон не требуется.