2009-03-18 14 views
71

Sto cercando di legarsi a una proprietà Readonly con OneWayToSource come modalità, ma sembra che questo non può essere fatto in XAML:OneWayToSource vincolante da proprietà di sola lettura in XAML

<controls:FlagThingy IsModified="{Binding FlagIsModified, 
              ElementName=container, 
              Mode=OneWayToSource}" /> 

ottengo:

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

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

Per essere chiari:

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

Questo è possibile utilizzando solo XAML?


Aggiornamento: Beh, ho risolto questo caso impostando il legame sul contenitore e non sul FlagThingy. Ma mi piacerebbe ancora sapere se questo è possibile.

+0

Ma come è possibile impostare il valore a una proprietà di sola lettura? – idursun

+2

Non puoi. Non è nemmeno quello che sto cercando di ottenere. Sto provando a ottenere DA proprietà readonly 'IsModified' per leggere la proprietà' FlagIsModified'. – Inferis

+0

Buona domanda. La soluzione alternativa funziona solo se il contenitore è DependencyObject e FlagIsModified è DependencyProperty. –

risposta

40

Alcuni risultati della 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()); 
} 
<controls:FlagThingy x:Name="_flagThingy" /> 
// 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"); } 
    } 
} 
<controls:FlagThingy IsModified="{Binding Path=FlagIsModified, 
    ElementName=container, Mode=OneWayToSource}" /> 

Opzione # 3 (True sola lettura proprietà di dipendenza)

System.ArgumentException: la proprietà 'IsModified' non può essere associato a 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; 
} 
<controls:FlagThingy x:Name="_flagThingy" /> 
// Binding Code 
Same binding code... 

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

Quindi questo è un bug, in realtà. – Inferis

+0

Bella ricerca. Se non l'avessi esposto così bene qui, avrei camminato su quella stessa strada dolorosa. D'accordo con @Inferis. – kevinarpe

+0

Si tratta di un bug? Perché un binding OneWayToSource non dovrebbe essere consentito con DependencyProperty di sola lettura? –

0

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

può essere nella vostra situazione questo può essere ok:

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

La proprietà CLR non è utilizzata in questo caso. – Inferis

+0

Vuoi dire che hai appena definito DependencyProperty ed è stato in grado di scrivere ? Per me dice: "La proprietà 'IsModified' non esiste nello spazio dei nomi XML" se non aggiungo la proprietà CLR. – alex2k8

+1

Credo che il tempo di progettazione utilizzi le proprietà clr dove il runtime in realtà va direttamente alla proprietà di dipendenza (se è una). – meandmycode

-1

Stai facendo il legame 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 si consiglia di utilizzare la modalità di OneWay vincolante

<controls:FlagThingy IsModified="{Binding FlagIsModified, 
              ElementName=container, 
              Mode=OneWay}" /> 

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

+3

No, voglio esattamente lo scenario che descrivi che non voglio fare. FlagThingy.IsModified -> container.FlagIsModified – Inferis

+3

Essere contrassegnati perché l'interrogante ha una domanda ambigua sembra un po 'eccessivo. – JaredPar

+4

@JaredPar: Non vedo cosa sia ambiguo riguardo alla domanda. La domanda afferma che 1) esiste una proprietà di dipendenza di sola lettura 'IsIsModified', che 2) l'OP vuole dichiarare un'associazione su quella proprietà in XAML e che 3) l'associazione dovrebbe funzionare nella modalità' OneWayToSource'. La tua soluzione non funziona praticamente perché, come descritto nella domanda, il compilatore non ti consente di dichiarare un'associazione su una proprietà di sola lettura, e non funziona concettualmente perché 'IsModified' è di sola lettura e quindi la sua il valore non può essere modificato (dal binding). –

18

Questa è una limitazione di WPF ed è in base alla progettazione. E 'riportata qui Connect:
OneWayToSource binding from a readonly dependency property

Ho fatto una soluzione per essere dinamicamente in grado di spingere sola lettura dipendenza proprietà alla sorgente chiamato PushBinding quale blogged about here. L'esempio che segue fa OneWayToSource binding dalla sola lettura DP di ActualWidth e ActualHeight alla proprietà Width e Height delle DataContext

<TextBlock Name="myTextBlock"> 
    <pb:PushBindingManager.PushBindings> 
     <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/> 
     <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/> 
    </pb:PushBindingManager.PushBindings> 
</TextBlock> 

PushBinding opere utilizzando due proprietà di dipendenza, ascoltatore e Mirror. L'ascoltatore è associato a OneWay nella TargetProperty e nello PropertyChangedCallback aggiorna la proprietà Mirror che è associata a OneWayToSource a qualsiasi cosa specificata nel Binding.

Demo Project can be Downloaded Here.
Esso contiene il codice sorgente e l'uso del campione breve, oppure visitare my WPF blog se siete interessati ai dettagli di implementazione.

+0

Interessante! Ho trovato una soluzione simile e l'ho chiamata "Conduit": il Conduit aveva due proprietà di dipendenza come da progetto e due binding separati. Il caso d'uso che avevo era quello di legare vecchie proprietà semplici a semplici vecchie proprietà in XAML. –

+2

Vedo che il tuo collegamento MS Connect non funziona più. Significa che MS l'ha corretto nella nuova versione di .NET o l'hanno appena cancellato? – Tiny

+0

Sembra non essere risolto il 2015-06-04. con .Net 4.5 –

0

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 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, semplicemente ignorare la modifica e tornare al valore sostenuto da un membro diverso che non ha un modificatore pubblico. - Nell'esempio precedente, MediaRenderer è in realtà il controllo MediaElement privato.

+0

Peccato che questo non funzioni per le proprietà predefinite delle classi BCL: - / –

5

scritto questo:

Usage:

<TextBox Text="{Binding Text}" 
     p:OneWayToSource.Bind="{p:Paths From={x:Static Validation.HasErrorProperty}, 
             To=SomeDataContextProperty}" /> 

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 hanno ancora testato in stili e modelli, immagino che ha bisogno di involucro speciale.

1

Ecco un'altra soluzione proprietà associata sulla base di SizeObserver dettagliato qui Pushing read-only GUI properties back into 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); 
    } 
} 

Declare proprietà associata in controllo

<ListView ItemsSource="{Binding SomeGridItems}"        
    ut:MouseObserver.Observe="True" 
    ut:MouseObserver.ObservedMouseOver="{Binding IsMouseOverGrid, Mode=OneWayToSource}">  
1

Ecco un'altra implementazione per il legame alla convalida.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 in XAML

<StackPanel> 
    <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"> 
     <local:OneWayToSource.Bindings> 
      <local:OneWayToSourceBindings HasError="{Binding HasError}" /> 
     </local:OneWayToSource.Bindings> 
    </TextBox> 
    <CheckBox IsChecked="{Binding HasError, Mode=OneWay}" /> 
</StackPanel> 

Questa implementazione è specifico di legame Validation.HasError

Problemi correlati