0

我有一个 Windows 窗体应用程序,它使用共享类来容纳应用程序的所有常见对象。设置类有一组定期做事的对象,然后有一些有趣的事情,他们需要提醒主窗体并让它更新。

我目前正在通过对象上的事件来执行此操作,并且在创建每个对象时,我添加一个 EventHandler 以将事件映射回表单。但是,我遇到了一些麻烦,这表明这些请求并不总是最终出现在我的表单的主副本上。例如,我的表单有一个通知托盘图标,但是当表单捕获和事件并尝试显示气泡时,没有出现气泡。但是,如果我修改该代码以使图标可见(尽管它已经可见),然后显示气泡,则会出现第二个图标并正确显示气泡。

有没有人遇到过这个?有没有一种方法可以强制我的所有事件被表单的单个实例捕获,或者是否有完全不同的方法来处理这个?如有必要,我可以发布代码示例,但我认为这是一个常见的线程问题。

更多信息:我目前在表单的事件处理程序中使用 Me.InvokeRequired,在这种情况下它总是返回 FALSE。此外,当我从该表单中使其可见时创建的第二个托盘图标没有上下文菜单,而“真实”图标有 - 这是否提示任何人?

我要拔头发了!这不可能那么难!

解决方案:感谢 nobugz 提供的线索,它引导我找到我现在正在使用的代码(它工作得很好,虽然我不禁想到有更好的方法来做到这一点)。我向名为“IsPrimary”的表单添加了一个私有布尔变量,并将以下代码添加到表单构造函数中:

    Public Sub New()
        If My.Application.OpenForms(0).Equals(Me) Then
            Me.IsFirstForm = True
        End If
    End Sub

一旦设置了这个变量并且构造函数完成,它直接进入事件处理程序,我以这种方式处理它(CAVEAT:由于我正在寻找的表单是应用程序的主要表单,My.Application.OpenForms( 0) 得到我需要的东西。如果我正在寻找非启动表单的第一个实例,我必须遍历直到找到它):

    Public Sub EventHandler()
        If Not IsFirstForm Then
            Dim f As Form1 = My.Application.OpenForms(0)
            f.EventHandler()
            Me.Close()
        ElseIf InvokeRequired Then
            Me.Invoke(New HandlerDelegate(AddressOf EventHandler))
        Else
            ' Do your event handling code '
        End If
    End Sub

首先,它检查它是否在正确的表单上运行——如果不是,则调用正确的表单。然后它检查线程是否正确,如果不正确则调用 UI 线程。然后它运行事件代码。我不喜欢这可能是三个电话,但我想不出另一种方法来做到这一点。它似乎工作得很好,虽然它有点麻烦。如果有人有更好的方法,我很想听听!

再次感谢所有的帮助——这让我发疯了!

4

4 回答 4

3

我认为这也是一个线程问题。您在事件处理程序中使用 Control.Invoke() 吗?.NET 通常会在您调试应用程序时捕获违规行为,但在某些情况下它不能。NotifyIcon 就是其中之一,没有用于检查线程亲和性的窗口句柄。

OP更改问题后编辑:

一个经典的 VB.NET 陷阱是通过类型名称来引用 Form 实例。像 Form1.NotifyIcon1.Something。当您使用线程时,这不会按预期工作。它将创建Form1 类的新实例,而不是使用现有实例。该实例是不可见的(从未调用过 Show()),并且由于它在不泵送消息循环的线程上运行,因此它作为门钉死了。看到第二个图标出现是一个死的赠品。当您知道您正在从线程中使用它时,获取 InvokeRequired = False 也是如此。

您必须使用对现有表单实例的引用。如果这很难实现(您通常将“Me”作为参数传递给类构造函数),您可以使用 Application.OpenForms:

  Dim main As Form1 = CType(Application.OpenForms(0), Form1)
  if (main.InvokeRequired)
    ' etc...
于 2008-11-07T21:13:08.530 回答
0

使用 Control.InvokeRequired 确定您是否在正确的线程上,如果不是,则使用 Control.Invoke。

于 2008-11-07T21:13:20.077 回答
0

您应该查看 Form 上的 Invoke 方法的文档。它将允许您使更新表单的代码在拥有该表单的线程上运行(它必须这样做,Windows 表单不是线程安全的)。类似于 Private Delegate Sub UpdateStatusDelegate(ByVal newStatus as String)

public sub UpdateStatus(ByVal newStatus as String) If Me.InvokeRequired Then Dim d As New UpdateStatusDelegate(AddressOf UpdateStatus) Me.Invoke(d,new Object() {newStatus}) Else '更新表单状态 End If

如果您提供一些示例代码,我很乐意提供一个更量身定制的示例。

在 OP 说他们正在使用 InvokeRequired 之后进行编辑。

在调用 InvokeRequired 之前,请检查表单句柄是否已创建,我相信有一个 HandleCreated 属性。如果控件当前没有句柄,则 InvokeRequired 总是返回 false,这意味着代码不是线程安全的,即使您已经做了正确的事情来做到这一点。当您发现时更新您的问题。一些示例代码也会有所帮助。

于 2008-11-07T21:14:27.937 回答
0

在 c# 中它看起来像这样:

private EventHandler StatusHandler = new EventHandler(eventHandlerCode)
void eventHandlerCode(object sender, EventArgs e)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(StatusHandler, sender, e);
        }
        else
        {
          //do work
        }
    }
于 2008-12-02T23:05:23.110 回答