【问题标题】:What prevents infinite loops among interconnected Winforms controls?是什么阻止了相互连接的 Winforms 控件之间的无限循环?
【发布时间】:2015-03-27 18:02:09
【问题描述】:

在表单上,​​我有两个控件,A 和 B。每个控件在其值更改时都发送一个事件。表单通过将 B 设置为某个值来处理 A 的 ValueChanged 事件,并通过设置 A 的值来处理 B 的 ValueChanged。

我是否需要做一些特别的事情来防止无限循环,用户更改 A,发送导致 B 更新的 ValueChanged 事件,现在向它发送 ValueChanged 事件,导致 A 更新...

我知道一些 GUI 工具包,例如 Qt,在基础架构中内置了逻辑来防止这样的循环。 (参见关于 Qt4 的 Blanchette 和 Summerfield 书籍第 1 章中的“输入您的年龄”示例。)一些较旧的工具包要求程序员定义和管理一个标志来检测递归。对于 WinForms,我还没有在任何地方阅读过明确的声明。

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

【问题讨论】:

    标签: c# winforms events


    【解决方案1】:

    根据我的经验,循环通常会结束,因为值实际上停止了变化。您的示例属于这种情况。考虑以下场景:

    • 用户将华氏度更改为 32
    • 事件将摄氏度更新为 0
    • 事件将华氏温度设为 32
    • 没有进一步发生,因为华氏度没有改变

    这通常在属性中实现,方法是在设置器的顶部放置一个检查,以在新值与当前值相同时不引发更改事件。

    【讨论】:

    • 这通常在属性中实现,方法是在设置器的顶部放置一个检查,以在新值与当前值相同时不引发更改的事件。由谁实施?
    • 在 Windows 窗体控件的情况下,它是在其中实现此功能的控件。除非更改值,否则不会为 NumericUpDown 触发 ValueChanged 事件。
    【解决方案2】:

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

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

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

    我不记得曾经在使用 Windows 窗体控件时遇到过这些问题,所以我一直在使用的控件必须正确地执行此检查。

    【讨论】:

      【解决方案3】:

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

      现在假设您有两个 NumericUpDown 控件 A 和 B。

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

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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 你是对的。我误解了问题。对此感到抱歉。
        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-11-11
            • 2021-09-19
            • 1970-01-01
            • 1970-01-01
            • 2018-03-24
            • 1970-01-01
            • 2020-02-20
            相关资源
            最近更新 更多