Fuerza de Propagación de Valor Coaccionado

tl; dr: Los valores forzados no se propagan a través de enlaces de datos. ¿Cómo puedo forzar la actualización a través del enlace de datos cuando el código subyacente no conoce el otro lado del enlace?

Estoy usando unCoerceValueCallback en una propiedad de dependencia de WPF y estoy atascado en el problema de que los valores forzados no se propagan a través de los enlaces.

Window1.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace CoerceValueTest
{
    public class SomeControl : UserControl
    {
        public SomeControl()
        {
            StackPanel sp = new StackPanel();

            Button bUp = new Button();
            bUp.Content = "+";
            bUp.Click += delegate(object sender, RoutedEventArgs e) {
                Value += 2;
            };

            Button bDown = new Button();
            bDown.Content = "-";
            bDown.Click += delegate(object sender, RoutedEventArgs e) {
                Value -= 2;
            };

            TextBlock tbValue = new TextBlock();
            tbValue.SetBinding(TextBlock.TextProperty,
                               new Binding("Value") {
                                Source = this
                               });

            sp.Children.Add(bUp);
            sp.Children.Add(tbValue);
            sp.Children.Add(bDown);

            this.Content = sp;
        }

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
                                                                                              typeof(int),
                                                                                              typeof(SomeControl),
                                                                                              new PropertyMetadata(0, ProcessValueChanged, CoerceValue));

        private static object CoerceValue(DependencyObject d, object baseValue)
        {
            if ((int)baseValue % 2 == 0) {
                return baseValue;
            } else {
                return DependencyProperty.UnsetValue;
            }
        }

        private static void ProcessValueChanged(object source, DependencyPropertyChangedEventArgs e)
        {
            ((SomeControl)source).ProcessValueChanged(e);
        }

        private void ProcessValueChanged(DependencyPropertyChangedEventArgs e)
        {
            OnValueChanged(EventArgs.Empty);
        }

        protected virtual void OnValueChanged(EventArgs e)
        {
            if (e == null) {
                throw new ArgumentNullException("e");
            }

            if (ValueChanged != null) {
                ValueChanged(this, e);
            }
        }

        public event EventHandler ValueChanged;

        public int Value {
            get {
                return (int)GetValue(ValueProperty);
            }
            set {
                SetValue(ValueProperty, value);
            }
        }
    }

    public class SomeBiggerControl : UserControl
    {
        public SomeBiggerControl()
        {
            Border parent = new Border();
            parent.BorderThickness = new Thickness(2);
            parent.Margin = new Thickness(2);
            parent.Padding = new Thickness(3);
            parent.BorderBrush = Brushes.DarkRed;

            SomeControl ctl = new SomeControl();
            ctl.SetBinding(SomeControl.ValueProperty,
                           new Binding("Value") {
                            Source = this,
                            Mode = BindingMode.TwoWay
                           });
            parent.Child = ctl;

            this.Content = parent;
        }

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
                                                                                              typeof(int),
                                                                                              typeof(SomeBiggerControl),
                                                                                              new PropertyMetadata(0));

        public int Value {
            get {
                return (int)GetValue(ValueProperty);
            }
            set {
                SetValue(ValueProperty, value);
            }
        }
    }

    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
    }
}

Window1.xaml

<Window x:Class="CoerceValueTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="CoerceValueTest" Height="300" Width="300"
    xmlns:local="clr-namespace:CoerceValueTest"
    >
    <StackPanel>
        <local:SomeBiggerControl x:Name="sc"/>
        <TextBox Text="{Binding Value, ElementName=sc, Mode=TwoWay}" Name="tb"/>
        <Button Content=" "/>
    </StackPanel>
</Window>

es decir, dos controles de usuario, uno anidado dentro del otro, y el exterior de los de una ventana. El control interno del usuario tiene unValue propiedad de dependencia que está vinculada a unaValue Propiedad de dependencia del control exterior. En la ventana, unTextBox.Text la propiedad está vinculada a laValue Propiedad del control exterior.

El control interno tiene unaCoerceValueCallback registrado con suValue propiedad cuyo efecto es que esteValue A la propiedad solo se le pueden asignar números pares.

Tenga en cuenta que este código está simplificado para fines de demostración. La versión real no inicializa nada en el constructor; los dos controles realmente tienen plantillas de control que hacen todo lo que se hace en los constructores respectivos aquí. Es decir, en el código real, el control externo no conoce el control interno.

Al escribir un número par en el cuadro de texto y cambiar el enfoque (por ejemplo, al enfocar el botón de relleno debajo del cuadro de texto), ambosValue Las propiedades se actualizan debidamente. Al escribir un número impar en el cuadro de texto, sin embargo, elValue propiedad del control interno no cambia, mientras que elValue propiedad del control exterior, así como laTextBox.Text propiedad, muestra el número impar.

Mi pregunta es:¿Cómo puedo forzar una actualización en el cuadro de texto (e idealmente también en el control externo)?Value propiedad, mientras estamos en ello)?

he encontradouna pregunta SO sobre el mismo problema, pero realmente no proporciona una solución. Se refiere al uso de un controlador de eventos de propiedades modificadas para restablecer el valor, pero por lo que puedo ver, eso significaría duplicar el código de evaluación en el control externo ... lo que no es realmente viable, ya que mi código de evaluación real se basa en algunos Información básicamente solo conocida (sin mucho esfuerzo) al control interno.

Además,este blogpost sugiere invocarUpdateTarget en la encuadernación enTextBox.Text en elCoerceValueCallback, pero primero, como se indicó anteriormente, mi control interno posiblemente no puede tener ningún conocimiento sobre el cuadro de texto, y segundo, probablemente tendría que llamarUpdateSource primero en la unión de laValue Propiedad del control interno. No veo dónde hacer eso, sin embargo, como dentro de laCoerceValue método, el valor de coerción aún no se ha establecido (por lo que es demasiado pronto para actualizar el enlace), mientras que en el caso de que el valor se restablezcaCoerceValue, el valor de la propiedad seguirá siendo lo que era, por lo tanto, una devolución de llamada de propiedad modificada no se invocará (como también se indica enesta discusión).

Una posible solución que había pensado era reemplazar la propiedad de dependencia enSomeControl con una propiedad convencional y unaINotifyPropertyChanged implementación (por lo que puedo activar manualmente elPropertyChanged evento incluso si el valor ha sido coaccionado). Sin embargo, esto significaría que ya no puedo declarar un enlace a esa propiedad, por lo que no es una solución realmente útil.

Respuestas a la pregunta(1)

Su respuesta a la pregunta