2017-08-01 95 views
0

作爲一個有點人爲的例子考慮一個簡單的外匯計算器有兩種不同貨幣的金額和一個匯率之間進行轉換。然後,規則是在任何一個金額改變時計算利率,如果利率改變,則第二金額是從第一金額和匯率中計算出來的。如何避免在WPF中使用數據綁定時的遞歸循環?

下面的實現具有視圖模型中的所有交互邏輯,更改GUI中的任何數量都會導致相互遞歸循環。

嘗試修復它的一種方法是在模型的setter上添加檢查,以便在將屬性設置爲其現有值時不會引發事件,這在任何情況下都是很好的做法。然而,對於浮點數來說,這本身並不是一個萬無一失的解決方案,因此總會有一個小的舍入誤差,從而導致事件發生。

在沒有數據綁定的世界中,對模型和其他文本框的更新可以在文本框的LostFocus事件中完成,該事件不會觸發任何其他事件,因爲我們只響應用戶事件而不會更改數據。

我想到的另一種方法是使用標誌來指示某個字段正在以編程方式更新,並且在設置標誌時忽略對該字段的更改,但當涉及很多字段時很快就會變得雜亂無章。

是否有任何標準技術或模式用於解決WPF應用程序中的這個問題?

視圖模型

namespace LoopingUpdates 
{ 
    public class FxModel : INotifyPropertyChanged 
    { 
     private double _amountCcy1; 
     private double _amountCcy2; 
     private double _rate; 

     public double AmountCcy1 
     { 
      get { return _amountCcy1; } 
      set 
      { 
       _amountCcy1 = value; 
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AmountCcy1")); 
      } 
     } 

     public double AmountCcy2 
     { 
      get { return _amountCcy2; } 
      set 
      { 
       _amountCcy2 = value; 
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AmountCcy2")); 
      } 
     } 

     public double Rate 
     { 
      get { return _rate; } 
      set 
      { 
       _rate = value; 
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Rate")); 
      } 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 
    } 

    public class ViewModel 
    { 
     public FxModel FxModel { get; set; } 

     public ViewModel() 
     { 
      FxModel = new FxModel() { AmountCcy1 = 100, AmountCcy2 = 200, Rate = 2 }; 
      FxModel.PropertyChanged += FxModel_PropertyChanged; 
     } 

     private void FxModel_PropertyChanged(object sender, PropertyChangedEventArgs e) 
     { 
      switch (e.PropertyName) { 
       case "AmountCcy1": 
        Debug.WriteLine("Amount Ccy 1 changed"); 
        FxModel.Rate = FxModel.AmountCcy2/FxModel.AmountCcy1; 
        break; 

       case "AmountCcy2": 
        Debug.WriteLine("Amount Ccy 2 changed"); 
        FxModel.Rate = FxModel.AmountCcy2/FxModel.AmountCcy1; 
        break; 

       case "Rate": 
        Debug.WriteLine("Rate 1 changed"); 
        FxModel.AmountCcy2 = FxModel.AmountCcy1 * FxModel.Rate; 
        break; 
      } 
     } 
    } 
} 

窗口XAML

<Window x:Class="LoopingUpdates.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:LoopingUpdates" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="148.7" Width="255.556" Loaded="Window_Loaded"> 
    <Grid> 
     <Label x:Name="label" Content="Amount Ccy 1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/> 
     <Label x:Name="label1" Content="Amount Ccy 2" HorizontalAlignment="Left" Margin="10,41,0,0" VerticalAlignment="Top"/> 
     <Label x:Name="label2" Content="Rate" HorizontalAlignment="Left" Margin="10,72,0,0" VerticalAlignment="Top"/> 
     <TextBox x:Name="txtAmountCcy1" Text="{Binding FxModel.AmountCcy1}" HorizontalAlignment="Left" Height="26" Margin="99,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="72" /> 
     <TextBox x:Name="txtAmountCcy2" Text="{Binding FxModel.AmountCcy2}" HorizontalAlignment="Left" Height="26" Margin="99,41,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="72" /> 
     <TextBox x:Name="txtRate" Text="{Binding FxModel.Rate}" HorizontalAlignment="Left" Height="26" Margin="99,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="72" /> 
    </Grid> 
</Window> 

背後

namespace LoopingUpdates 
{ 
    public partial class MainWindow : Window 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 
     } 

     private void Window_Loaded(object sender, RoutedEventArgs e) 
     { 
      DataContext = new ViewModel(); 
     } 
    } 
} 
+0

如果你擔心舍入誤差,則在設置器中使用布爾標誌。對於你在這裏做的事情,你不需要每個屬性都有一個單獨的標誌,只有一個由所有屬性共享的類級標誌。 –

回答

0

好問題。

我看到兩種方式來面對這個問題:

  1. 創建屬性IsUpdating和不處理PropertyChanged如果IsUpdating是真實的。然後,您可以「停用」更新過程...
  2. 爲不調用屬性更改的每個屬性創建第二個屬性(例如RateInternal,AmountCcy2Internal,...)。

這些選項並不理想,但我不知道更好的方法。

+0

我最終做了類似於第一個選項的事情,同時它需要添加大量的檢查,我的健壯和簡單。 – Shane

1

窗口代碼有什麼錯把一個檢查你的財產器例如

if (property == value) 
    return; 

因此不設置屬性或提高屬性更改事件。如果舍入是你害怕的,那麼我會把視圖模型中的四捨五入處理掉。

1

我總是避免遞歸循環檢查我的ViewModel屬性的setter中的(value != _privateField)

,如果你認爲四捨五入可能是一個問題,我只想改變字段的值,並調用PropertyChanged如果四捨五入值是不同的:

public double AmountCcy1 
{ 
    get { return _amountCcy1; } 
    set 
    { 
     if (Math.Round(value, 2) != Math.Round(_amountCcy1, 2)) 
     { 
      _amountCcy1 = value; 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AmountCcy1")); 
     } 
    } 
} 

public double AmountCcy2 
{ 
    get { return _amountCcy2; } 
    set 
    { 
     if (Math.Round(value, 2) != Math.Round(_amountCcy2, 2)) 
     { 
      _amountCcy2 = value; 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AmountCcy2")); 
     } 
    } 
} 
+0

非常感謝您的回答。我不確定四捨五入是否能完全解決問題。你可能會得到一箇舊值爲2.4999 ...和新值爲2.5000的情況......差別很小,但它足以使兩個數字變成不同的值。我已經看到了這樣的有趣的事情發生在浮點數字。你也依賴於這樣一個事實,即邏輯會計算出與原始更新相同的數字,儘管我無法想象這種情況不會是一個錯誤。 – Shane