tl;dr: 強制された値は、データ バインディング間で伝播されません。コード ビハインドがバインディングの反対側を認識していない場合、データ バインディング全体で更新を強制するにはどうすればよいですか?
私はCoerceValueCallback
WPF 依存関係プロパティで を使用していますが、強制された値がバインディングに反映されないという問題で立ち往生しています。
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
登録されています。Value
Value
このコードは、デモンストレーションのために単純化されていることに注意してください。実際のバージョンでは、コンストラクターで何も初期化しません。2 つのコントロールには、実際には、それぞれのコンストラクターで行われるすべてのことを行うコントロール テンプレートがあります。つまり、実際のコードでは、外側のコントロールは内側のコントロールを認識していません。
テキスト ボックスに偶数を書き込んでフォーカスを変更すると (たとえば、テキスト ボックスの下にあるダミー ボタンにフォーカスすることによって)、両方のValue
プロパティが適切に更新されます。ただし、テキスト ボックスに奇数を書き込むValue
と、内側のコントロールのプロパティは変化しませんがValue
、外側のコントロールのTextBox.Text
プロパティとプロパティは奇数を示します。
私の質問は次のとおりです:テキスト ボックス (そして、理想的には、外側のコントロールのプロパティでも更新を強制するにはどうすればよいですか?)Value
同じ問題についてSOの質問を見つけましたが、実際には解決策を提供していません。プロパティが変更されたイベントハンドラーを使用して値をリセットすることを暗示していますが、私が見る限り、それは評価コードを外部コントロールに複製することを意味します...実際の評価コードはいくつかに依存しているため、これは実際には実行可能ではありません情報は基本的に内部コントロールにのみ (多くの努力なしに) 知られています。
さらに、このブログ投稿UpdateTarget
では のバインディングを呼び出すことを提案しTextBox.Text
てCoerceValueCallback
いますが、まず、上記で暗示されているように、私の内部コントロールはテキスト ボックスに関する知識を持っている可能性はありません。次に、プロパティのUpdateSource
バインディングを最初に呼び出す必要があります。Value
内部コントロール。ただし、CoerceValue
メソッド内では、強制された値はまだ設定されていないため (バインディングを更新するには時期尚早です) CoerceValue
、プロパティ値そのため、プロパティが変更されたコールバックは呼び出されません (この説明でも暗示されているように)。
SomeControl
私が考えた 1 つの可能な回避策は、依存関係プロパティを従来のプロパティとINotifyPropertyChanged
実装に置き換えることでした(PropertyChanged
値が強制された場合でも、イベントを手動でトリガーできます)。ただし、これは、そのプロパティでバインドを宣言できなくなることを意味するため、実際には有用な解決策ではありません。