代码之家  ›  专栏  ›  技术社区  ›  Inferis

XAML中只读属性的OneWayToSource绑定

  •  77
  • Inferis  · 技术社区  · 17 年前

    我正试着和一个 Readonly 财产 OneWayToSource 作为模式,但这似乎无法在XAML中完成:

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

    我得到:

    无法设置属性“FlagThingy.IsModified”,因为它没有可访问的集访问器。

    IsModified 是只读的 DependencyProperty 在…上 FlagThingy . 我想将该值绑定到 FlagIsModified

    要明确的是:

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

    这是否可能仅使用XAML?


    更新: 苍白的 . 但我还是想知道这是否可行。

    8 回复  |  直到 11 年前
        1
  •  47
  •   brunnerh    15 年前

    关于单向源的一些研究结果。。。

    选项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;
    }
    
    <控件:FlagThingy x:Name=“\u FlagThingy”/>
    
    // Binding Code
    Same binding code...
    

    反射器给出了答案:

    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");
        }
     ....
    
        2
  •  24
  •   Fredrik Hedblad    6 年前


    OneWayToSource binding from a readonly dependency property

    我提出了一个解决方案,可以动态地将只读依赖属性推送到名为 PushBinding 我 blogged about here . 下面的例子确实如此 OneWayToSource 来自只读DP的绑定 ActualWidth ActualHeight 设置的宽度和高度属性 DataContext

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

    图钉 通过使用两个依赖属性(侦听器和镜像)工作。听者被束缚了 OneWay 到TargetProperty,并在 PropertyChangedCallback 它将更新绑定的镜像属性 单向来源 绑定中指定的任何内容。

    Demo Project can be Downloaded Here.
    它包含源代码和简短的示例用法。

        3
  •  5
  •   Johan Larsson    10 年前

    他写道:

    用法:

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

    还没有测试过它的样式和模板,猜测它需要特殊的外壳。

        4
  •  2
  •   g t Omri Btian    6 年前

    下面是另一个基于SizeObserver的附加属性解决方案 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);
        }
    }
    

    声明控制中的附加属性

    <ListView ItemsSource="{Binding SomeGridItems}"                             
         ut:MouseObserver.Observe="True"
         ut:MouseObserver.ObservedMouseOver="{Binding IsMouseOverGrid, Mode=OneWayToSource}">    
    
        5
  •  1
  •   Johan Larsson    10 年前

    下面是绑定到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

        6
  •  0
  •   alex2k8    17 年前

    WPF不会使用CLR属性设置器,但似乎它会基于它进行一些奇怪的验证。

    可能在您的情况下,这没问题:

        public bool IsModified
        {
            get { return (bool)GetValue(IsModifiedProperty); }
            set { throw new Exception("An attempt ot modify Read-Only property"); }
        }
    
        7
  •  0
  •   TeaDrivenDev    11 年前

    六羟甲基三聚氰胺六甲醚。。。我不确定我是否同意这些解决方案。在属性注册中指定忽略外部更改的强制回调如何?例如,我需要实现一个只读位置依赖属性来获取用户控件中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控件。

        8
  •  0
  •   John Thoits    6 年前

    我绕过这个限制的方法是在我的类中只公开一个绑定属性,使dependencProperty完全私有。我实现了一个“PropertyBindingToSource”只写属性(这个属性不是DependencyProperty),可以在xaml中设置为绑定值。在这个只写属性的setter中,我调用BindingOperations.SetBinding将绑定链接到DependencyProperty。

    对于OP的具体示例,它如下所示:

    扁平化的实现:

    public partial class FlatThingy : UserControl
    {
        public FlatThingy()
        {
            InitializeComponent();
        }
    
        public Binding IsModifiedBindingToSource
        {
            set
            {
                if (value?.Mode != BindingMode.OneWayToSource)
                {
                    throw new InvalidOperationException("IsModifiedBindingToSource must be set to a OneWayToSource binding");
                }
    
                BindingOperations.SetBinding(this, IsModifiedProperty, value);
            }
        }
    
        public bool IsModified
        {
            get { return (bool)GetValue(IsModifiedProperty); }
            private set { SetValue(IsModifiedProperty, value); }
        }
    
        private static readonly DependencyProperty IsModifiedProperty =
            DependencyProperty.Register("IsModified", typeof(bool), typeof(FlatThingy), new PropertyMetadata(false));
    
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            IsModified = !IsModified;
        }
    }
    

    请注意,静态只读DependencyProperty对象是私有的。在控件中,我添加了一个按钮,该按钮的单击由button\u click处理。 在my window.xaml中使用FlatThingy控件:

    <Window x:Class="ReadOnlyBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ReadOnlyBinding"
        mc:Ignorable="d"
        DataContext="{x:Static local:ViewModel.Instance}"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
    
        <TextBlock Text="{Binding FlagIsModified}" Grid.Row="0" />
        <local:FlatThingy IsModifiedBindingToSource="{Binding FlagIsModified, Mode=OneWayToSource}" Grid.Row="1" />
    </Grid>
    

    注意,我还实现了一个用于绑定到的ViewModel,这里没有显示。它公开了一个名为“FlagIsModified”的DependencyProperty,您可以从上面的源代码中获得它。

    它工作得很好,允许我以松散耦合的方式将信息从视图推回ViewModel,并明确定义信息流的方向。

        9
  •  -1
  •   JaredPar    17 年前

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

    枚举成员的完整列表: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx