Enlace de OneWayToSource desde la propiedad de solo lectura en XAML

Estoy tratando de vincularme a una propiedad Readonly con OneWayToSource como modo, pero parece que esto no se puede hacer en XAML:

  

Yo obtengo:

La propiedad ‘FlagThingy.IsModified’ no se puede establecer porque no tiene un acceso de conjunto accesible.

IsModified es una DependencyProperty de FlagThingy lectura en FlagThingy . Quiero vincular ese valor a la propiedad FlagIsModified en el contenedor.

Para ser claro:

 FlagThingy.IsModified --> container.FlagIsModified ------ READONLY ----- ----- READWRITE -------- 

¿Es posible usar solo XAML?


Actualización: Bueno, arreglé este caso estableciendo el enlace en el contenedor y no en el FlagThingy . Pero aún me gustaría saber si esto es posible.

Algunos resultados de investigación para OneWayToSource …

Opción 1.

 // Control definition public partial class FlagThingy : UserControl { public static readonly DependencyProperty IsModifiedProperty = DependencyProperty.Register("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata()); } 
  
 // Binding Code Binding binding = new Binding(); binding.Path = new PropertyPath("FlagIsModified"); binding.ElementName = "container"; binding.Mode = BindingMode.OneWayToSource; _flagThingy.SetBinding(FlagThingy.IsModifiedProperty, binding); 

Opcion 2

 // Control definition public partial class FlagThingy : UserControl { public static readonly DependencyProperty IsModifiedProperty = DependencyProperty.Register("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata()); public bool IsModified { get { return (bool)GetValue(IsModifiedProperty); } set { throw new Exception("An attempt ot modify Read-Only property"); } } } 
  

Opción n. ° 3 (propiedad de dependencia de solo lectura verdadera)

System.ArgumentException: la propiedad ‘IsModified’ no puede vincularse a datos.

 // Control definition public partial class FlagThingy : UserControl { private static readonly DependencyPropertyKey IsModifiedKey = DependencyProperty.RegisterReadOnly("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata()); public static readonly DependencyProperty IsModifiedProperty = IsModifiedKey.DependencyProperty; } 
  
 // Binding Code Same binding code... 

Reflector da la respuesta:

 internal static BindingExpression CreateBindingExpression(DependencyObject d, DependencyProperty dp, Binding binding, BindingExpressionBase parent) { FrameworkPropertyMetadata fwMetaData = dp.GetMetadata(d.DependencyObjectType) as FrameworkPropertyMetadata; if (((fwMetaData != null) && !fwMetaData.IsDataBindingAllowed) || dp.ReadOnly) { throw new ArgumentException(System.Windows.SR.Get(System.Windows.SRID.PropertyNotBindable, new object[] { dp.Name }), "dp"); } .... 

Esta es una limitación de WPF y es por diseño. Se informa en Connect aquí:
Enlace de OneWayToSource desde una propiedad de dependencia de solo lectura

Hice una solución para poder impulsar dinámicamente las propiedades de dependencia de solo lectura a la fuente llamada PushBinding que PushBinding aquí . El ejemplo siguiente hace enlaces de OneWayToSource desde el ActualWidth y ActualHeight del DP de ActualWidth ActualHeight a las propiedades de ActualWidth y ActualHeight del DataContext

       

PushBinding funciona utilizando dos propiedades de dependencia, Listener y Mirror. El oyente está vinculado a OneWay con TargetProperty y en PropertyChangedCallback actualiza la propiedad Mirror que vincula OneWayToSource a lo que se especificó en el enlace.

Demo Project se puede descargar aquí.
Contiene código fuente y uso de muestra breve, o visite mi blog de WPF si está interesado en los detalles de la implementación.

Escribí esto:

Uso:

  

Código:

 using System; using System.Windows; using System.Windows.Data; using System.Windows.Markup; public static class OneWayToSource { public static readonly DependencyProperty BindProperty = DependencyProperty.RegisterAttached( "Bind", typeof(ProxyBinding), typeof(OneWayToSource), new PropertyMetadata(default(Paths), OnBindChanged)); public static void SetBind(this UIElement element, ProxyBinding value) { element.SetValue(BindProperty, value); } [AttachedPropertyBrowsableForChildren(IncludeDescendants = false)] [AttachedPropertyBrowsableForType(typeof(UIElement))] public static ProxyBinding GetBind(this UIElement element) { return (ProxyBinding)element.GetValue(BindProperty); } private static void OnBindChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((ProxyBinding)e.OldValue)?.Dispose(); } public class ProxyBinding : DependencyObject, IDisposable { private static readonly DependencyProperty SourceProxyProperty = DependencyProperty.Register( "SourceProxy", typeof(object), typeof(ProxyBinding), new PropertyMetadata(default(object), OnSourceProxyChanged)); private static readonly DependencyProperty TargetProxyProperty = DependencyProperty.Register( "TargetProxy", typeof(object), typeof(ProxyBinding), new PropertyMetadata(default(object))); public ProxyBinding(DependencyObject source, DependencyProperty sourceProperty, string targetProperty) { var sourceBinding = new Binding { Path = new PropertyPath(sourceProperty), Source = source, Mode = BindingMode.OneWay, }; BindingOperations.SetBinding(this, SourceProxyProperty, sourceBinding); var targetBinding = new Binding() { Path = new PropertyPath($"{nameof(FrameworkElement.DataContext)}.{targetProperty}"), Mode = BindingMode.OneWayToSource, Source = source }; BindingOperations.SetBinding(this, TargetProxyProperty, targetBinding); } public void Dispose() { BindingOperations.ClearAllBindings(this); } private static void OnSourceProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.SetCurrentValue(TargetProxyProperty, e.NewValue); } } } [MarkupExtensionReturnType(typeof(OneWayToSource.ProxyBinding))] public class Paths : MarkupExtension { public DependencyProperty From { get; set; } public string To { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) { var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); var targetObject = (UIElement)provideValueTarget.TargetObject; return new OneWayToSource.ProxyBinding(targetObject, this.From, this.To); } } 

