创建功能性双重控制

Creating a functional Double Control

我有一个名为 DoubleNumericBox 的自定义控件,它验证并接受用户输入,例如 23,000,923.900,0134... 等.

当我尝试将某些东西绑定到它时,问题就开始了。绑定不够可靠,有时控件不会显示新值,但如果我再设置一次 DataContext 它会设置值,等等

所以,我的自定义属性和事件一定是做错了什么。

自定义Properties/Events

值已更改:Event

预期行为

代码

public class DoubleNumericBox : TextBox
{

变量:

    public readonly static DependencyProperty MinValueProperty;
    public readonly static DependencyProperty ValueProperty;
    public readonly static DependencyProperty MaxValueProperty;

属性:

    public double MinValue
    {
        get { return (double)GetValue(MinValueProperty); }
        set { SetCurrentValue(MinValueProperty, value); }
    }

    public double Value
    {
        get { return (double)GetValue(ValueProperty); }
        set
        {
            SetCurrentValue(ValueProperty, value);
            RaiseValueChangedEvent();
        }
    }

    public double MaxValue
    {
        get { return (double)GetValue(MaxValueProperty); }
        set { SetCurrentValue(MaxValueProperty, value); }
    }

事件:

    public static readonly RoutedEvent ValueChangedEvent;

    public event RoutedEventHandler ValueChanged
    {
        //Provide CLR accessors for the event 
        add { AddHandler(ValueChangedEvent, value); }              
        remove { RemoveHandler(ValueChangedEvent, value); }
    }

    public void RaiseValueChangedEvent()
    {
        var newEventArgs = new RoutedEventArgs(ValueChangedEvent);
        RaiseEvent(newEventArgs);
    }

Constructor/Override:

    static DoubleNumericBox()
    {
        MinValueProperty = DependencyProperty.Register("MinValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D));
        ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D, ValueCallback));
        MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(Double.MaxValue));

        ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DoubleNumericBox));
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        PreviewTextInput += DoubleNumericBox_PreviewTextInput;
        ValueChanged += DoubleNumericBox_ValueChanged;
        TextChanged += DoubleNumericBox_TextChanged;
        LostFocus += DoubleNumericBox_LostFocus;

        AddHandler(DataObject.PastingEvent, new DataObjectPastingEventHandler(PastingEvent));
    }

事件:

    private static void ValueCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var textBox = d as DoubleNumericBox;
        if (textBox == null) return;

        //textBox.Text = String.Format("{0:###,###,##0.0###}", textBox.Value);

        textBox.RaiseValueChangedEvent();
    }

    private void DoubleNumericBox_ValueChanged(object sender, RoutedEventArgs e)
    {
        var textBox = sender as DoubleNumericBox;

        if (textBox == null) return;

        ValueChanged -= DoubleNumericBox_ValueChanged;
        TextChanged -= DoubleNumericBox_TextChanged;

        if (Value > MaxValue)
            Value = MaxValue;

        else if (Value < MinValue)
            Value = MinValue;

        textBox.Text = Text = String.Format("{0:###,###,##0.0###}", Value);

        ValueChanged += DoubleNumericBox_ValueChanged;
        TextChanged += DoubleNumericBox_TextChanged;
    }

    private void DoubleNumericBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        var textBox = sender as TextBox;

        if (textBox == null) return;
        if (String.IsNullOrEmpty(textBox.Text)) return;
        if (IsTextDisallowed(textBox.Text)) return;

        ValueChanged -= DoubleNumericBox_ValueChanged;

        var newValue = Convert.ToDouble(textBox.Text);

        if (newValue > MaxValue)
            Value = MaxValue;
        else if (newValue < MinValue)
            Value = MinValue;
        else
        {
            Value = newValue;
        }

        ValueChanged += DoubleNumericBox_ValueChanged;
    }

    private void DoubleNumericBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        if (String.IsNullOrEmpty(e.Text))
        {
            e.Handled = true;
            return;
        }

        //Only Numbers, comma and points.
        if (IsEntryDisallowed(sender, e.Text))
        {
            e.Handled = true;
        }
    }

    private void PastingEvent(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(typeof(String)))
        {
            var text = (String)e.DataObject.GetData(typeof(String));

            if (IsTextDisallowed(text))
            {
                e.CancelCommand();
            }
        }
        else
        {
            e.CancelCommand();
        }
    }

    private void DoubleNumericBox_LostFocus(object sender, RoutedEventArgs e)
    {
        TextChanged -= DoubleNumericBox_TextChanged;

        Text = String.Format("{0:###,###,##0.0###}", Value);

        TextChanged += DoubleNumericBox_TextChanged;
    }

