Почему моя привязка данных видит действительное значение вместо принужденного значения?

Я пишу реальное 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>

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

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 показывает правильное значение.

Это то, как принужденные значения, как предполагается, действуют? Хорошо, что это принуждено в ui, но я ожидал, что принужденное значение пробежит привязку данных также.

13
задан H.B. 7 September 2011 в 16:29
поделиться

2 ответа

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

Если вы посмотрите на 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; }

Если вы перетащите слайдер, а затем нажмете кнопку, вы получите сообщение типа "Value changed from 11 to -1; Slider changed from 11 to 10".

13
ответ дан 1 December 2019 в 23:47
поделиться

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

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

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

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

-1
ответ дан 1 December 2019 в 23:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: