XAMLの読み取り専用プロパティからのOneWayToSourceバインディング


87

asモードでReadonlyプロパティにバインドしようとしてOneWayToSourceいますが、XAMLではこれを実行できないようです。

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

私は得る:

プロパティ 'FlagThingy.IsModified'には、アクセス可能なセットアクセサーがないため、設定できません。

IsModified読み取り専用であるDependencyPropertyFlagThingy。その値をFlagIsModifiedコンテナのプロパティにバインドしたいと思います。

明確にするために:

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

これはXAMLだけを使用して可能ですか?


更新:まあ、私はこのケースを修正しましたFlagThingy。バインディングをコンテナではなくコンテナに設定しました。しかし、これが可能かどうかはまだ知りたいです。


しかし、どうすれば読み取り専用プロパティに値を設定できますか?
idursun 2009年

3
できません。それは私が達成しようとしていることでもありません。FROM読み取り専用プロパティIsModifiedから読み取り/書き込みプロパティを取得しようとしていますFlagIsModified
インフェリス

良い質問。回避策は、コンテナがDependencyObjectであり、FlagIsModifiedがDependencyPropertyである場合にのみ機能します。
Josh G

10
素晴らしい質問ですが、受け入れられた答えを理解できません。WPFの第一人者が私にもう少し教えてくれたら幸いです-これはバグですか、それとも設計ごとですか?
オスカー

@Oskarこのそれはバグです。しかし、修正は見えません。
user1151923 2013

回答:


45

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...

リフレクターは答えを与えます:

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

30
実際、これはバグです。
インフェリス

素晴らしい研究。ここにうまくレイアウトしていなかったら、私は同じ、つらい道を歩いていただろう。@Inferisに同意します。
kevinarpe 2012年

1
これはバグですか?OneWayToSourceバインディングが読み取り専用のDependencyPropertyで許可されないのはなぜですか?
アレックスホープオコナー

これはバグではありません。これは設計によるものであり、十分に文書化されています。これは、バインディングエンジンが依存関係プロパティシステムと連携して機能する方法によるものです(バインディングターゲットDependencyPropertyDPである必要あります)。読み取り専用DPは、関連するを使用してのみ変更できDependencyPropertyKeyます。BindingExpressionエンジンを登録するには、ターゲットDPのメタデータを操作する必要があります。DependencyPropertyKeyパブリック書き込み保護を保証するためにプライベートと見なされるため 、エンジンはこのキーを無視する必要があり、その結果、読み取り専用DPにバインディングを登録できなくなります。
BionicCode

23

これはWPFの制限であり、仕様によるものです。ここでConnectについて報告されています:
読み取り専用の依存関係プロパティからのOneWayToSourceバインディング

ここでブログPushBinding書いたソースに読み取り専用の依存関係プロパティを動的にプッシュできるようにするソリューションを作成しました。以下の例ではありませんOneWayToSource、読み取り専用DPのからバインディングをActualWidthActualHeightの幅と高さプロパティにDataContext

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

PushBindingリスナーとミラーの2つの依存関係プロパティを使用して機能します。リスナーはOneWayTargetPropertyにバインドされ、その中で、Bindingで指定されたものにバインドされてPropertyChangedCallbackいるMirrorプロパティを更新しますOneWayToSource

デモプロジェクトはこちらからダウンロードできます。
ソースコードと短いサンプルの使用法が含まれています。


面白い!私は同様の解決策を考え出し、それを「コンジット」と呼びました。コンジットには、設計に応じて2つの依存関係プロパティと、2つの個別のバインディングがありました。私が持っていたユースケースは、XAMLのプレーンな古いプロパティをプレーンな古いプロパティにバインドすることでした。
ダニエルポール2012

3
MSConnectリンクが機能しなくなったようです。MSが新しいバージョンの.NETで修正したのか、それとも削除しただけなのか。
小さな

@Tiny Connectは、残念ながら最終的には放棄されたようです。それは多くの場所でリンクされていました。問題が修正されたかどうかについては、具体的には何も意味しないと思います。
UuDdLrLrSs