Todavía no lo he probado en estilos y plantillas, supongo que necesita una carcasa especial.

Aquí hay otra implementación para vincular a Validation.HasError

 public static class OneWayToSource { public static readonly DependencyProperty BindingsProperty = DependencyProperty.RegisterAttached( "Bindings", typeof(OneWayToSourceBindings), typeof(OneWayToSource), new PropertyMetadata(default(OneWayToSourceBindings), OnBinidngsChanged)); public static void SetBindings(this FrameworkElement element, OneWayToSourceBindings value) { element.SetValue(BindingsProperty, value); } [AttachedPropertyBrowsableForChildren(IncludeDescendants = false)] [AttachedPropertyBrowsableForType(typeof(FrameworkElement))] public static OneWayToSourceBindings GetBindings(this FrameworkElement element) { return (OneWayToSourceBindings)element.GetValue(BindingsProperty); } private static void OnBinidngsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((OneWayToSourceBindings)e.OldValue)?.ClearValue(OneWayToSourceBindings.ElementProperty); ((OneWayToSourceBindings)e.NewValue)?.SetValue(OneWayToSourceBindings.ElementProperty, d); } } public class OneWayToSourceBindings : FrameworkElement { private static readonly PropertyPath DataContextPath = new PropertyPath(nameof(DataContext)); private static readonly PropertyPath HasErrorPath = new PropertyPath($"({typeof(Validation).Name}.{Validation.HasErrorProperty.Name})"); public static readonly DependencyProperty HasErrorProperty = DependencyProperty.Register( nameof(HasError), typeof(bool), typeof(OneWayToSourceBindings), new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); internal static readonly DependencyProperty ElementProperty = DependencyProperty.Register( "Element", typeof(UIElement), typeof(OneWayToSourceBindings), new PropertyMetadata(default(UIElement), OnElementChanged)); private static readonly DependencyProperty HasErrorProxyProperty = DependencyProperty.RegisterAttached( "HasErrorProxy", typeof(bool), typeof(OneWayToSourceBindings), new PropertyMetadata(default(bool), OnHasErrorProxyChanged)); public bool HasError { get { return (bool)this.GetValue(HasErrorProperty); } set { this.SetValue(HasErrorProperty, value); } } private static void OnHasErrorProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.SetCurrentValue(HasErrorProperty, e.NewValue); } private static void OnElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (e.NewValue == null) { BindingOperations.ClearBinding(d, DataContextProperty); BindingOperations.ClearBinding(d, HasErrorProxyProperty); } else { var dataContextBinding = new Binding { Path = DataContextPath, Mode = BindingMode.OneWay, Source = e.NewValue }; BindingOperations.SetBinding(d, DataContextProperty, dataContextBinding); var hasErrorBinding = new Binding { Path = HasErrorPath, Mode = BindingMode.OneWay, Source = e.NewValue }; BindingOperations.SetBinding(d, HasErrorProxyProperty, hasErrorBinding); } } } 

