2012-12-12 38 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> 

即,兩個用戶控件,一個嵌套在另一個內,和那些在一個窗口的外層一個。內部用戶控件具有Value依賴項屬性,該屬性綁定到外部控件的依賴項屬性Value。在窗口中,TextBox.Text屬性綁定到外部控件的Value屬性。

內部控制有一個CoerceValueCallback註冊其Value屬性,其效果是這個Value屬性只能分配偶數。

請注意,爲了演示目的,此代碼被簡化。真正的版本不會在構造函數中初始化任何東西;這兩個控件實際上都有控制模板,它們完成了這裏各個構造函數中所做的所有事情。也就是說,在真實的代碼中,外部控制不知道內部控制。

當向文本框中寫入偶數並更改焦點(例如,通過將文本框下面的虛擬按鈕聚焦)時,兩個Value屬性都會得到適時更新。但是,在向文本框中寫入奇數時,內部控件的Value屬性不會更改,而外部控件的Value屬性以及TextBox.Text屬性顯示奇數。

我的問題是:如何在文本框中強制更新(理想情況下還在外部控件的Value屬性中,而我們在此)?

我發現an SO question on the same problem,但並沒有真正提供解決方案。它暗示使用屬性更改事件處理程序來重置值,但據我所知,這意味着將評估代碼複製到外部控制...這不是真正可行的,因爲我的實際評估代碼依賴於某些基本上只知道(沒有太多努力)內部控制的信息。

此外,this blogpost建議在TextBox.TextCoerceValueCallback的結合,但首先調用UpdateTarget,如上提示過,我內心的控制不可能有關於文本框的任何知識,第二,我可能會叫UpdateSource第一對內部控制的屬性的綁定。但我不知道該怎麼做,因爲在CoerceValue方法中,強制值尚未設置(因此更新綁定還爲時過早),而在值爲CoerceValue的情況下,屬性值將保持原來的狀態,因此屬性更改回調將不會被調用(也暗示在this discussion中)。

一種可能的解決方法我曾認爲用常規的特性和INotifyPropertyChanged執行替換SomeControl依賴項屬性的(所以我可以手動觸發即使該值已經被裹挾着PropertyChanged事件)。但是,這意味着我不能再聲明對該屬性的綁定,所以它不是一個真正有用的解決方案。

+2

讓我知道,如果你找到這個答案=) –

回答

3

我一直在尋找這個相當討厭的bug的答案。 一種方式做到這一點,而無需迫使綁定的UpdateTarget是這樣的:

  • 刪除您CoerceValue回調。
  • 卻將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連續調用兩次。放入else語句只會引發一次有效值的事件。

我希望這有助於!

+0

有前途 - 我會檢查一下這段時間,然後決定我是否接受答案,但因爲它可能需要一段時間,直到現在,+1現在。 –

+1

我試過了,它沒有爲我工作。 –