Почему моя привязка данных видит реальное значение вместо приведенного значения? - PullRequest
13 голосов
/ 24 июля 2010

Я пишу настоящий NumericUpDown/Spinner элемент управления в качестве упражнения для изучения разработки пользовательских элементов управления.У меня есть большая часть поведения, которое я ищу, включая соответствующее принуждение.Однако один из моих тестов выявил недостаток.

Мой элемент управления имеет 3 свойства зависимости: Value, MaximumValue и MinimumValue.Я использую принуждение, чтобы убедиться, что Value остается между минимальным и максимальным включительно.Например:

// In NumericUpDown.cs

public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), 
    new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, HandleValueChanged, HandleCoerceValue));

[Localizability(LocalizationCategory.Text)]
public int Value
{
    get { return (int)this.GetValue(ValueProperty); }
    set { this.SetCurrentValue(ValueProperty, value); }
}

private static object HandleCoerceValue(DependencyObject d, object baseValue)
{
    NumericUpDown o = (NumericUpDown)d;
    var v = (int)baseValue;

    if (v < o.MinimumValue) v = o.MinimumValue;
    if (v > o.MaximumValue) v = o.MaximumValue;

    return v;
}

Мой тест просто для того, чтобы убедиться, что привязка данных работает так, как я ожидаю.Я создал приложение wpf для Windows по умолчанию и добавил следующий xaml:

<Window x:Class="WpfApplication.MainWindow" x:Name="This"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:nud="clr-namespace:WpfCustomControlLibrary;assembly=WpfCustomControlLibrary"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <nud:NumericUpDown Value="{Binding ElementName=This, Path=NumberValue}"/>
        <TextBox Grid.Row="1" Text="{Binding ElementName=This, Path=NumberValue, Mode=OneWay}" />
    </Grid>
</Window>

с очень простым кодом:

public partial class MainWindow : Window
{
    public int NumberValue
    {
        get { return (int)GetValue(NumberValueProperty); }
        set { SetCurrentValue(NumberValueProperty, value); }
    }

    // Using a DependencyProperty as the backing store for NumberValue.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty NumberValueProperty =
        DependencyProperty.Register("NumberValue", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0));     

    public MainWindow()
    {
        InitializeComponent();
    }
}

(я опускаю xaml для представления элемента управления)

Теперь, если я запускаю это, я вижу значение из NumericUpDown, отраженное соответствующим образом в текстовом поле, но если я ввожу значение, которое выходит за пределы диапазона, значение вне диапазона отображается в текстовом поле теста, в то время как NumericUpDown показывает правильное значение.

Так должны действовать принудительные значения?Хорошо, что он принудительно используется в пользовательском интерфейсе, но я ожидал, что приведенное значение также будет проходить через привязку данных.

Ответы [ 3 ]

13 голосов
/ 24 июля 2010

Ух ты, это удивительно. Когда вы устанавливаете значение в свойстве зависимости, выражения привязки обновляются до выполнения приведения значения!

Если вы посмотрите на DependencyObject.SetValueCommon в Reflector, вы увидите вызов Expression.SetValue на полпути через метод. Вызов UpdateEffectiveValue, который вызовет ваш CoerceValueCallback, находится в самом конце, после того как привязка уже была обновлена.

Это можно увидеть и на классах фреймворка. Из нового приложения WPF добавьте следующий XAML:

<StackPanel>
    <Slider Name="Slider" Minimum="10" Maximum="20" Value="{Binding Value, 
        RelativeSource={RelativeSource AncestorType=Window}}"/>
    <Button Click="SetInvalid_Click">Set Invalid</Button>
</StackPanel>

и следующий код:

private void SetInvalid_Click(object sender, RoutedEventArgs e)
{
    var before = this.Value;
    var sliderBefore = Slider.Value;
    Slider.Value = -1;
    var after = this.Value;
    var sliderAfter = Slider.Value;
    MessageBox.Show(string.Format("Value changed from {0} to {1}; " + 
        "Slider changed from {2} to {3}", 
        before, after, sliderBefore, sliderAfter));
}

public int Value { get; set; }

Если вы перетащите ползунок, а затем нажмете кнопку, вы получите сообщение типа «Значение изменено с 11 на -1; Ползунок изменен с 11 на 10».

5 голосов
/ 05 августа 2015

Новый ответ на старый вопрос:: -)

При регистрации ValueProperty используется FrameworkPropertyMetadata экземпляр.Установите для свойства UpdateSourceTrigger этого экземпляра значение Explicit.Это можно сделать при перегрузке конструктора.

public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), 
    new FrameworkPropertyMetadata(
      0, 
      FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, 
      HandleValueChanged, 
      HandleCoerceValue,
      false
      UpdateSourceTrigger.Explicit));

Теперь источник привязки ValueProperty не будет обновляться автоматически при PropertyChanged.Выполните обновление вручную, используя метод HandleValueChanged (см. Код выше).Этот метод вызывается только при «реальных» изменениях свойства ПОСЛЕ вызывающего метода.

Вы можете сделать это следующим образом:

static void HandleValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    NumericUpDown nud = obj as NumericUpDown;
    if (nud == null)
        return;

    BindingExpression be = nud.GetBindingExpression(NumericUpDown.ValueProperty);
    if(be != null)
        be.UpdateSource();
}

Таким образом вы можете избежатьобновите свои привязки с помощью не приведенных значений вашего DependencyProperty.

0 голосов
/ 24 июля 2010

Вы принуждаете v, который является целым числом и как таковой тип значения.Поэтому он хранится в стеке.Он никак не связан с baseValue.Таким образом, изменение v не изменит baseValue.

Та же логика применима к baseValue.Он передается по значению (не по ссылке), поэтому его изменение не изменит фактического параметра.

v возвращается и явно используется для обновления пользовательского интерфейса.

Возможно, вы захотитеисследовать изменение типа данных свойств на ссылочный тип.Затем он будет передан по ссылке, и любые сделанные изменения будут отражены обратно к источнику.Предполагая, что процесс привязки данных не создает копию.

...