私はこれを正確に書き込もうとしていました。よくできました!
aaronburro

5

これを書いた:

使用法:

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

スタイルとテンプレートでまだテストしていません。特別なケーシングが必要だと思います。


2

ここで詳しく説明されているSizeObserverに基づく別の添付プロパティソリューションを次に示します。読み取り専用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}">    

1

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


0

WPFはCLRプロパティセッターを使用しませんが、それに基づいて奇妙な検証を行うようです。

あなたの状況にあるかもしれませんこれは大丈夫かもしれません:

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

1
この場合、CLRプロパティは使用されません。
インフェリス

DependencyPropertyを定義し、<controls:FlagThingy IsModified = "..." />を記述できたということですか?私の場合、CLRプロパティを追加しないと、「プロパティ 'IsModified'はXML名前空間に存在しません」と表示されます。
alex2k8 2009年

1
設計時にはclrプロパティが使用され、ランタイムは実際には依存関係プロパティ(存在する場合)に直接移動すると思います。
meandmycode 2009年

私の場合(コードからIsModifiedを使用していません)、CLRプロパティは不要ですが、それでも(パブリックセッターのみを使用して)存在します。設計時と実行時の両方が、dependencyproperty登録だけで正常に機能します。
インフェリス

バインディング自体はCLRプロパティを使用していませんが、XAMLでバインディングを定義する場合は、コードに変換する必要があります。この段階で、XAMLパーサーは、IsModifiedプロパティが読み取り専用であり、(バインディングが作成される前であっても)例外をスローすることを確認していると思います。
alex2k8 2009年

0

うーん...これらの解決策のいずれかに同意するかどうかはわかりません。プロパティ登録で、外部の変更を無視する強制コールバックを指定するのはどうですか?たとえば、ユーザーコントロール内のMediaElementコントロールの位置を取得するには、読み取り専用のPosition依存関係プロパティを実装する必要がありました。これが私がそれをした方法です:

    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コントロールです。


残念ながら、これはBCLクラスの事前定義されたプロパティでは機能しません:-/
またはマッパー2014年

0

この制限を回避する方法は、クラス内のBindingプロパティのみを公開し、DependencyPropertyを完全にプライベートに保つことでした。xamlのバインディング値に設定できる「PropertyBindingToSource」書き込み専用プロパティ(これはDependencyPropertyではありません)を実装しました。この書き込み専用プロパティのセッターで、BindingOperations.SetBindingを呼び出して、バインディングをDependencyPropertyにリンクします。

OPの特定の例では、次のようになります。

FlatThingyの実装:

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オブジェクトはプライベートであることに注意してください。コントロールに、ClickがButton_Clickによって処理されるボタンを追加しました。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に情報をプッシュバックできます。


-1

あなたは今、間違った方向にバインディングを行っています。OneWayToSourceは、作成しているコントロールでIsModifiedが変更されるたびに、コンテナーでFlagIsModifiedを更新しようとします。反対に、IsModifiedをcontainer.FlagIsModifiedにバインドする必要があります。そのためには、バインディングモードOneWayを使用する必要があります

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

列挙メンバーの完全なリスト:http//msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx


5
いいえ、私はあなたが説明した、私がやりたくないシナリオを正確に望んでいます。FlagThingy.IsModified-> container.FlagIsModified
Inferis

3
質問者があいまいな質問をしたためにマークダウンされるのは少しやり過ぎのようです。
JaredPar 2009年

6
@JaredPar:質問について何が曖昧なのかわかりません。質問には、1)読み取り専用の依存関係プロパティがIsIsModifiedあり、2)OPがXAMLでそのプロパティのバインディングを宣言したい、3)バインディングがOneWayToSourceモードで機能することになっていることが示されています。質問で説明されているように、コンパイラは読み取り専用プロパティのバインディングを宣言できないため、ソリューションは実際には機能しません。また、IsModifiedは読み取り専用であるため、その値は機能しないため、概念的には機能しません。 (バインディングによって)変更されました。
またはマッパー2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.