如何阻止方法递归调用自己?

时间:2012-06-21 13:50:14

标签: c# visual-studio recursion listbox

我在employees中有两个表projectlistbox2,我在employees显示所有listbox1 projects EmployeeProject现在很明显,一个员工可以参与很多项目,一个项目可以有很多员工。所以我有这个 private void listBox1_SelectedIndexChanged(object sender, EventArgs e) { // Load the list of employees cmd.CommandText = "SELECT EmpName FROM Employee WHERE EmpID IN(SELECT EmpID FROM EmployeeProject WHERE ProjectID =(SELECT ProjectID FROM Project WHERE ProjectName = '" + listBox1.SelectedItem.ToString() + "')) ORDER BY EmpId"; var rdr = cmd.ExecuteReader(); listBox2.Items.Clear(); // right now, I am doing this to escape this recursive loop, but thats not what I want while (rdr.Read()) { listBox2.Items.Add(rdr.GetString(0)); } rdr.Close(); this.AutoScroll = true; } private void listBox2_SelectedIndexChanged(object sender, EventArgs e) { // Load the list of projects cmd.CommandText = "SELECT ProjectName FROM Projects WHERE ProjectID IN(SELECT ProjectID FROM EmployeeProject WHERE EmpId=(SELECT EmpId FROM Employee WHERE EmpName= '" + listBox2.SelectedItem.ToString() + "')) ORDER BY ProjectID"; var rdr = cmd.ExecuteReader(); listBox1.Items.Clear(); // again, I don't want to clear, but select all those employee in listbox1 that are involved in this selected project, but can't do it because it would cause infinite recursion of these while (rdr.Read()) { listBox2.Items.Add(rdr.GetString(0)); } rdr.Close(); this.AutoScroll = true; } 来映射存在的多对多关系。我想要的是,如果用户在第一个列表框中单击项目名称,则应该在listbox2中选择该项目中的所有员工。此外,当用户单击listbox2中的项目时,(员工)应该在listbox1中选择该员工所属的所有项目

但是如果我为此进程使用ListBox.SelectedIndexChanged事件,并在listbox2中选择一个单独的值,那么它将触发listbox2的SelectedIndexChagned,并且将通过选择listbox1中的所有项目开始工作当前员工是其中的一部分,但是,一旦选择了listbox1中的一个项目,它就会启动其SelectedIndexChanged事件,并且它会像这样永远发生。那么这个解决方案是什么?到目前为止,我已经做到了..

{{1}}

所以?我该怎么做才能实现我想要达到的目标?我怎么能避免那次递归呢?我知道这种方式也适用于我刚刚展示的内容,但我不想清理并再次显示,(这可能会让一个简单的用户感到困惑)。我想要每个选择,在其他列表框中选择与该选择对应的值(当然不会导致递归!)。我该怎么做?

编辑我不知道如何以编程方式在列表框中选择多个项目,所以如果你能说出来,那就太棒了!

5 个答案:

答案 0 :(得分:5)

有一种名为Balking的设计模式,我认为它适用于此处。

http://en.wikipedia.org/wiki/Balking_pattern

我们的想法是引入一个辅助状态变量来控制操作:

  private bool doesProcessing { get; set; }

  private void listBox1_SelectedIndexChanging( ... )
  {
     // signal the beginning of processing
     if ( doesProcessing ) 
        return;
     else
        doesProcessing = true; 

     try
     {
        // your logic goes here
     }
     finally
     {
        // signal the end of processing
        doesProcessing = false;
     }
  }

和listBox2相同。

答案 1 :(得分:0)

在进行任何修改之前,您可以在另一个控件中删除附加事件。 然后在最后重新连接。

答案 2 :(得分:0)

当您更改一个列表框的选定项目时,您可以暂时禁用另一个列表框中的事件。例如:

// Store the event handlers in private member variables.
private System.EventHandler selectedEmployeeChanged = new System.EventHandler(this.lbEmployees_SelectedIndexChanged); 

private void lbProjects_SelectedIndexChanged(object sender, EventArgs e)
{
    try
    {
        // Remove your event so that updating lbEmployees doesn't cause
        // lbEmployees_SelectedIndexChanged to get fired.
        lbEmployees.SelectedIndexChanged -= selectedEmployeeChanged;

        // other event handler logic
    }
    finally
    {
        // Ensure that the handler on lbEmployees is re-added,
        // even if an exception was encountered.
        lbEmployees.SelectedIndexChanged += selectedEmployeeChanged;
    }
}

注意:我已将listBoxes(以及相关事件)重命名为更具可读性。根据您的说明,listBox1现在是lbEmployeeslistBox2现在是lbProjects

至于以编程方式选择多个项目,如果您知道每个项目的索引,则可以使用ListBox.SetSelected Method。例如:

listBox1.SetSelected(1, true);
listBox1.SetSelected(3, true);

导致listBox1选择1和3处的项目。

在您的情况下,我建议仅使用查询来获取要选择的值(不清除列表框,然后只添加应该选择的值)。以下是关于如何重写其中一个处理程序的建议:

// Store the event handlers in private member variables.
private System.EventHandler selectedEmployeeChanged = new System.EventHandler(this.lbEmployees_SelectedIndexChanged); 

private void lbProjects_SelectedIndexChanged(object sender, EventArgs e)
{
    // Declare this outside of try so that we can close it in finally
    DbDataReader reader = null;
    try
    {
        // Remove your event so that updating lbEmployees doesn't cause
        // lbEmployees_SelectedIndexChanged to get fired.
        lbEmployees.SelectedIndexChanged -= selectedEmployeeChanged;
        // Deselect all items in lbEmployees
        lbEmployees.ClearSelected();

        cmd.CommandText = "SELECT ProjectName FROM Projects WHERE ProjectID IN(SELECT ProjectID FROM EmployeeProject WHERE EmpId=(SELECT EmpId FROM Employee WHERE EmpName= '@EmpName')) ORDER BY ProjectID";
        cmd.Parameters.AddWithValue("@EmpName", lbProjects.SelectedItem.ToString());
        reader = cmd.ExecuteReader();

        // For each row returned, find the index of the matching value
        // in lbEmployees and select it.
        while (rdr.Read())
        {
            int index = lbEmployees.FindStringExact(rdr.GetString(0));
            if(index != ListBox.NoMatches)
            {
                lbEmployees.SetSelected(index, true);
            }
        }

        this.AutoScroll = true;
    }
    finally
    {
        // Ensure that the reader gets closed, even if an exception ocurred
        if(reader != null)
        {
            reader.Close();
        }
        // Ensure that the handler on lbEmployees is re-added,
        // even if an exception was encountered.
        lbEmployees.SelectedIndexChanged += selectedEmployeeChanged;
    }
}

作为旁注,您可能希望在查询字符串中使用JOIN运算符,而不是多个嵌套的SELECTS。

答案 3 :(得分:0)

OP代码的逻辑(如果像OP期望的那样工作)包含一个问题 假设您使用所有可能的值填充两个列表 然后,当用户点击一个项目时,第二个列表被清空并仅填充链接到另一个列表的项目。但是在两个列表之间单击可以永远删除另一个列表中的一个或多个项目。最好让两个列表填满并尝试突出显示相应项目的行

例如(假设ListBox.SelectionMode = SelectionMode.MultiSimple或MultiExtended)

private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
........
try
{
   ......
   // Remove the event handler, do your selection, No event on the listbox2 will be fired....
   listBox2.SelectedIndexChanged -= new System.EventHandler(this.listBox2_SelectedIndexChanged);

   // Do not remove the items , instead clear previous selection
   listBox2.ClearSelected(); 

   while (rdr.Read()) 
   { 
       int index = listBox2.FindString(rdr.GetString(0), -1);
       if(index != -1) listBox2.SetSelected(index, true)); 
   } 
   ....
}
finally
{
    // before exit, reapply the event handler 
    listBox2.SelectedIndexChanged += new System.EventHandler(this.listBox2_SelectedIndexChanged);
}
}

当然您需要确保重新应用该事件,因此请使用try / finally块 当您在listBox2.SelectedIndexChanged事件中填充它时,相同的方法将有效停止listBox1的事件触发,

答案 4 :(得分:0)

有一个eventMask整数变量,并使用下面的模式来保护你的事件。

对于这种情况,使用另一个答案中建议的bool就足够了。但是,我更喜欢使用整数而不是bool:它可以正确处理嵌套和堆叠的事件调用。

当我必须处理树视图和列表视图中的选择/取消选择事件时,它特别有用。

int eventMask = 0;

void items_PropertyChanged(object sender, PropertyChangedEventArgs e) // for example...
{
    if (eventMask > 0)
        return;

    try
    {
        eventMask++;

        // processing
        // it happens some other events can be called. Including this one.
    }
    finally 
    {
        // put this in finally, so there is no lock risk
        eventMask--;
    }
}