首页 > 解决方案 > WPF:创建从 TextBox 派生的“DoubleBox”

问题描述

我希望有一个类似于 aTextBox但具有附加功能的 WPF 控件,以确保仅接受特定范围内的数字。我想出了以下DoubleBox控件:

public class DoubleBox : TextBox
{
    public static readonly DependencyProperty MinProperty = DependencyProperty.Register(nameof(Min), typeof(double), typeof(DoubleBox), new PropertyMetadata(double.NegativeInfinity, OnMinPropertyChanged));
    public static readonly DependencyProperty MaxProperty = DependencyProperty.Register(nameof(Max), typeof(double), typeof(DoubleBox), new PropertyMetadata(double.PositiveInfinity, OnMaxPropertyChanged));
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(double), typeof(DoubleBox), new PropertyMetadata(default(double), OnValuePropertyChanged));

    private static void OnMinPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        double min = (double)e.NewValue;
        double max = (double)d.GetValue(MaxProperty);
        double value = (double)d.GetValue(ValueProperty);

        if (min > max) throw new Exception("Min value is greater than max value.");

        if (value < min) d.SetValue(ValueProperty, min);
    }
    private static void OnMaxPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        double min = (double)d.GetValue(MinProperty);
        double max = (double)e.NewValue;
        double value = (double)d.GetValue(ValueProperty);

        if (max < min) throw new Exception("Max value is less than min value.");

        if (value > max) d.SetValue(ValueProperty, max);
    }
    private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        double min = (double)d.GetValue(MinProperty);
        double max = (double)d.GetValue(MaxProperty);
        double value = (double)e.NewValue;

        if (value < min) d.SetValue(ValueProperty, min);
        if (value > max) d.SetValue(ValueProperty, max);
    }

    public double Min
    {
        get => (double)GetValue(MinProperty);
        set => SetValue(MinProperty, value);
    }
    public double Max
    {
        get => (double)GetValue(MaxProperty);
        set => SetValue(MaxProperty, value);
    }
    public double Value
    {
        get => (double)GetValue(ValueProperty);
        set
        {
            SetValue(ValueProperty, value);
            Text = Value.ToString();
        }
    }

    private double previousValue = 0.0;

    //protected override void OnInitialized(EventArgs e)
    //{
    //  Text = Value.ToString();
    //  base.OnInitialized(e);
    //}
    protected override void OnGotFocus(RoutedEventArgs e)
    {
        double.TryParse(Text, out previousValue);
        base.OnGotFocus(e);
    }
    protected override void OnLostFocus(RoutedEventArgs e)
    {
        if (double.TryParse(Text, out double currentValue))
        {
            if (currentValue < Min) currentValue = Min;
            if (currentValue > Max) currentValue = Max;
            Value = currentValue;
        }
        else Value = previousValue;

        base.OnLostFocus(e);
    }

}

可以像这样在视图中使用:

<DoubleBox Min="{Binding Path=DoubleWithValueOf1, Mode=TwoWay}" Value="{Binding Path=DoubleWithValueOf5, Mode=TwoWay}" />

我的第一个问题是:由于我不习惯 MVVM,这种整体方法是否正确?似乎我为这样一个简单的事情编写了太多代码。

我的主要问题是:即使我绑定Value了. 我可以通过覆盖该方法来“修复”这个问题(如上所述),但这似乎不正确,我无法在 Visual Studio 的 xaml 设计器中查看实时绑定。DoubleBoxTextnullValuestringOnInitialized

标签: c#wpfxaml

解决方案


您的这部分实现是错误的:

public double Value
{
    get => (double)GetValue(ValueProperty);
    set
    {
        SetValue(ValueProperty, value);
        Text = Value.ToString(); // here
    }
}

因为在依赖属性的 CLR 包装器中除了 SetValue 之外不能有其他任何东西。设置属性时并不总是调用设置器,例如通过绑定。WPF 可能会直接调用 SetValue。

Text属性分配移动到 PropertyChanged 回调,并添加 CoerceValueCallback,而不是在 PropertyChanged 回调中执行范围检查。

public double Value
{
    get => (double)GetValue(ValueProperty);
    set => SetValue(ValueProperty, value);
}

public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        nameof(Value),
        typeof(double),
        typeof(DoubleBox),
        new FrameworkPropertyMetadata(
            OnValuePropertyChanged, CoerceValueProperty));

private static object CoerceValueProperty(DependencyObject d, object value)
{
    var db = (DoubleBox)d;
    var val = (double)value;
    return val < db.Min ? db.Min : val > db.Max ? db.Max : val;
}

private static void OnValuePropertyChanged(
    DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var db = (DoubleBox)d;
    db.Text = db.Value.ToString();
}

推荐阅读