10

tl;dr: 強制された値は、データ バインディング間で伝播されません。コード ビハインドがバインディングの反対側を認識していない場合、データ バインディング全体で更新を強制するにはどうすればよいですか?


私はCoerceValueCallbackWPF 依存関係プロパティで を使用していますが、強制された値がバインディングに反映されないという問題で立ち往生しています。

Window1.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace CoerceValueTest
{
    public class SomeControl : UserControl
    {
        public SomeControl()
        {
            StackPanel sp = new StackPanel();

            Button bUp = new Button();
            bUp.Content = "+";
            bUp.Click += delegate(object sender, RoutedEventArgs e) {
                Value += 2;
            };

            Button bDown = new Button();
            bDown.Content = "-";
            bDown.Click += delegate(object sender, RoutedEventArgs e) {
                Value -= 2;
            };

            TextBlock tbValue = new TextBlock();
            tbValue.SetBinding(TextBlock.TextProperty,
                               new Binding("Value") {
                                Source = this
                               });

            sp.Children.Add(bUp);
            sp.Children.Add(tbValue);
            sp.Children.Add(bDown);

            this.Content = sp;
        }

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
                                                                                              typeof(int),
                                                                                              typeof(SomeControl),
                                                                                              new PropertyMetadata(0, ProcessValueChanged, CoerceValue));

        private static object CoerceValue(DependencyObject d, object baseValue)
        {
            if ((int)baseValue % 2 == 0) {
                return baseValue;
            } else {
                return DependencyProperty.UnsetValue;
            }
        }

        private static void ProcessValueChanged(object source, DependencyPropertyChangedEventArgs e)
        {
            ((SomeControl)source).ProcessValueChanged(e);
        }

        private void ProcessValueChanged(DependencyPropertyChangedEventArgs e)
        {
            OnValueChanged(EventArgs.Empty);
        }

        protected virtual void OnValueChanged(EventArgs e)
        {
            if (e == null) {
                throw new ArgumentNullException("e");
            }

            if (ValueChanged != null) {
                ValueChanged(this, e);
            }
        }

        public event EventHandler ValueChanged;

        public int Value {
            get {
                return (int)GetValue(ValueProperty);
            }
            set {
                SetValue(ValueProperty, value);
            }
        }
    }

    public class SomeBiggerControl : UserControl
    {
        public SomeBiggerControl()
        {
            Border parent = new Border();
            parent.BorderThickness = new Thickness(2);
            parent.Margin = new Thickness(2);
            parent.Padding = new Thickness(3);
            parent.BorderBrush = Brushes.DarkRed;

            SomeControl ctl = new SomeControl();
            ctl.SetBinding(SomeControl.ValueProperty,
                           new Binding("Value") {
                            Source = this,
                            Mode = BindingMode.TwoWay
                           });
            parent.Child = ctl;

            this.Content = parent;
        }

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
                                                                                              typeof(int),
                                                                                              typeof(SomeBiggerControl),
                                                                                              new PropertyMetadata(0));

        public int Value {
            get {
                return (int)GetValue(ValueProperty);
            }
            set {
                SetValue(ValueProperty, value);
            }
        }
    }

    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
    }
}

Window1.xaml

<Window x:Class="CoerceValueTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="CoerceValueTest" Height="300" Width="300"
    xmlns:local="clr-namespace:CoerceValueTest"
    >
    <StackPanel>
        <local:SomeBiggerControl x:Name="sc"/>
        <TextBox Text="{Binding Value, ElementName=sc, Mode=TwoWay}" Name="tb"/>
        <Button Content=" "/>
    </StackPanel>
</Window>

つまり、2 つのユーザー コントロールがあり、一方が他方の内側にネストされ、外側がウィンドウ内にあります。内側のユーザー コントロールには、外側のコントロールの依存関係プロパティにValueバインドされている依存関係プロパティがあります。Valueウィンドウでは、プロパティが外側のコントロールTextBox.Textのプロパティにバインドされます。Value

内部コントロールには、このプロパティに偶数のみを割り当てることができるという効果を持つプロパティがCoerceValueCallback登録されています。ValueValue

