1

我有两张表,employeesprojectlistbox2,我展示了所有的employeeslistbox1所有的projects,现在显然一名员工可以参与许多项目,而一个项目可能有很多员工。所以我有这个EmployeeProject映射存在的多对多关系。我想要的是,如果用户单击第一个列表框中的项目名称,则该项目中的所有员工都应在列表框2 中选择。此外,当用户单击 listbox2 中的项目时,(员工)应在 listbox1 中选择该员工所属的所有项目

但是,如果我在此过程中使用 ListBox.SelectedIndexChanged 事件,并在 listbox2 中选择一个值,那么它将触发 listbox2 的 SelectedIndexChagned,这将通过选择当前员工所属的 listbox1 中的所有项目开始工作,但又一次,只要 listbox1 中的一个项目被选中,它就会触发它的 SelectedIndexChanged 事件,并且它会像这样永远持续下去。那么这有什么解决办法呢?到目前为止,我已经做到了这一点..

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

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

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

4

5 回答 5

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 一样。

于 2012-06-21T13:58:43.387 回答
0

当您在另一个控件中时,您可以在进行任何修改之前移除附​​加到其中一个控件的事件。然后在最后重新连接。

于 2012-06-21T13:55:32.560 回答
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(和相关事件)重命名为更具可读性。根据您的描述,listBox1is nowlbEmployeeslistBox2is now lbProjects

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

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。

于 2012-06-21T13:57:48.300 回答
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 块 当您将 listBox1 填充到 listBox2.SelectedIndexChanged 事件中时,相同的方法将有效地停止为 listBox1 触发事件,

于 2012-06-21T13:59:40.040 回答
0

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

对于这种情况,使用另一个答案中建议的布尔值就足够了。但是,我更喜欢使用整数而不是 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--;
    }
}
于 2012-06-21T14:05:15.007 回答