DependencyProperty CoerceValue & ChangedCallback - PullRequest
1 голос
/ 13 мая 2019

Я пытаюсь создать пользовательский просто Числовой Up Down, используя свойства зависимости для практики.

Но у меня какое-то нежелательное поведение.

Мой код:

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 имеетневерное значение.

[1

Если я нажму UP и мое значение будет 15:

[2]

У меня всегда будет Maximum что-токак Maximum + Increment и Minimum - Increment.Я имею в виду, когда значение достигает Minimum, я могу щелкнуть еще раз и иметь Minimum - Increment (на примере 8) на ViewModel, но на TextBox он показывает MinimumValue (наПример 10).

Что не так с моим кодом?

Ответы [ 2 ]

1 голос
/ 13 мая 2019

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));
}
0 голосов
/ 13 мая 2019

Если вы спросите о слайдере, возможно, объяснение того, как это работает, может вдохновить альтернативу.

Слайдер работает так, что есть метод, который изменяет значение. Это вызывается всякий раз, когда изменение необходимо, и это проверяет против min max. Следовательно, это вообще не является частью системы dp, и значение отделяется посредником, который выполняет проверку.

Просто установка значения свойства зависимости игнорирует этот внутренний метод, поэтому мин / макс не применяется.

Поскольку у вас есть текстовое поле, вы не можете «просто» изменить шаблон слайдера и использовать его встроенное поведение.

Вы могли бы вместо этого использовать подобный образец.

Добавить внутренний dp для хранения входного значения.

Давайте назовем это бесценным.

Свяжите это с текстовым полем.

Когда значение изменяется, проверьте значение min / max и установите внешнее значение или сбросьте значение.

Когда нажаты кнопки повтора, проверьте соответствие минимальным и максимальным значениям, прежде чем устанавливать значение и значение.

...