【问题标题】:Determine who fired an event确定谁触发了事件
【发布时间】:2013-07-09 11:50:21
【问题描述】:

背景:

在我的 winforms 表单中,我有一个 Checked ListView 和一个名为 checkBoxAll 的“主”复选框。 master的行为如下:

  • 如果 master 被选中或未选中,所有 ListViewItems 都必须相应更改。

  • 如果用户取消选中 ListViewItem,则主视图必须相应更改。

  • 如果用户检查了一个 ListViewItem,并且所有其他 ListViewItems 也都检查了,则 master 必须相应地更改。

我编写了以下代码来模仿这种行为:

private bool byProgram = false; //Flag to determine the caller of the code. True for program, false for user.

private void checkBoxAll_CheckedChanged(object sender, EventArgs e)
{
    //Check if the user raised this event.
     if (!byProgram)
     {
         //Event was raised by user!

         //If checkBoxAll is checked, all listviewitems must be checked too and vice versa.

         //Check if there are any items to (un)check.
         if (myListView.Items.Count > 0)
         {
             byProgram = true; //Raise flag.

             //(Un)check every item.
             foreach (ListViewItem lvi in myListView.Items)
             {
                 lvi.Checked = checkBoxAll.Checked;
             }

             byProgram = false; //Lower flag.
         }
     }
}

private void myListView_ItemChecked(object sender, ItemCheckedEventArgs e)
{
    //Get the appropiate ListView that raised this event
    var listView = sender as ListView;

    //Check if the user raised this event.
    if (!byProgram)
    {
        //Event was raised by user!

        //If all items are checked, set checkBoxAll checked, else: uncheck him!

        bool allChecked = true; //This boolean will be used to set the value of checkBoxAll

        //This event was raised by an ListViewItem so we don't have to check if any exist. 
        //Check all items untill one is not checked.
        foreach (ListViewItem lvi in listView.Items)
        {
            allChecked = lvi.Checked;
            if (!allChecked) break;
        }

        byProgram = true; //Raise flag.

        //Set the checkBoxAll according to the value determined for allChecked.
        checkBoxAll.Checked = allChecked;

        byProgram = false; //Lower flag.
    }
}

在这个例子中,我使用一个标志 (byProgram) 来确定一个事件是否是由用户引起的,从而防止无限循环(一个事件可以触发另一个事件,它可以触发第一个再等等等等)。恕我直言,这是一个 hacky 解决方案。 我四处搜索,但找不到 MSDN 记录的方法来确定是否由于用户而直接触发了用户控制事件。这让我觉得很奇怪(再次,恕我直言)。

我知道 FormClosingEventArgs 有一个字段,我们可以使用它来确定用户是否正在关闭表单。但据我所知,这是唯一提供这种功能的 EventArg...

总之:

有没有办法(除了我的例子)来确定一个事件是否是由用户直接触发的?

请注意:我不是指事件的发送者!我编码 someCheckBox.Checked = true; 没关系或者手动设置someCheckBox,事件的发送者永远是someCheckBox。我想知道是否可以确定是通过用户(点击)还是通过程序(.Checked = true)。

另外: 30% 的时间写这个问题是为了正确地制定问题和标题。仍然不确定它是否 100% 清晰,所以如果你认为你可以做得更好,请编辑:)

【问题讨论】:

  • 只是一个疯狂的猜测,但 EventArgs 中确实应该有一些东西。您是否在运行时检查过它们(调试)?
  • 点击事件设置一个标志来知道这个调用是来自点击而不是代码?
  • @Romiox,检查我的问题,它说我没有那种功能(据我所知,只有 FormClosing 有这个)。
  • @MEYWD,我想过这个问题,但这仍然是一个标志,现在还有另一个事件。这似乎不是理想的解决方案。

标签: c# events


【解决方案1】:

不,没有实用的方法来确定更改是来自 GUI 还是由程序完成(实际上,您可以分析调用堆栈 - 但不建议这样做,因为它非常慢且容易出错)。

顺便说一句,除了设置byProgram,您还可以做另一件事。您可以分别在更改控件之前或之后删除和添加事件处理程序:

checkBoxAll.CheckedChanged -= checkBoxAll_CheckedChanged;
// do something
checkBoxAll.CheckedChanged += checkBoxAll_CheckedChanged;

【讨论】:

  • 这是我经常做的事情。
  • +1 用于提供不涉及额外事件或标志的解决方案!
  • 我认为以这种方式分离/重新连接事件处理程序并不是一个好主意。除了 slight 开销之外,如果您有 20 个事件处理程序怎么办?我觉得阻塞代码更好,因为你对“状态”做出反应,而不是事件是否被连接,因为你总是有机会忘记重新连接或分离。
【解决方案2】:

您可以使用 clicked 事件将更改级联到相关控件,而不是使用 changed 事件。这将是对用户点击的响应,而不是以编程方式更改的值。

【讨论】:

  • +1 用于提供不涉及额外标志的解决方案。但是你怎么知道属性实际上改变了?这将涉及存储旧值对吗?
  • 如果我在全选框上收到点击事件,那么我可以设置所有复选框的值。如果我在其中一个选项上获得点击事件,我只会根据选项的状态更新全选复选框。
  • 你是对的!我不在乎价值是否发生了变化。我可以看看它目前的价值。
  • 一个警告是时间。我相信点击事件可能发生在项目更改之前,因此您必须考虑到这一点。
【解决方案3】:

这是我经常遇到的事情,我倾向于尝试做的不是在用户交互和程序交互之间拆分它 - 我使用更通用的代码,即 UI 正在更新并且不需要任何事件处理。我通常通过BeginUpdate/EndUpdate 方法将其打包,例如

private int updates = 0;

public bool Updating { get { return updates > 0; } }

public void BeginUpdate()
{
    updates++;
}

public void EndUpdate()
{
    updates--;
}

public void IndividualCheckBoxChanged(...)
{
    if (!Updating)
    {
        // run code
    }
}

public void CheckAllChanged(...)
{
    BeginUpdate();
    try
    {
        // run code
    }
    finally
    {
        EndUpdate();
    }
}

【讨论】:

  • +1,仍然是一个标志,但下次需要升标志时我将使用此结构。
  • @Jordy 这不是 另一个 标志,它是通过添加更多结构和含义对现有标志的改进。 BeginUpdate/EndUpdate 模式在各种不同的平台(包括 .NET)中是解决此类问题的一种很好的方法。这个想法是无论是否附加事件,您都会对“状态”做出反应,这使得代码更具可读性。
  • 我明白了……我可能不应该将其描述为“仍然是一面旗帜”。但由于某种原因,当评论太旧或其他原因时,我无法编辑评论:\
  • 别担心,我明白你的意思。但是,我要说明的一点是不要将其视为“哦,不,另一个标志” - 从应用程序架构的角度考虑它,即我的应用程序必须处理 UI 更新可能导致的场景在某些情况下我想忽略的事件被触发,我该如何解释?您在这里所做的基本上是采用通用模式并将其应用于您自己的代码,从而保持您的代码一致。
猜你喜欢
  • 2020-09-24
  • 1970-01-01
  • 1970-01-01
  • 2016-04-02
  • 1970-01-01
  • 2016-05-03
  • 1970-01-01
  • 2020-02-13
  • 1970-01-01
相关资源
最近更新 更多