Итак, суть проблемы была тривиальной (см. Принятый ответ): мне нужно было позвонить DependencyProperty.RegisterAttached(...)
(в отличие от DependencyProperty.Register(...)
.
Просто хотел поделиться результатом. Я решил отказаться от использования простого enum
для указания типа ввода и решил вместо этого использовать расширения разметки.
Реализация присоединенного свойства теперь выглядит следующим образом:
public static class IsValid
{
public static readonly DependencyProperty InputProperty = DependencyProperty.RegisterAttached(
"Input",
typeof(IsValidInputExtension),
typeof(IsValid),
new UIPropertyMetadata(onInput));
public static IsValidInputExtension GetInput(DependencyObject d)
{
return (IsValidInputExtension)d.GetValue(InputProperty);
}
public static void SetInput(DependencyObject d, IsValidInputExtension value)
{
d.SetValue(InputProperty, value);
}
private static void onInput(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = (TextBox)d;
var value = (IsValidInputExtension)e.NewValue;
if (value == null)
{
textBox.PreviewTextInput -= validateInput;
textBox.PreviewKeyDown -= validateKeyDown;
return;
}
textBox.PreviewTextInput += validateInput;
textBox.PreviewKeyDown += validateKeyDown;
}
private static void validateInput(object sender, TextCompositionEventArgs e)
{
// dispatch validation to specified markup class ...
var textBox = (TextBox) sender;
var markup = (IsValidInputExtension)textBox.GetValue(InputProperty);
markup.ValidateInput(sender, e);
}
private static void validateKeyDown(object sender, KeyEventArgs e)
{
// dispatch validation to specified markup class ...
var textBox = (TextBox)sender;
var markup = (IsValidInputExtension)textBox.GetValue(InputProperty);
markup.ValidateKeyDown(sender, e);
}
}
А вот часть классов расширения разметки:
public abstract class IsValidInputExtension : MarkupExtension
{
internal abstract void ValidateInput(object sender, TextCompositionEventArgs e);
internal abstract void ValidateKeyDown(object sender, KeyEventArgs e);
}
public class NumericExtension : IsValidInputExtension
{
public double Minimum { get; set; }
public double Maximum { get; set; }
public uint Decimals { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
internal override void ValidateInput(object sender, TextCompositionEventArgs e)
{
var textBox = (TextBox) sender;
if (isDecimalSeparator(e.Text) && Decimals == 0)
{
e.Handled = true;
return;
}
// todo: honor Minimum and Maximum ...
}
private static bool isDecimalSeparator(string s)
{
return CultureInfo.CurrentUICulture.NumberFormat.CurrencyDecimalSeparator == s;
}
internal override void ValidateKeyDown(object sender, KeyEventArgs e)
{
// block [SPACE] when numeric input is expected ...
e.Handled = e.Key == Key.Space;
}
}
public class StringExtension : IsValidInputExtension
{
public double MaximumLength { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
internal override void ValidateInput(object sender, TextCompositionEventArgs e)
{
// (nop)
}
internal override void ValidateKeyDown(object sender, KeyEventArgs e)
{
// todo: honor MaximumLength here
}
}
Конечный результат в XAML довольно приятный и легко читаемый ...
<TextBox v:IsValid.Input="{v:Numeric Minimum=0, Maximum=99, Decimals=0}" />
Кажется, все работает, как я надеялся. Спасибо за все комментарии
Приветствия
/ Jonas