DependencyProperty CoerceValue & ChangedCallback

DependencyProperty CoerceValue & ChangedCallback

我正在尝试使用依赖属性创建一个简单的自定义数字上下练习。

但我有一些不想要的行为。

我的代码:

XAML:

<Grid Height="22">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="3*"/>
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Grid Grid.Column="0">
        <TextBox 
            x:Name="PART_TextboxEditable"
            HorizontalContentAlignment="Right"
            VerticalContentAlignment="Center"
            Text="{Binding Value, ElementName=parent, Mode=TwoWay}"
            IsEnabled="{Binding IsEditable, ElementName=parent}"
            PreviewTextInput="TextBox_PreviewTextInput"
            FontWeight="Normal">
        </TextBox>
    </Grid>

    <Grid Grid.Column="1">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <RepeatButton 
            Click="IncressValueClick"
            FontSize="8"
            Background="#FFF6F6F6"
            BorderThickness="0 1 1 1">
        <RepeatButton.Content>
                <Path
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center" 
                    Fill="Black" 
                    Data="M4,0 L0,4 L8,4 z"/>
            </RepeatButton.Content>
        </RepeatButton>
    <RepeatButton 
        Click="DecressValueClick"
        Grid.Row="1"
        FontSize="8"
        BorderThickness="0 0 1 1"
        Background="#FFF6F6F6">
        <RepeatButton.Content>
                <Path 
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center" 
                    Fill="Black" 
                    Data="M0,0 L8,0 L4,4 z"/>
            </RepeatButton.Content>
        </RepeatButton>
    </Grid>
</Grid>

代码隐藏:

/// <summary>
/// Interação lógica para IntegerUpDown.xam
/// </summary>
public partial class IntegerUpDown : UserControl
{
    public int Value
    {
        get { return (int)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public int Minimum
    {
        get { return (int)GetValue(MinimumProperty); }
        set { SetValue(MinimumProperty, value); }
    }

    public int? Maximum
    {
        get { return (int?)GetValue(MaximumProperty); }
        set { SetValue(MaximumProperty, value); }
    }

    public int Increment
    {
        get { return (int)GetValue(IncrementProperty); }
        set { SetValue(IncrementProperty, value); }
    }

    public static readonly DependencyProperty IncrementProperty =
        DependencyProperty.Register(
            "Increment",
            typeof(int),
            typeof(IntegerUpDown),
            new PropertyMetadata(1));

    public static readonly DependencyProperty MaximumProperty =
        DependencyProperty.Register(
            "Maximum",
            typeof(int?),
            typeof(IntegerUpDown),
            new PropertyMetadata(null, new PropertyChangedCallback(MaximumPropertyChangedCallback)));

    public static readonly DependencyProperty MinimumProperty =
        DependencyProperty.Register(
            "Minimum",
            typeof(int),
            typeof(IntegerUpDown),
            new PropertyMetadata(0, new PropertyChangedCallback(MinimumPropertyChangedCallback)));

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register(
            "Value",
            typeof(int),
            typeof(IntegerUpDown),
            new FrameworkPropertyMetadata(0, 
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                new PropertyChangedCallback(ValuePropertyChangedCalllback),
                new CoerceValueCallback(ValuePropertyCoerceValueCallback)));

    public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
        "ValueChanged", 
        RoutingStrategy.Bubble, 
        typeof(RoutedPropertyChangedEventHandler<int>), typeof(IntegerUpDown));

    private static void ValuePropertyChangedCalllback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        BindingExpression be = (d as IntegerUpDown).GetBindingExpression(ValueProperty);
        if (be != null)
            be.UpdateSource();

        (d as IntegerUpDown).RaiseEvent(new RoutedPropertyChangedEventArgs<int>((int)e.OldValue, (int)e.NewValue, ValueChangedEvent));
    }

