2010-07-24 7 views
9

Tôi đang viết một kiểm soát thực sự NumericUpDown/Spinner làm bài tập để tìm hiểu tác giả điều khiển tùy chỉnh. Tôi đã có hầu hết các hành vi mà tôi đang tìm kiếm, bao gồm cả cưỡng chế thích hợp. Tuy nhiên, một trong những thử nghiệm của tôi đã tiết lộ một lỗ hổng.Tại sao dữ liệu của tôi ràng buộc thấy giá trị thực thay vì giá trị bị cưỡng chế?

Kiểm soát của tôi có 3 thuộc tính phụ thuộc: Value, MaximumValueMinimumValue. Tôi sử dụng cưỡng chế để đảm bảo rằng Value vẫn nằm trong khoảng từ min đến tối đa. Ví dụ:

// In NumericUpDown.cs 

public static readonly DependencyProperty ValueProperty = 
    DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), 
    new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, HandleValueChanged, HandleCoerceValue)); 

[Localizability(LocalizationCategory.Text)] 
public int Value 
{ 
    get { return (int)this.GetValue(ValueProperty); } 
    set { this.SetCurrentValue(ValueProperty, value); } 
} 

private static object HandleCoerceValue(DependencyObject d, object baseValue) 
{ 
    NumericUpDown o = (NumericUpDown)d; 
    var v = (int)baseValue; 

    if (v < o.MinimumValue) v = o.MinimumValue; 
    if (v > o.MaximumValue) v = o.MaximumValue; 

    return v; 
} 

Thử nghiệm của tôi chỉ nhằm đảm bảo ràng buộc dữ liệu hoạt động như mong đợi. Tôi tạo ra một mặc định WPF cửa sổ ứng dụng và ném trong XAML sau:

<Window x:Class="WpfApplication.MainWindow" x:Name="This" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:nud="clr-namespace:WpfCustomControlLibrary;assembly=WpfCustomControlLibrary" 
     Title="MainWindow" Height="350" Width="525"> 
    <Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition /> 
      <RowDefinition /> 
     </Grid.RowDefinitions> 
     <nud:NumericUpDown Value="{Binding ElementName=This, Path=NumberValue}"/> 
     <TextBox Grid.Row="1" Text="{Binding ElementName=This, Path=NumberValue, Mode=OneWay}" /> 
    </Grid> 
</Window> 

với codebehind rất đơn giản:

public partial class MainWindow : Window 
{ 
    public int NumberValue 
    { 
     get { return (int)GetValue(NumberValueProperty); } 
     set { SetCurrentValue(NumberValueProperty, value); } 
    } 

    // Using a DependencyProperty as the backing store for NumberValue. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty NumberValueProperty = 
     DependencyProperty.Register("NumberValue", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0));  

    public MainWindow() 
    { 
     InitializeComponent(); 
    } 
} 

(Tôi bỏ qua XAML để trình bày của điều khiển)

Bây giờ nếu tôi chạy điều này, tôi thấy giá trị từ NumericUpDown được phản ánh một cách thích hợp trong hộp văn bản, nhưng nếu tôi nhập giá trị nằm ngoài phạm vi, giá trị phạm vi được hiển thị trong hộp văn bản thử nghiệm trong khi NumericUpDown hiển thị giá trị chính xác.

Đây có phải là cách các giá trị bị cưỡng chế được cho là hành động không? Thật tốt khi nó bị ép buộc trong ui, nhưng tôi cũng mong đợi giá trị bị ép buộc để chạy qua databinding.

+0

Dường như ràng buộc không hỗ trợ đồng bộ hóa val. Bạn đã thử chế độ khác trong TextBox chưa? –

Trả lời

9

Thật đáng ngạc nhiên. Khi bạn đặt một giá trị trên một thuộc tính phụ thuộc, các biểu thức ràng buộc được cập nhật trước khi cưỡng chế giá trị chạy!

Nếu bạn nhìn vào DependencyObject.SetValueCommon trong Reflector, bạn có thể thấy lệnh gọi Expression.SetValue nửa chừng thông qua phương thức. Các cuộc gọi đến UpdateEffectiveValue sẽ gọi CoerceValueCallback của bạn là ở cuối cùng, sau khi ràng buộc đã được cập nhật.

