2012-12-12 25 views
7

tl; dr:強制的な値は、データバインディング全体に伝播されません。コードビハインドがバインディングのもう一方の側を知らないときに、データバインディングを介して更新を強制する方法はありますか?強制的な値の強制伝播


私は、WPFの依存関係プロパティにCoerceValueCallbackを使用していると私は、バインディングに介して伝播されません値を強要問題でこだわっています。

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つのユーザーコントロール、他の内部にネスト1、およびウィンドウ内のそれらの外側の。内部ユーザーコントロールには、Value依存プロパティがあり、これは外部コントロールの依存関係プロパティValueにバインドされています。このウィンドウでは、TextBox.Textプロパティが外部コントロールのValueプロパティにバインドされています。

内部コントロールにValueプロパティで登録されたCoerceValueCallbackプロパティがあります。これは、このValueプロパティにのみ偶数を割り当てることができます。

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

テキストボックスに偶数番号を書き込み、フォーカスを変更する場合(たとえば、ダミーボタンをテキストボックスの下に合わせる)、両方ともValueのプロパティが正しく更新されます。ただし、奇数をテキストボックスに書き込むと、内部コントロールのValueプロパティは変更されず、外部コントロールのValueプロパティとTextBox.Textプロパティの奇数が表示されます。

私の質問は:テキストボックス内の更新を強制するにはどうすればいいですか(外部コントロールのValueプロパティでも理想的です)。

私はan SO question on the same problemを見つけましたが、解決策は実際にはありません。それはプロパティ変更イベントハンドラを使って値をリセットすることを暗示しますが、私が見る限り、評価コードを外部コントロールに複製することを意味します...実際の評価コードは、情報は基本的に(多くの労力を要することなく)内部コントロールにのみ知られています。

また、this blogpostは、上記の暗示のように、最初のCoerceValueCallbackTextBox.Textで結合が、上UpdateTargetを呼び出す示唆して、私の内側の制御は、おそらく、テキストボックスについての知識を持つことができない、そして第二に、私はおそらく最初UpdateSourceを呼び出す必要があります内側のコントロールのプロパティーのバインドについて。しかし、私はそれを行う場所がわかりませんが、CoerceValueメソッドの中では、強制的な値はまだ設定されていません(バインディングを更新するには早すぎます)。値がCoerceValueでリセットされた場合、プロパティ値はそのまま残っているので、プロパティ変更コールバックは呼び出されません(this discussionにも暗示されています)。私は、従来のプロパティとINotifyPropertyChanged実装とSomeControlに依存プロパティを交換したと考えた

一つの可能​​な回避策は、(私は手動で値が強制されている場合でもPropertyChangedイベントをトリガすることができます)。しかし、これは、そのプロパティにバインディングを宣言することができないため、本当に便利なソリューションではありません。

+2

あなたがこの回答を見つけたら教えてください) –

答えて

3

私はしばらくの間、このかなり厄介なバグに対する答えを探していました。

  • あなたCoerceValueコールバックを削除します。バインディングにUpdateTargetはを強制することなく、それを行うに 一つの方法はこれです。
  • CoerceValueコールバックのロジックをProcessValueChangedコールバックに移行します。
  • あなたが二回ヒットさProcessValueChangedコールバックで終わるだろうが、あなたの強要値が効果的に押されてしまいます
  • 該当する場合(数が奇数の場合)、あなたのプロパティにご強要値を代入あなたの縛りに。あなたのコードの

ベース、あなたの依存関係プロパティの宣言は、このなる:

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回だけ発生します。

こちらがお役に立てば幸いです。

+0

有望 - 私はこれを少し時間をかけてチェックし、答えを受け入れるかどうかを決めますが、それまでには時間がかかるかもしれません。 –

+1

私はこれを試しましたが、それは私のために働いていませんでした。 –

関連する問題