Uso en xaml

         

Esta implementación es específica para Validation.HasError vinculante

Aquí hay otra solución de propiedad adjunta basada en SizeObserver que se detalla aquí. Empujando las propiedades de la GUI de solo lectura a ViewModel.

 public static class MouseObserver { public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached( "Observe", typeof(bool), typeof(MouseObserver), new FrameworkPropertyMetadata(OnObserveChanged)); public static readonly DependencyProperty ObservedMouseOverProperty = DependencyProperty.RegisterAttached( "ObservedMouseOver", typeof(bool), typeof(MouseObserver)); public static bool GetObserve(FrameworkElement frameworkElement) { return (bool)frameworkElement.GetValue(ObserveProperty); } public static void SetObserve(FrameworkElement frameworkElement, bool observe) { frameworkElement.SetValue(ObserveProperty, observe); } public static bool GetObservedMouseOver(FrameworkElement frameworkElement) { return (bool)frameworkElement.GetValue(ObservedMouseOverProperty); } public static void SetObservedMouseOver(FrameworkElement frameworkElement, bool observedMouseOver) { frameworkElement.SetValue(ObservedMouseOverProperty, observedMouseOver); } private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { var frameworkElement = (FrameworkElement)dependencyObject; if ((bool)e.NewValue) { frameworkElement.MouseEnter += OnFrameworkElementMouseOverChanged; frameworkElement.MouseLeave += OnFrameworkElementMouseOverChanged; UpdateObservedMouseOverForFrameworkElement(frameworkElement); } else { frameworkElement.MouseEnter -= OnFrameworkElementMouseOverChanged; frameworkElement.MouseLeave -= OnFrameworkElementMouseOverChanged; } } private static void OnFrameworkElementMouseOverChanged(object sender, MouseEventArgs e) { UpdateObservedMouseOverForFrameworkElement((FrameworkElement)sender); } private static void UpdateObservedMouseOverForFrameworkElement(FrameworkElement frameworkElement) { frameworkElement.SetCurrentValue(ObservedMouseOverProperty, frameworkElement.IsMouseOver); } } 

Declarar propiedad adjunta en control

  

WPF no usará el ajustador de propiedades CLR, pero parece que tiene alguna validación impar basada en él.

Puede estar en tu situación esto puede estar bien:

  public bool IsModified { get { return (bool)GetValue(IsModifiedProperty); } set { throw new Exception("An attempt ot modify Read-Only property"); } } 

Hmmm … No estoy seguro de estar de acuerdo con ninguna de estas soluciones. ¿Qué hay de especificar una callback de coerción en el registro de su propiedad que ignora el cambio externo? Por ejemplo, necesitaba implementar una propiedad de dependencia Posición de solo lectura para obtener la posición de un control MediaElement dentro de un control de usuario. Así es como lo hice:

  public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(double), typeof(MediaViewer), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, OnPositionChanged, OnPositionCoerce)); private static void OnPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctrl = d as MediaViewer; } private static object OnPositionCoerce(DependencyObject d, object value) { var ctrl = d as MediaViewer; var position = ctrl.MediaRenderer.Position.TotalSeconds; if (ctrl.MediaRenderer.NaturalDuration.HasTimeSpan == false) return 0d; else return Math.Min(position, ctrl.Duration); } public double Position { get { return (double)GetValue(PositionProperty); } set { SetValue(PositionProperty, value); } } 

En otras palabras, simplemente ignore el cambio y devuelva el valor respaldado por un miembro diferente que no tenga un modificador público. – En el ejemplo anterior, MediaRenderer es realmente el control de MediaElement privado.

Estás haciendo la encuadernación en la dirección incorrecta en este momento. OneWayToSource intentará actualizar FlagIsModified en el contenedor cada vez que IsModified cambie en el control que está creando. Desea lo contrario, que es tener IsModified vincularse a container.FlagIsModified. Para eso deberías usar el modo vinculante OneWay

  

Lista completa de miembros de enumeración: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx