Associazione OneWayToSource dalla proprietà readonly in XAML

Sto cercando di associare a una proprietà OneWayToSource con OneWayToSource come modalità, ma sembra che questo non può essere fatto in XAML:

  

Ottengo:

La proprietà ‘FlagThingy.IsModified’ non può essere impostata perché non ha un accessor set accessibile.

IsModified è una proprietà DependencyProperty sola lettura su FlagThingy . Voglio associare quel valore alla proprietà FlagIsModified sul contenitore.

Per essere chiari:

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

È ansible utilizzare solo XAML?


Aggiornamento: Bene, ho risolto questo caso impostando l’associazione sul contenitore e non su FlagThingy . Ma mi piacerebbe ancora sapere se questo è ansible.

Alcuni risultati di ricerca per OneWayToSource …

Opzione 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); 

Opzione 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"); } } } 
  

Opzione n. 3 (vera proprietà di dipendenza di sola lettura)

System.ArgumentException: la proprietà ‘IsModified’ non può essere associata ai dati.

 // 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 dà la risposta:

 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"); } .... 

Questa è una limitazione di WPF ed è di progettazione. È riportato su Connect qui:
Associazione OneWayToSource da una proprietà di dipendenza di sola lettura

Ho creato una soluzione per poter PushBinding in modo dinamico le proprietà di dipendenza di sola lettura per l’origine chiamata PushBinding cui ho PushBinding blog qui . Nell’esempio seguente OneWayToSource Bindings dalle proprietà ActualWidth e ActualHeight di DP di ActualWidth ActualHeight alle proprietà Width e Height di DataContext

       

PushBinding funziona utilizzando due proprietà di dipendenza, Listener e Mirror. Il Listener è associato OneWay alla TargetProperty e nella PropertyChangedCallback aggiorna la proprietà Mirror che è associata a OneWayToSource a qualsiasi cosa specificata nel Binding.

Demo Project può essere scaricato qui.
Contiene codice sorgente e breve utilizzo del campione o visita il mio blog WPF se sei interessato ai dettagli dell’implementazione.

Ha scritto questo:

Uso:

  

Codice:

 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); } } 

Non l’ho ancora testato in stili e modelli, suppongo che abbia bisogno di un involucro speciale.

Ecco un’altra implementazione per il binding 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); } } } 

Utilizzo in xaml

         

Questa implementazione è specifica per vincolare Validation.HasError

Ecco un’altra soluzione di proprietà allegata basata su SizeObserver dettagliata qui Spingendo le proprietà della GUI di sola lettura in 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); } } 

Dichiarare la proprietà associata in controllo

  

WPF non utilizzerà il setter di proprietà CLR, ma sembra che abbia qualche convalida dispari basata su di esso.

Potrebbe essere nella tua situazione questo può essere ok:

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

Hmmm … Non sono sicuro di essere d’accordo con nessuna di queste soluzioni. Che ne dici di specificare un callback di coercizione nella registrazione delle proprietà che ignora le modifiche esterne? Ad esempio, avevo bisogno di implementare una proprietà di dipendenza Position di sola lettura per ottenere la posizione di un controllo MediaElement all’interno di un controllo utente. Ecco come l’ho fatto:

  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); } } 

In altre parole, ignora semplicemente la modifica e restituisce il valore supportato da un membro diverso che non ha un modificatore pubblico. – Nell’esempio precedente, MediaRenderer è in realtà il controllo MediaElement privato.

Stai facendo l’associazione nella direzione sbagliata in questo momento. OneWayToSource tenterà di aggiornare FlagIsModified sul contenitore ogni volta che IsModified cambia sul controllo che stai creando. Volete il contrario, che è quello di avere IsModified associare al contenitore. FlagIsModified. Per questo è necessario utilizzare la modalità di associazione OneWay

  

Elenco completo dei membri di enumerazione: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx