c# - 在WPF中使用数据绑定(bind)时如何避免递归循环?

标签 c# wpf

作为一个有点人为的例子,考虑一个简单的外汇计算器,它具有两种不同货币的金额以及在它们之间转换的汇率。规则是,当任一金额发生更改时,都会计算汇率;如果汇率发生更改,则根据第一个金额和汇率计算第二个金额。

下面的实现在 View 模型中包含所有交互逻辑,更改 GUI 中的任何数量都会导致相互递归循环。

尝试修复此问题的一种方法是添加对模型 setter 的检查,以便在将属性设置为其现有值时不会引发事件,这在任何情况下都是良好的做法。然而,这本身并不是一个万无一失的解决方案,因为对于 float ,总是有可能存在小的舍入误差,从而导致引发事件。

在没有数据绑定(bind)的世界中,模型和其他文本框的更新可以在发生更改的文本框的 LostFocus 事件中完成,这不会触发任何进一步的事件,因为我们只响应用户事件而不是数据更改。

我想到的另一种方法是使用标志来指示某个字段正在以编程方式更新,并在设置标志时忽略对该字段的更改,但当涉及很多字段时,很快就会变得困惑。

是否有任何标准技术或模式可用于解决 WPF 应用程序中的此问题?

View 模型

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

最佳答案

好问题。

我看到有两种方法可以解决这个问题:

  1. 创建属性 IsUpdating,如果 IsUpdating 为 true,则不处理 PropertyChanged。然后您可以“停用”更新过程...
  2. 为每个不调用属性更改的属性(例如 RateInternalAmountCcy2Internal...)创建第二个属性。

这些选项并不理想,但我不知道更好的方法。

关于c# - 在WPF中使用数据绑定(bind)时如何避免递归循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45436740/

相关文章:

wpf - 如何将 XAML 绑定(bind)延迟到加载后

c# - WPF 应用程序应在任何 PC/笔记本电脑的左侧或右侧开始

WPF 无法使 ItemContainerGenerator.ContainerFromItem 工作

C# WPF Windows 10 (1803) TouchKeyboard 不可靠问题 (Prism ClickOnce)

c# - 导航属性 Project.Models.Customer.SubCustomers 的声明类型与指定导航的结果不兼容

c# - 圆形进度条粗细

c# - 如何在 gridview 控件中显示空数据行

wpf - 更改该 DependencyProperty 的 PropertyChangedCallback 中的 DependencyProperty 的值

c# MySqlCommand 似乎没有解析参数

c# - Azure Webjobs TimerTrigger 不起作用