是什么阻止了互连的 Winforms 控件之间的无限循环?
What prevents infinite loops among interconnected Winforms controls?
在一个窗体上,我有两个控件,A 和 B。每个控件在其值更改时发送一个事件。表单通过将 B 设置为某个值来处理 A 的 ValueChanged 事件,并通过设置 A 的值来处理 B 的 ValueChanged 事件。
我是否需要做任何特别的事情来防止无限循环,在这种情况下,用户更改 A,发送导致 B 更新的 ValueChanged 事件,现在发送它的 ValueChanged 事件,导致 A 更新...
我知道一些 GUI 工具包(例如 Qt)在基础结构中内置了逻辑以防止类似的循环。 (请参阅 Blanchette & Summerfield 关于 Qt4 的书第 1 章中的 "Enter Your Age" 示例。)一些较旧的工具包要求程序员定义和管理标志以检测递归。对于 WinForms,我还没有在任何地方读到关于此的明确声明。
举一个具体的例子,假设 A 和 B 是 NumericUpDown 控件,以华氏度和摄氏度显示温度。当用户更改一个时,另一个更新以在另一个系统中显示相应的温度。
我认为您应该在 A 事件中设置 B 之前分离 B 事件。因此,当您在 A 事件中设置 B 时,B 事件永远不会触发。你应该为 B 事件做同样的事情。你可以打破循环。
对不起我的英语。希望对你有帮助。
根据我的经验,循环通常会结束,因为值实际上停止了变化。你的例子属于这种情况。考虑以下情况:
- 用户将华氏度更改为 32
- 事件将摄氏度更新为 0
- 事件将华氏温度设置为 32
- 没有进一步发生,因为华氏温度没有改变
这通常在属性中通过在 setter 的顶部放置一个检查来实现,以便在新值与当前值相同时不引发更改的事件。
确实没有任何标准。作为用户,您应该处理引发无用事件的控件,以及不会引发无用事件的控件。即使对于 .NET 自己的控件,也缺乏这方面的文档,因此您只能尝试一下。
可以使用标志来检测递归,但更好的 IMO 是将 control.Value = newvalue;
更改为 if (control.Value != newvalue) control.Value = newvalue;
。
也就是说,如果您编写自己的控件,请不要引发无用的事件,并请清楚地记录这一事实。
我在实现自定义控件时设法防止此问题的方法是仅在值实际发生更改时引发事件,如下所示。
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 表单控件的情况下,框架会为你管理它, 如果你想为自己实现这个 类 你遵循类似的原则。在值的 setter 上,检查新值是否与旧值不同。只有当它不同时才会触发事件。
为了回答您的问题,我使用以下代码构建了一个简单的测试。
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;
}
}
表单只有两个初始化为值 1 和 2 的 NumericUpDown。
正如您在 Visual Studio 2013 年测试的那样,在您真正设法在每个 ValueChanged 事件中生成不同数字的情况下,没有什么可以阻止无限递归。
要防止无限循环,您可以使用通常的技术。声明一个全局表单变量
bool changing = false;
在两个事件中添加此代码
try
{
if (changing) return;
changing = true;
.... event code ....
}
finally
{
changing = false;
}
在一个窗体上,我有两个控件,A 和 B。每个控件在其值更改时发送一个事件。表单通过将 B 设置为某个值来处理 A 的 ValueChanged 事件,并通过设置 A 的值来处理 B 的 ValueChanged 事件。
我是否需要做任何特别的事情来防止无限循环,在这种情况下,用户更改 A,发送导致 B 更新的 ValueChanged 事件,现在发送它的 ValueChanged 事件,导致 A 更新...
我知道一些 GUI 工具包(例如 Qt)在基础结构中内置了逻辑以防止类似的循环。 (请参阅 Blanchette & Summerfield 关于 Qt4 的书第 1 章中的 "Enter Your Age" 示例。)一些较旧的工具包要求程序员定义和管理标志以检测递归。对于 WinForms,我还没有在任何地方读到关于此的明确声明。
举一个具体的例子,假设 A 和 B 是 NumericUpDown 控件,以华氏度和摄氏度显示温度。当用户更改一个时,另一个更新以在另一个系统中显示相应的温度。
我认为您应该在 A 事件中设置 B 之前分离 B 事件。因此,当您在 A 事件中设置 B 时,B 事件永远不会触发。你应该为 B 事件做同样的事情。你可以打破循环。
对不起我的英语。希望对你有帮助。
根据我的经验,循环通常会结束,因为值实际上停止了变化。你的例子属于这种情况。考虑以下情况:
- 用户将华氏度更改为 32
- 事件将摄氏度更新为 0
- 事件将华氏温度设置为 32
- 没有进一步发生,因为华氏温度没有改变
这通常在属性中通过在 setter 的顶部放置一个检查来实现,以便在新值与当前值相同时不引发更改的事件。
确实没有任何标准。作为用户,您应该处理引发无用事件的控件,以及不会引发无用事件的控件。即使对于 .NET 自己的控件,也缺乏这方面的文档,因此您只能尝试一下。
可以使用标志来检测递归,但更好的 IMO 是将 control.Value = newvalue;
更改为 if (control.Value != newvalue) control.Value = newvalue;
。
也就是说,如果您编写自己的控件,请不要引发无用的事件,并请清楚地记录这一事实。
我在实现自定义控件时设法防止此问题的方法是仅在值实际发生更改时引发事件,如下所示。
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 表单控件的情况下,框架会为你管理它, 如果你想为自己实现这个 类 你遵循类似的原则。在值的 setter 上,检查新值是否与旧值不同。只有当它不同时才会触发事件。
为了回答您的问题,我使用以下代码构建了一个简单的测试。
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;
}
}
表单只有两个初始化为值 1 和 2 的 NumericUpDown。
正如您在 Visual Studio 2013 年测试的那样,在您真正设法在每个 ValueChanged 事件中生成不同数字的情况下,没有什么可以阻止无限递归。
要防止无限循环,您可以使用通常的技术。声明一个全局表单变量
bool changing = false;
在两个事件中添加此代码
try
{
if (changing) return;
changing = true;
.... event code ....
}
finally
{
changing = false;
}