Bạn cũng có thể thấy điều này trên các lớp khung công tác. Từ một ứng dụng WPF mới, thêm XAML sau:

<StackPanel> 
    <Slider Name="Slider" Minimum="10" Maximum="20" Value="{Binding Value, 
     RelativeSource={RelativeSource AncestorType=Window}}"/> 
    <Button Click="SetInvalid_Click">Set Invalid</Button> 
</StackPanel> 

và đoạn mã sau:

private void SetInvalid_Click(object sender, RoutedEventArgs e) 
{ 
    var before = this.Value; 
    var sliderBefore = Slider.Value; 
    Slider.Value = -1; 
    var after = this.Value; 
    var sliderAfter = Slider.Value; 
    MessageBox.Show(string.Format("Value changed from {0} to {1}; " + 
     "Slider changed from {2} to {3}", 
     before, after, sliderBefore, sliderAfter)); 
} 

public int Value { get; set; } 

Nếu bạn kéo trượt và sau đó nhấn nút, bạn sẽ nhận được một thông báo như "giá trị gia tăng thay đổi từ 11 đến -1; Thanh trượt thay đổi từ 11 đến 10 ".

+1

Thú vị, vì vậy đây là hành vi mong đợi. Tôi cho rằng bước tiếp theo của tôi là tìm ra cách cung cấp thuộc tính 'ActualValue' có thể ràng buộc phản ánh những gì thực sự được hiển thị trong NumericUpDown. Bạn có nhận thức được một cách tốt hơn là chỉ đánh giá lại sự ép buộc trong một thuộc tính đã thay đổi trình xử lý và đặt 'ActualValue' ở đó? –

+0

Tôi nghĩ bạn xứng đáng nhận được nhiều hơn một câu trả lời cho câu trả lời. –

-1

Bạn đang co giật v là một int và như một loại giá trị như vậy. Do đó nó được lưu trữ trên ngăn xếp. Không có cách nào được kết nối với baseValue. Vì vậy, thay đổi v sẽ không thay đổi baseValue.

Cùng một logic áp dụng cho baseValue. Nó được truyền theo giá trị (không phải bằng tham chiếu) để thay đổi nó sẽ không thay đổi tham số thực tế.

v được trả lại và được sử dụng rõ ràng để cập nhật giao diện người dùng.

Bạn có thể muốn điều tra việc thay đổi loại dữ liệu thuộc tính thành loại tham chiếu. Sau đó, nó sẽ được chuyển qua tham chiếu và mọi thay đổi được thực hiện sẽ phản ánh lại nguồn. Giả sử quá trình databinding không tạo ra một bản sao.

+0

Nó không được đóng hộp nếu nó được trả về như Object từ phương pháp ép buộc? –

1

Một câu trả lời mới cho một câu hỏi cũ: :-)

Trong đăng ký ValueProperty một thể hiện FrameworkPropertyMetadata được sử dụng. Đặt thuộc tính UpdateSourceTrigger của phiên bản này thành Explicit. Điều này có thể được thực hiện trong một quá tải constructor.

public static readonly DependencyProperty ValueProperty = 
    DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), 
    new FrameworkPropertyMetadata(
     0, 
     FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, 
     HandleValueChanged, 
     HandleCoerceValue, 
     false 
     UpdateSourceTrigger.Explicit)); 

Bây giờ, nguồn ràng buộc của ValueProperty sẽ không được tự động cập nhật trên PropertyChanged. Thực hiện cập nhật theo cách thủ công trong phương thức HandleValueChanged của bạn (xem mã ở trên). Phương pháp này chỉ được gọi là các thay đổi 'thực' của thuộc tính SAU KHI phương thức ép buộc đã được gọi.

Bạn có thể làm theo cách này:

static void HandleValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) 
{ 
    NumericUpDown nud = obj as NumericUpDown; 
    if (nud == null) 
     return; 

    BindingExpression be = nud.GetBindingExpression(NumericUpDown.ValueProperty); 
    if(be != null) 
     be.UpdateSource(); 
} 

Bằng cách này bạn có thể tránh để cập nhật các ràng buộc của bạn với các giá trị phi ép buộc các thuộc tính phụ thuộc của bạn.

+0

Giải pháp thay thế tốt. :) –