OneWayToSource从XAML中的readonly属性绑定
我试图绑定到OneWayToSource
作为模式的Readonly
属性,但似乎这不能在XAML中完成:
<controls:FlagThingy IsModified="{Binding FlagIsModified, ElementName=container, Mode=OneWayToSource}" />
我得到:
无法设置属性“FlagThingy.IsModified”,因为它没有可访问的set访问器。
IsModified
是FlagThingy
上的只读DependencyProperty
。 我想将该值绑定到容器上的FlagIsModified
属性。
要清楚:
FlagThingy.IsModified --> container.FlagIsModified ------ READONLY ----- ----- READWRITE --------
这可能只使用XAML?
更新:好吧,我通过在容器上设置绑定来解决这个问题,而不是在FlagThingy
。 但我仍然想知道这是否可能。
OneWayToSource的一些研究结果…
选项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);
选项#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}" />
选项#3(真正的只读依赖项属性)
System.ArgumentException:“IsModified”属性不能被数据绑定。
// 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...
reflection板给出了答案:
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"); } ....
这是WPF的一个限制,它是通过devise的。 这里报道Connect:
OneWayToSource从只读依赖项属性绑定
我做了一个解决scheme,dynamic地将只读依赖项属性推PushBinding
我在这里所说的PushBinding
源码。 下面的示例将OneWayToSource
绑定从只读DP的ActualWidth
和ActualHeight
到DataContext
的Width和Height属性
<TextBlock Name="myTextBlock"> <pb:PushBindingManager.PushBindings> <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/> <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/> </pb:PushBindingManager.PushBindings> </TextBlock>
PushBinding
通过使用两个依赖属性,侦听器和镜像来工作。 监听器绑定到TargetProperty,在PropertyChangedCallback
它将绑定OneWayToSource的Mirror属性更新为Binding中指定的值。
演示项目可以在这里下载。
它包含源代码和简短的示例用法,或者如果您对实现细节感兴趣,请访问我的WPF博客 。
写这个:
用法:
<TextBox Text="{Binding Text}" p:OneWayToSource.Bind="{p:Paths From={x:Static Validation.HasErrorProperty}, To=SomeDataContextProperty}" />
码:
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); } }
还没有在样式和模板testing,猜猜它需要特殊的shell。
这是绑定到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); } } }
在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>
该实现特定于绑定Validation.HasError
这里是另一个基于SizeObserver的附加属性解决scheme,在这里详细介绍将只读GUI属性重新放回到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); } }
在控制中声明附属财产
<ListView ItemsSource="{Binding SomeGridItems}" ut:MouseObserver.Observe="True" ut:MouseObserver.ObservedMouseOver="{Binding IsMouseOverGrid, Mode=OneWayToSource}">
WPF不会使用CLR属性设置器,但似乎基于它做了一些奇怪的validation。
可能在你的情况下,这可以确定:
public bool IsModified { get { return (bool)GetValue(IsModifiedProperty); } set { throw new Exception("An attempt ot modify Read-Only property"); } }
嗯…我不确定我是否同意这些解决scheme。 如何在你的财产登记中指定强制callback,忽略外部变更? 例如,我需要实现一个只读的位置依赖项属性来获取用户控件中的MediaElement控件的位置。 以下是我如何做到的:
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); } }
换句话说,只需忽略更改并返回不具有公共修饰符的其他成员支持的值。 – 在上面的例子中,MediaRenderer实际上是私有的MediaElement控件。
你现在正在做错误的方向。 OneWayToSource将尝试更新容器上的FlagIsModified,只要IsModified在您正在创build的控件上发生更改。 你想要相反,这是IsModified绑定到container.FlagIsModified。 为此,您应该使用绑定模式OneWay
<controls:FlagThingy IsModified="{Binding FlagIsModified, ElementName=container, Mode=OneWay}" />
枚举成员的完整列表: http : //msdn.microsoft.com/zh-cn/library/system.windows.data.bindingmode.aspx