方法:

    private bool IsEntryDisallowed(object sender, string text)
    {
        var regex = new Regex(@"^[0-9]|\.|\,$");

        if (regex.IsMatch(text))
        {
            return !CheckPontuation(sender, text);
        }

        //Not a number or a Comma/Point.
        return true;
    }

    private bool IsTextDisallowed(string text)
    {
        var regex = new Regex(@"^((\d+)|(\d{1,3}(\.\d{3})+)|(\d{1,3}(\.\d{3})(\,\d{3})+))((\,\d{4})|(\,\d{3})|(\,\d{2})|(\,\d{1})|(\,))?$");
        return !regex.IsMatch(text); //\d+(?:,\d{1,2})?
    }

    private bool CheckPontuation(object sender, string next)
    {
        var textBox = sender as TextBox;

        if (textBox == null) return true;

        if (Char.IsNumber(next.ToCharArray()[0]))
            return true;

        if (next.Equals("."))
        {
            var textAux = textBox.Text;

            if (!String.IsNullOrEmpty(textBox.SelectedText))
                textAux = textAux.Replace(textBox.SelectedText, "");

            //Check if the user can add a point mark here.
            var before = textAux.Substring(0, textBox.SelectionStart);
            var after = textAux.Substring(textBox.SelectionStart);

            //If no text, return true.
            if (String.IsNullOrEmpty(before) && String.IsNullOrEmpty(after)) return true;

            if (!String.IsNullOrEmpty(before))
            {
                if (before.Contains(',')) return false;

                if (after.Contains("."))
                {
                    var split = before.Split('.');

                    if (split.Last().Length != 3) return false;
                }
            }

            if (!String.IsNullOrEmpty(after))
            {
                var split = after.Split('.', ',');

                if (split.First().Length != 3) return false;
            }

            return true;
        }

        //Only one comma.
        if (next.Equals(","))
        {
            return !textBox.Text.Any(x => x.Equals(','));
        }

        return true;
    }  
}

你们能帮我改进这个自定义控件吗?

所以我在您的代码中看到了一些陷阱: 不要使用 += / -= 来连接 WPF 控件中的事件,它可以而且会破坏路由事件,请改用 Addhandler / RemoveHandler。 我删除了事件的取消挂钩和重新挂钩,并使用成员级别标志代替更改循环问题。这是我想出的代码,似乎可以很好地绑定到值字段。

旁注,您未能说明多个“.”在您的文本框中输入,以便用户可以键入 345.34.434.23,这不会被阻止。我知道要检查这个,因为我几年前写了一个 WPF FilterTextBox,这在我的测试中出现了。

public class DoubleNumericBox : TextBox
{
    public readonly static DependencyProperty MinValueProperty;
    public readonly static DependencyProperty ValueProperty;
    public readonly static DependencyProperty MaxValueProperty;
    public bool _bIgnoreChange = false;

public double MinValue
{
    get { return (double)GetValue(MinValueProperty); }
    set { SetCurrentValue(MinValueProperty, value); }
}

public double Value
{
    get { return (double)GetValue(ValueProperty); }
    set
    {
        SetCurrentValue(ValueProperty, value);
        RaiseValueChangedEvent();
    }
}

public double MaxValue
{
    get { return (double)GetValue(MaxValueProperty); }
    set { SetCurrentValue(MaxValueProperty, value); }
}

public static readonly RoutedEvent ValueChangedEvent;

public event RoutedEventHandler ValueChanged
{
    //Provide CLR accessors for the event 
    add { AddHandler(ValueChangedEvent, value); }
    remove { RemoveHandler(ValueChangedEvent, value); }
}

public void RaiseValueChangedEvent()
{
    var newEventArgs = new RoutedEventArgs(ValueChangedEvent);
    RaiseEvent(newEventArgs);
}

static DoubleNumericBox()
{
    MinValueProperty = DependencyProperty.Register("MinValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D));
    ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D, ValueCallback));
    MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(Double.MaxValue));

    ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DoubleNumericBox));
}

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    AddHandler(TextBox.PreviewTextInputEvent, new TextCompositionEventHandler(DoubleNumericBox_PreviewTextInput));
    AddHandler(TextBox.TextChangedEvent, new  TextChangedEventHandler(DoubleNumericBox_TextChanged));
    AddHandler(TextBox.LostFocusEvent, new RoutedEventHandler(DoubleNumericBox_LostFocus));
    AddHandler(DataObject.PastingEvent, new DataObjectPastingEventHandler(PastingEvent));
}