    private static void MinimumPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs baseValue)
    {
        var value = (int)baseValue.NewValue;
        var obj = d as IntegerUpDown;

        obj.SetCurrentValue(ValueProperty, (int)Math.Max(obj.Value, value));
    }

    private static void MaximumPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs baseValue)
    {
        var value = (int?)baseValue.NewValue;
        var obj = d as IntegerUpDown;

        obj.SetCurrentValue(ValueProperty, Math.Min(obj.Value, value ?? obj.Value));
    }

    private static object ValuePropertyCoerceValueCallback(DependencyObject d, object baseValue)
    {
        var value = (int)baseValue;
        var obj = d as IntegerUpDown;

        obj.CoerceValue(MaximumProperty);
        obj.CoerceValue(MinimumProperty);

        int newValue = Math.Max(obj.Minimum, Math.Min(value, obj.Maximum ?? value));

        return newValue;
    }

    public IntegerUpDown()
    {
        InitializeComponent();
    }

    private void IncressValueClick(object sender, RoutedEventArgs e)
    {
        IncressValue();
    }

    private void DecressValueClick(object sender, RoutedEventArgs e)
    {
        DecressValue();
    }

    private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var textBox = sender as TextBox;
        var fullText = textBox.Text.Insert(textBox.SelectionStart, e.Text);

        e.Handled = !int.TryParse(fullText, out _);
    }

    private void NumericUpDownPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        IntegerUpDown control = (IntegerUpDown)sender;

        e.Handled = control.Focus() || e.Handled;
    }

    private void Parent_MouseWheel(object sender, MouseWheelEventArgs e)
    {
        if (e.Delta > 0)
            IncressValue();
        else
            DecressValue();
    }

    private void IncressValue()
    {
        Value += Increment;
    }

    private void DecressValue()
    {
        Value -= Increment;
    }
    private void Parent_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        switch (e.Key)
        {
            case Key.Up:
                IncressValue();
                break;

            case Key.Down:
                DecressValue();
                break;

            default:
                return;
        }
    }

这是一个非常简单的代码,但它没有按预期工作。我知道我做错了什么,但我无法识别这里的问题。

问题:

我正在使用 XAML 进行测试:

<local:IntegerUpDown Value="{Binding Value}" 
                     Maximum="15"
                     Minimum="10"
                     Increment="2"></local:IntegerUpDown>

<TextBlock 
    Foreground="White"
    Text="{Binding Value}" Grid.Row="1"></TextBlock>

正如您在下面看到的,我设置手动将“15151515”值写入 TextBox 并且我的 CoerceValue 被调用,我的 newValue 返回到 CoerceValue 是 ' 15' 因为 Maximum 值设置为 15。我的 textbox 显示正确的值 (15) 但我的 ViewModel 值不正确。

[

如果我按向上键,我的值为 15:

[

我的 Maximum 总是类似于 Maximum + IncrementMinimum - Increment。我的意思是,当值达到 Minimum 时,我可以再单击一次,然后 Minimum - Increment(示例为 8)在 ViewModel 上,但在 [=13 上=] 它显示 MinimumValue(示例为 10)。

我的代码有什么问题?

CoerceValueCallback 运行 源 属性 设置之后。您可以通过将依赖项 属性 的 DefaultUpdateSourceTrigger 属性 设置为 UpdateSourceTrigger.Explicit 并显式设置源 属性:

来解决此问题
public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(
                "Value",
                typeof(int),
                typeof(IntegerUpDown),
                new FrameworkPropertyMetadata(0,
                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                    new PropertyChangedCallback(ValuePropertyChangedCalllback),
                    new CoerceValueCallback(ValuePropertyCoerceValueCallback))
                { DefaultUpdateSourceTrigger = UpdateSourceTrigger.Explicit });
...
private static void ValuePropertyChangedCalllback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    IntegerUpDown ctrl = (IntegerUpDown)d;
    int newValue = (int)e.NewValue;
    BindingExpression be = ctrl.GetBindingExpression(ValueProperty);
    if (be != null && be.ResolvedSource != null && be.ParentBinding != null && be.ParentBinding.Path != null
        && !string.IsNullOrEmpty(be.ParentBinding.Path.Path))
    {
        var pi = be.ResolvedSource.GetType().GetProperty(be.ParentBinding.Path.Path);
        if (pi != null)
            pi.SetValue(be.ResolvedSource, newValue);
    }

    ctrl.RaiseEvent(new RoutedPropertyChangedEventArgs<int>((int)e.OldValue, newValue, ValueChangedEvent));
}

看到你问的是滑块,也许解释一下它是如何工作的可能会激发另一种选择。

滑块的工作方式是有一种方法可以更改值。每当需要更改时都会调用它并检查最小最大值。 因此,这根本不是 dp 系统的一部分,并且该值由处理检查的中介分离。

仅设置依赖项的值 属性 会忽略此内部方法,因此不会应用 min/max。

因为你有一个文本框,所以你不能 "just" re-template 一个滑块并使用它的内置行为。

不过您也可以使用类似的模式。

添加一个内部 dp 来保存输入值。

我们称其为 invalue。

将其绑定到文本框。

当 invalue 改变时,检查 vs min/max 并设置您的外部值或重置 invalue。

按下重复按钮时,在设置 invalue 和 value 之前检查是否与 min/max 值相对应。