2012-12-12 10 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 속성에 바인딩됩니다.

내부 컨트롤의 Value 속성에 CoerceValueCallback 속성이 등록되어 있으며이 속성은 Value 속성에만 짝수를 할당 할 수 있습니다.

이 코드는 데모 용으로 단순화되어 있습니다. 실제 버전은 생성자에서 아무 것도 초기화하지 않습니다. 두 컨트롤에는 실제로 각 생성자에서 수행되는 모든 작업을 수행하는 컨트롤 템플릿이 있습니다. 즉, 실제 코드에서 외부 컨트롤은 내부 컨트롤을 알지 못합니다.

텍스트 상자에 짝수를 쓰고 포커스를 변경하면 (예 : 텍스트 상자 아래에있는 더미 버튼에 초점 맞추기) Value 속성이 정식으로 업데이트됩니다. 그러나 홀수를 텍스트 상자에 쓸 때 외부 컨트롤의 Value 속성과 TextBox.Text 속성은 홀수를 표시하는 반면 내부 컨트롤의 Value 속성은 변경되지 않습니다.

내 질문 : 텍스트 상자에서 업데이트를 강제로 수행 할 수 있습니까 (이상적으로 외부 컨트롤의 Value 속성에도 적용 할 수 있습니다).

나는 an SO question on the same problem을 찾았지만 해결책을 제시하지 못했습니다. 그것은 속성을 변경하는 이벤트 핸들러를 사용하여 값을 재설정하는 것을 암시하지만, 알 수있는 한, 이는 외부 컨트롤에 평가 코드를 복제하는 것을 의미합니다. 실제 평가 코드는 일부 코드에 의존하기 때문에 실제로 실행 가능하지 않습니다. 정보는 근본적으로 (많은 노력없이) 내부 통제에만 알려졌다.

또한, this blogpost 위의 암시대로 먼저 CoerceValueCallbackTextBox.Text 바인딩 만에 UpdateTarget를 호출 제안, 내 내부 통제 가능성이 텍스트 상자에 대한 지식이 없으며, 둘째, 나는 아마 처음 UpdateSource 전화를 할 것입니다 내부 컨트롤의 속성 인 Value의 바인딩그러나, 그 값을 CoerceValue으로 리셋하는 경우에는 CoerceValue 메서드 내에서 강제 설정 값이 아직 설정되지 않았습니다 (바인딩을 업데이트하기에는 너무 빠름). 속성 값은 그대로 남아있게되므로 속성 변경 콜백이 호출되지 않습니다 (this discussion에 함축 됨). I는 통상의 특성 및 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이 연속으로 두 번 호출됩니다. else 문을두면 이벤트를 유효한 값으로 한 번만 올립니다.

도움이 되었기를 바랍니다.

+0

유망한 - 언젠가 이것을 확인하고 그 대답을 받아 들일지를 결정할 것입니다.하지만 그때까지는 시간이 걸릴 수 있습니다. . –

+1

나는 이것을 시험해 보았고 나를 위해 일하지 않았다. –