DependencyProperty CoerceValue和ChangedCallback

时间:2019-05-13 12:01:33

标签: c# wpf xaml dependency-properties

我正在尝试创建一个自定义的简单数字,使用“依赖属性”进行练习。

但是我有一些不需要的行为。

我的代码:

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的{​​{1}}是'15'因为CoerceValue的值设置为15。我的Maximum显示正确的值(15),但是我来自textbox的值具有错误的值。

[1

如果我按UP,我的值是15:

[2]

我的ViewModel总是类似于Maximum + MaximumIncrement-Minimum。我的意思是,当值达到Increment时,我可以再单击一次,在Minimum上有Minimum-Increment(例如8),但在{{1 }}显示ViewModel(例如10)。

我的代码有什么问题?

2 个答案:

答案 0 :(得分:1)

CoerceValueCallback在设置源属性后 运行。您可以通过将依赖项属性的DefaultUpdateSourceTrigger属性设置为UpdateSourceTrigger.Explicit并显式设置source属性来解决此问题:

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));
}

答案 1 :(得分:0)

看到您在询问滑块时,可能对它的工作原理进行解释可能会激发出另一种选择。

滑块的工作方式是存在一种更改Value的方法。只要有必要进行更改,就会调用此方法,并且它会检查最小最大值。 因此,这根本不是dp系统的一部分,并且值由处理检查的中介解耦。

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

因为有一个文本框,所以不能“仅”重新模板化滑块并使用其内置行为。

您可以改用类似的模式。

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

让我们称之为无价。

将其绑定到文本框。

更改无价值后,请检查vs最小值/最大值并设置外部值或重置无价值。

按下重复按钮时,在设置无值和值之前,请检查是否与最小值/最大值相对。