作为一个有点人为的例子考虑一个简单的外汇计算器有两种不同货币的金额和一个汇率之间进行转换。然后,规则是在任何一个金额改变时计算利率,如果利率改变,则第二金额是从第一金额和汇率中计算出来的。如何避免在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();
}
}
}
如果你担心舍入误差,则在设置器中使用布尔标志。对于你在这里做的事情,你不需要每个属性都有一个单独的标志,只有一个由所有属性共享的类级标志。 –