このコードは、デモンストレーションのために単純化されていることに注意してください。実際のバージョンでは、コンストラクターで何も初期化しません。2 つのコントロールには、実際には、それぞれのコンストラクターで行われるすべてのことを行うコントロール テンプレートがあります。つまり、実際のコードでは、外側のコントロールは内側のコントロールを認識していません。

テキスト ボックスに偶数を書き込んでフォーカスを変更すると (たとえば、テキスト ボックスの下にあるダミー ボタンにフォーカスすることによって)、両方のValueプロパティが適切に更新されます。ただし、テキスト ボックスに奇数を書き込むValueと、内側のコントロールのプロパティは変化しませんがValue、外側のコントロールのTextBox.Textプロパティとプロパティは奇数を示します。

私の質問は次のとおりです:テキスト ボックス (そして、理想的には、外側のコントロールのプロパティでも更新を強制するにはどうすればよいですか?)Value

同じ問題についてSOの質問を見つけましたが、実際には解決策を提供していません。プロパティが変更されたイベントハンドラーを使用して値をリセットすることを暗示していますが、私が見る限り、それは評価コードを外部コントロールに複製することを意味します...実際の評価コードはいくつかに依存しているため、これは実際には実行可能ではありません情報は基本的に内部コントロールにのみ (多くの努力なしに) 知られています。

さらに、このブログ投稿UpdateTargetでは のバインディングを呼び出すことを提案しTextBox.TextCoerceValueCallbackいますが、まず、上記で暗示されているように、私の内部コントロールはテキスト ボックスに関する知識を持っている可能性はありません。次に、プロパティのUpdateSourceバインディングを最初に呼び出す必要があります。Value内部コントロール。ただし、CoerceValueメソッド内では、強制された値はまだ設定されていないため (バインディングを更新するには時期尚早です) CoerceValue、プロパティ値そのため、プロパティが変更されたコールバックは呼び出されません (この説明でも暗示されているように)。

SomeControl私が考えた 1 つの可能な回避策は、依存関係プロパティを従来のプロパティとINotifyPropertyChanged実装に置き換えることでした(PropertyChanged値が強制された場合でも、イベントを手動でトリガーできます)。ただし、これは、そのプロパティでバインドを宣言できなくなることを意味するため、実際には有用な解決策ではありません。

4

1 に答える 1

4

私はしばらくの間、このかなり厄介なバグに対する答えを探していました。バインディングで UpdateTarget を強制する必要なく、これを行う 1 つの方法は次のとおりです。

  • CoerceValueコールバックを削除します。
  • CoerceValueコールバックのロジックをProcessValueChangedコールバックにシフトします。
  • 該当する場合 (数値が奇数の場合)、強制された値をValueプロパティに割り当てます。
  • ProcessValueChangedコールバックが 2 回ヒットすることになりますが、強制された値はバインディングに効果的にプッシュされます。

コードに基づいて、依存関係プロパティの宣言は次のようになります。

public static readonly DependencyProperty ValueProperty = 
                       DependencyProperty.Register("Value",
                                                   typeof(int),
                                                   typeof(SomeControl),
                                                   new PropertyMetadata(0, ProcessValueChanged, null));

そして、 ProcessValueChangedは次のようになります。

private static void ProcessValueChanged(object source, DependencyPropertyChangedEventArgs e)
    {
        int baseValue = (int) e.NewValue;
        SomeControl someControl = source as SomeControl;
        if (baseValue % 2 != 0) 
        {
            someControl.Value = DependencyProperty.UnsetValue;
        }
        else
        {
            someControl.ProcessValueChanged(e);
        }
    }

値を強制する必要があるときにイベントが発生しないように、ロジックを少し変更しました。前に述べたように、強制された値をsomeControl.Valueに割り当てると、 ProcessValueChangedが連続して 2 回呼び出されます。else ステートメントを配置すると、有効な値を持つイベントが 1 回だけ発生します。

これが役立つことを願っています!

于 2013-08-28T14:18:10.403 に答える