2012-12-12 40 views
7

tl; dr: Giá trị bị chặn không được lan truyền qua các ràng buộc dữ liệu. Làm thế nào tôi có thể buộc các cập nhật trên các ràng buộc dữ liệu khi code-behind không biết phía bên kia của ràng buộc?Tuyên truyền lực lượng giá trị bị ràng buộc


Tôi đang sử dụng một CoerceValueCallback trên một tài sản phụ thuộc WPF và tôi bị mắc kẹt tại các vấn đề mà ép buộc các giá trị không được tuyên truyền thông qua các cam kết ràng buộc.

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> 

tức là hai người dùng điều khiển, một lồng vào bên trong người kia, và một trong những bên ngoài của những người trong một cửa sổ . Điều khiển người dùng bên trong có thuộc tính phụ thuộc Value được gắn với thuộc tính phụ thuộc Value của điều khiển bên ngoài. Trong cửa sổ, thuộc tính TextBox.Text được gắn với thuộc tính Value của điều khiển bên ngoài.

Điều khiển bên trong có đăng ký CoerceValueCallback với thuộc tính Value có hiệu lực là thuộc tính Value này chỉ có thể được gán số chẵn.

Lưu ý rằng mã này được đơn giản hóa cho mục đích trình diễn. Phiên bản thực sự không khởi tạo bất kỳ thứ gì trong hàm tạo; hai điều khiển thực sự có các mẫu điều khiển làm mọi thứ được thực hiện trong các nhà xây dựng tương ứng ở đây. Tức là, trong mã thực, điều khiển bên ngoài không biết điều khiển bên trong.

Khi viết số chẵn vào hộp văn bản và thay đổi tiêu điểm (ví dụ: bằng cách tập trung nút giả bên dưới hộp văn bản), cả hai thuộc tính Value đều được cập nhật đầy đủ. Khi viết một số lẻ vào hộp văn bản, tuy nhiên, thuộc tính Value của điều khiển bên trong không thay đổi, trong khi thuộc tính Value của điều khiển bên ngoài, cũng như thuộc tính TextBox.Text, hiển thị số lẻ.

Câu hỏi của tôi là: Làm cách nào để buộc cập nhật trong hộp văn bản (và lý tưởng nhất là thuộc tính Value của kiểm soát bên ngoài, trong khi chúng tôi đang ở đó)?

Tôi đã tìm thấy an SO question on the same problem, nhưng không thực sự cung cấp giải pháp. Nó ám chỉ đến việc sử dụng một trình xử lý sự kiện thay đổi thuộc tính để thiết lập lại giá trị, nhưng theo như tôi thấy, điều đó có nghĩa là sao chép mã đánh giá vào điều khiển bên ngoài ... điều đó không thực sự khả thi, vì mã đánh giá thực tế của tôi dựa vào một số thông tin về cơ bản chỉ được biết đến (không có nhiều nỗ lực) để kiểm soát bên trong.

Hơn nữa, this blogpost gợi ý cách gọi UpdateTarget trên các ràng buộc trong TextBox.Text trong CoerceValueCallback, nhưng trước tiên, như ngụ ý ở trên, kiểm soát bên trong của tôi không thể nào có bất kỳ kiến ​​thức về hộp văn bản, và thứ hai, tôi có lẽ sẽ phải gọi UpdateSource đầu tiên về sự ràng buộc của thuộc tính Value của điều khiển bên trong.Tuy nhiên, tôi không thấy nơi để làm điều đó, như trong phương thức CoerceValue, giá trị cưỡng chế chưa được đặt (vì vậy còn quá sớm để cập nhật ràng buộc), trong trường hợp giá trị được đặt lại bởi CoerceValue, giá trị tài sản sẽ chỉ còn lại những gì nó đã được, do đó một tài sản thay đổi gọi lại sẽ không nhận được gọi (cũng ngụ ý trong this discussion).

Một cách giải quyết khác mà tôi đã nghĩ là thay thế thuộc tính phụ thuộc trong SomeControl bằng thuộc tính thông thường và thực hiện INotifyPropertyChanged (vì vậy tôi có thể kích hoạt sự kiện PropertyChanged theo cách thủ công ngay cả khi giá trị đã bị ép buộc). Tuy nhiên, điều này có nghĩa là tôi không thể tuyên bố một ràng buộc về tài sản đó nữa, vì vậy nó không phải là một giải pháp thực sự hữu ích.

+2

Hãy cho tôi biết nếu bạn tìm câu trả lời cho câu trả lời này =) –

Trả lời

3

Tôi đã tìm kiếm câu trả lời cho lỗi khá khó chịu này trong một thời gian. Một cách để làm điều đó, mà không cần phải buộc một UpdateTarget trên các ràng buộc là thế này:

  • Di CoerceValue bạn gọi lại.
  • phím Shift logic của CoerceValue gọi lại vào bạn ProcessValueChanged gọi lại.
  • Gán giá trị ép buộc bạn để tài sản Giá trị của bạn, khi áp dụng (khi số là số lẻ)
  • Bạn sẽ kết thúc với ProcessValueChanged callback bị trúng hai lần, nhưng giá trị cưỡng bức của bạn sẽ kết thúc lên bị đẩy một cách hiệu quả để ràng buộc của bạn.

Căn cứ vào mã của bạn, kê khai tài sản phụ thuộc của bạn sẽ trở thành này:

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

Và sau đó, bạn ProcessValueChanged sẽ trở thành này:

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); 
     } 
    } 

tôi chút thay đổi logic của bạn, để ngăn chặn việc tăng sự kiện khi giá trị cần phải bị ép buộc. Như đã đề cập trước đây, chỉ định cho someControl.Value giá trị bị cưỡng chế sẽ làm cho ProcessValueChanged của bạn được gọi hai lần liên tiếp. Đặt câu lệnh khác sẽ chỉ tăng các sự kiện với các giá trị hợp lệ một lần.

Tôi hy vọng điều này sẽ hữu ích!

+0

Hứa hẹn - Tôi sẽ kiểm tra điều này một chút và quyết định xem tôi có chấp nhận câu trả lời hay không, nhưng có thể mất một thời gian . –

+1

Tôi đã thử điều này và nó đã không làm việc cho tôi. –

Các vấn đề liên quan