是什么阻止了互连Winforms控件之间的无限循环



在Form上,我有两个控件,a和B。当值发生变化时,每个控件都会发送一个事件。Form通过将B设置为某个值来处理A的ValueChanged事件,并通过设置A的值来处理B的ValueChanged事件。

我需要做什么特别的事情来防止无限循环吗?在无限循环中,用户更改A,发送导致B更新的ValueChanged事件,现在发送它ValueChanged,导致A更新。。。

我知道一些GUI工具包,比如Qt,在基础设施中内置了逻辑,可以防止类似的循环。(请参阅Blanchette&Summerfield关于Qt4的书第1章中的"输入年龄"示例。)一些较旧的工具包要求程序员定义和管理一个标志来检测递归。对于WinForms,我还没有读到任何关于这方面的明确声明。

举个具体的例子,假设a和B是NumericUpDown控件,以华氏度和摄氏度显示温度。当用户更改其中一个时,另一个会更新以显示另一个系统中的相应温度。

根据我的经验,循环通常会因为值停止实际更改而结束。你的例子就属于这种情况。考虑以下场景:

  • 用户将华氏温度更改为32
  • 事件将摄氏度更新为0
  • 事件将华氏温度设置为32
  • 因为华氏温度没有变化,所以没有发生任何进一步的事情

这通常是在属性中实现的,方法是在setter的顶部进行检查,以在新值与当前值相同时不引发更改的事件。

在实现自定义控件时,我设法防止这个问题的方法是,只有在值确实发生更改时才引发事件,就像这样。

public string Text
{
    get { return _text; }
    set
    {
        if (_text != value)
        {
            _text = value;
            OnTextChanged();
        }
    }
}

有人可能会说,在触发事件之前不检查值是否实际不同是错误代码。

我不记得在Windows窗体控件中遇到过这些问题,所以我一直使用的控件一定是正确的。

事件不会在您提供的NumericUpDown示例中循环。NumericUpDown的ValueChanged事件只有在值发生变化时才会触发,即如果NumericUpDown的值为5,并且在代码中再次将其设置为5,则不会触发任何事件。当您有两个NumericUpDown控件时,事件的这种行为会阻止它循环。

现在假设您有两个NumericUpDown控件A&B.

  1. 用户更改了A,它引发了一个事件
  2. 在A触发的事件上,计算并设置B的值。B检测到值更改并触发事件
  3. 在B触发的事件上,您计算并设置A的值。但是,此值将与原始值相同,Windows将不会触发ValueChanged的事件

因此,在Windows窗体控件的情况下,框架会为您管理它,如果您想为自己的类实现这一点,请遵循类似的原则。在值的设置器上,检查新值是否与旧值不同。只有当它不同时才触发事件。

我认为应该在A事件中设置B之前分离B事件。所以当你在A事件中设置B时,B事件永远不会触发。你们应该为B事件做同样的事情。你们可以打破循环。

对不起我的英语。我希望它能帮助你。

实际上没有任何标准。作为一个用户,您应该处理引发无用事件的控件,以及不引发有用事件的控件。即使对于.NET自己的控件,也缺乏这方面的文档,所以您只能尝试一下

使用标志来检测递归是可能的,但更好的IMO是将control.Value = newvalue;更改为if (control.Value != newvalue) control.Value = newvalue;

也就是说,如果你编写自己的控件,请不要引发无用的事件,并请清楚地记录这一事实。

为了回答您的问题,我用下面的代码构建了一个简单的测试。

public partial class Form1 : Form
{
    Random r = new Random();
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Shown(object sender, EventArgs e)
    {
        // This triggers the infinite loop between the two controls
        numericUpDown1.Value = 10;
    }
    private void numericUpDown1_ValueChanged(object sender, EventArgs e)
    {
        int cur = (int)numericUpDown2.Value;
        int r1 = r.Next(1, 100);
        while (r1 == cur)
            r1 = r.Next(1, 100);
        Console.WriteLine("Random in NUM1=" + r1);
        numericUpDown2.Value = r1;
    }
    private void numericUpDown2_ValueChanged(object sender, EventArgs e)
    {
        int cur = (int)numericUpDown1.Value;
        int r1 = r.Next(1, 100);
        while (r1 == cur)
            r1 = r.Next(1, 100);
        Console.WriteLine("Random in NUM2=" + r1);
        numericUpDown1.Value = r1;
    }
}

该窗体只有两个NumericUpDown初始化值分别为1和2。

正如您可以在Visual Studio 2013中测试的那样,如果您真的设法在每个ValueChanged事件中生成不同的数字,那么没有什么可以阻止无限递归。

为了防止无限循环,可以使用常用的技术。声明全局表单变量

 bool changing = false;

在两个事件中添加此代码

try
{   
     if (changing) return;
     changing = true;
     .... event code ....
}
finally
{
     changing = false;
}

最新更新