private static void ValueCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var textBox = d as DoubleNumericBox;
    if (textBox == null) return;

    //textBox.Text = String.Format("{0:###,###,##0.0###}", textBox.Value);

    textBox.DoubleNumericBox_ValueChanged();
}

private void DoubleNumericBox_ValueChanged()
{

    if (Value > MaxValue)
        Value = MaxValue;

    else if (Value < MinValue)
        Value = MinValue;

    if (!_bIgnoreChange)
        this.Text = Text = String.Format("{0:###,###,##0.0###}", Value);
}

private void DoubleNumericBox_TextChanged(object sender, TextChangedEventArgs e)
{
    var textBox = sender as TextBox;

    if (textBox == null) return;
    if (String.IsNullOrEmpty(textBox.Text)) return;
    if (IsTextDisallowed(textBox.Text)) return;

    //ValueChanged -= DoubleNumericBox_ValueChanged;
    _bIgnoreChange = true;

    Value = Convert.ToDouble(textBox.Text);

    //if (newValue > MaxValue)
    //    Value = MaxValue;
    //else if (newValue < MinValue)
    //    Value = MinValue;
    //else
    //{
    //    Value = newValue;
    //}
    _bIgnoreChange = false;

    //ValueChanged += DoubleNumericBox_ValueChanged;
}

private void DoubleNumericBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    if (String.IsNullOrEmpty(e.Text))
    {
        e.Handled = true;
        return;
    }

    //Only Numbers, comma and points.
    if (IsEntryDisallowed(sender, e.Text))
    {
        e.Handled = true;
    }
}

private void PastingEvent(object sender, DataObjectPastingEventArgs e)
{
    if (e.DataObject.GetDataPresent(typeof(String)))
    {
        var text = (String)e.DataObject.GetData(typeof(String));

        if (IsTextDisallowed(text))
        {
            e.CancelCommand();
        }
    }
    else
    {
        e.CancelCommand();
    }
}

private void DoubleNumericBox_LostFocus(object sender, RoutedEventArgs e)
{
    //TextChanged -= DoubleNumericBox_TextChanged;

    Text = String.Format("{0:###,###,##0.0###}", Value);

    //TextChanged += DoubleNumericBox_TextChanged;
}




private bool IsEntryDisallowed(object sender, string text)
{
    var regex = new Regex(@"^[0-9]|\.|\,$");

    if (regex.IsMatch(text))
    {
        return !CheckPontuation(sender, text);
    }

    //Not a number or a Comma/Point.
    return true;
}

private bool IsTextDisallowed(string text)
{
    var regex = new Regex(@"^((\d+)|(\d{1,3}(\.\d{3})+)|(\d{1,3}(\.\d{3})(\,\d{3})+))((\,\d{4})|(\,\d{3})|(\,\d{2})|(\,\d{1})|(\,))?$");
    return !regex.IsMatch(text); //\d+(?:,\d{1,2})?
}

private bool CheckPontuation(object sender, string next)
{
    var textBox = sender as TextBox;

    if (textBox == null) return true;

    if (Char.IsNumber(next.ToCharArray()[0]))
        return true;

    if (next.Equals("."))
    {
        var textAux = textBox.Text;

        if (!String.IsNullOrEmpty(textBox.SelectedText))
            textAux = textAux.Replace(textBox.SelectedText, "");

        //Check if the user can add a point mark here.
        var before = textAux.Substring(0, textBox.SelectionStart);
        var after = textAux.Substring(textBox.SelectionStart);

        //If no text, return true.
        if (String.IsNullOrEmpty(before) && String.IsNullOrEmpty(after)) return true;

        if (!String.IsNullOrEmpty(before))
        {
            if (before.Contains(',')) return false;

            if (after.Contains("."))
            {
                var split = before.Split('.');

                if (split.Last().Length != 3) return false;
            }
        }

        if (!String.IsNullOrEmpty(after))
        {
            var split = after.Split('.', ',');

            if (split.First().Length != 3) return false;
        }

        return true;
    }

    //Only one comma.
    if (next.Equals(","))
    {
        return !textBox.Text.Any(x => x.Equals(','));
    }

    return true;
}

}