在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.
- 用户更改了A,它引发了一个事件
- 在A触发的事件上,计算并设置B的值。B检测到值更改并触发事件
- 在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;
}