2010-07-24 22 views
9

我正在寫一個真實的NumericUpDown/Spinner控件作爲學習自定義控件創作的練習。我已經掌握了大部分我正在尋找的行爲,包括適當的強制措施。然而,我的一項測試揭示了一個缺陷。爲什麼我的數據綁定看到的是真實值而不是被脅迫值?

我的控件有3個依賴屬性:Value,MaximumValueMinimumValue。我使用強制來確保Value保持在最小值和最大值之間,包括最小值和最大值。例如:

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

我的測試只是爲了確保數據綁定工作如何我期望。我創建了一個默認的WPF Windows應用程序,並在下面的XAML拋出:

<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> 

用非常簡單的代碼隱藏:

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

(我省略了控制的介紹了XAML)

現在如果我運行此操作,我會在文本框中看到NumericUpDown中的值恰當地反映出來,但如果輸入的值超出範圍,則超出範圍的值將顯示在測試文本框中,而NumericUpDown將顯示正確的值。

這是如何強制值應該採取行動?它在UI中被強制是好的,但我也希望被強制的值也能通過數據綁定。

+0

似乎綁定不支持認識val。你在TextBox中試過差異模式嗎? – 2010-07-24 02:08:08

回答

9

哇,這是令人驚訝的。在依賴項屬性上設置值時,綁定表達式會在值強制運行前更新!

如果您查看Reflector中的DependencyObject.SetValueCommon,您可以在該方法的中途看到對Expression.SetValue的調用。在綁定已更新後,調用UpdateEffectiveValue來調用CoerceValueCallback的操作即將結束。

你也可以在框架類上看到它。從一個新的WPF應用程序,添加以下XAML:

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

和下面的代碼:

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

如果您拖動滑塊,然後按一下按鈕,你會得到像「值的消息從11更改爲-1;滑塊從11更改爲10「。

+1

有趣,所以這是預期的行爲。我想我的下一步是弄清楚如何提供一個可反映的'ActualValue'屬性來反映NumericUpDown中實際顯示的內容。你是否意識到一種更好的方式,而不是僅僅重新評估一個屬性改變處理器中的強制並在那裏設置'ActualValue'? – 2010-07-24 12:45:21

+0

我認爲你應該得到不止一個upvote答案。 – 2010-07-24 15:02:32

-1

您正在強制v這是一個int類型和值類型。因此它被存儲在堆棧中。它不會連接到baseValue。所以改變v不會改變baseValue。

相同的邏輯適用於baseValue。它通過值傳遞(而不是通過引用),所以改變它不會改變實際參數。

v被返回並且明確用於更新UI。

您可能希望研究將屬性數據類型更改爲引用類型。然後它會通過引用傳遞,並且所做的任何更改都會反射回來。假定數據綁定過程不會創建副本。

+0

如果它從強制方法返回爲Object,它不會被裝箱嗎? – 2010-07-24 02:49:06

1

對一個老問題的新答案::-)

ValuePropertyFrameworkPropertyMetadata實例的註冊使用。將此實例的UpdateSourceTrigger屬性設置爲Explicit。這可以在構造函數重載中完成。

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

現在ValueProperty的結合源不會自動上PropertyChanged更新。 在您的HandleValueChanged方法中手動執行更新(請參閱上面的代碼)。 只有在強制方法被調用後,屬性的'實際'更改纔會調用此方法。

你可以這樣來做:

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

這樣就可以避免與你的DependencyProperty的非強制值更新您的綁定。

+0

良好的解決方法。 :) – 2015-08-05 15:59:42

相關問題