【问题标题】:BeginInvoke fails because a window handle has not been createdBeginInvoke 失败,因为尚未创建窗口句柄
【发布时间】:2025-12-04 12:40:01
【问题描述】:

我正在维护一个包含客户信息的程序。它由许多表格组成,每个表格都显示数据库中的一些相关信息。执行以下操作后,此错误以单一形式出现

  1. 打开客户搜索表单
  2. 在 customerinfo 表单中查看随机客户 A 信息
  3. 打开 crm 表单,它会自动显示客户 A。然后通过拖放向他添加一个文件。
  4. 关闭最后两个表单并随机选择客户 B 并执行相同操作。
  5. 关闭最后两个表单并选择客户 A 并添加一个新文件。错误!!!

下面是失败的代码:

private void FireFileCountChanged() {
    if (FileCountChanged != null)
        BeginInvoke(new DeferEvent(FireFileCountChangedDeferred), 2); // FAILS

“System.Windows.Forms.dll 中发生了“System.InvalidOperationException”类型的未处理异常

附加信息:在创建窗口句柄之前,不能对控件调用 Invoke 或 BeginInvoke。"

我尝试添加以下内容:

private void FireFileCountChanged() {
        if (FileCountChanged != null && this.Handle != null) // CHANGED AND FAILS.
            BeginInvoke(new DeferEvent(FireFileCountChangedDeferred), 2);
    }

但是 this.handle 给出:

“this.Handle”引发了“System.ObjectDisposedException”类型的异常,并且 "无法访问已释放的对象。\r\n对象名称:'AttachmentsControl'。"

然后我在方法的第一行添加了 10 秒的超时时间,但句柄仍然没有创建。当其中一个窗口关闭时,手柄是否以某种方式被处置?对此可以做些什么?任何帮助表示赞赏。我有点卡住了。

private void FireFileCountChangedDeferred(int repostCount) {
    if (FileCountChanged != null) {
        if (repostCount > 0) {
            //black magic is somehow involved in getting this event to fire *after* the filewatcher reports the change.
            System.Threading.Thread.Sleep(10);
            BeginInvoke(new DeferEvent(FireFileCountChangedDeferred), repostCount - 1);
        } else
            FileCountChanged(this, null);
    }
}

private void CopyFiles(string[] files, bool reload) {
    if (CreatePath()) {
        foreach (string src in files) {
            try {
                string dest = MakeSafeFilename(src);
                File.Copy(src, dest);
                FireFileCountChanged();
            } catch (Exception ex) {
                //Util.Print("Copy ex: {0}", ex.Message);
                ErrMsg("Error while copying:{1}{0}", ex.Message, environment.NewLine);
            }
        }
    }
}

private void Lstv_DragDrop(object sender, DragEventArgs ea) {
    if (m_CanAdd) {
        string[] files = GetDraggedFiles(ea);
        if (files != null)
            CopyFiles(files, true);
        else if (OutlookDataObject.HoldsOutlookData(ea) && CreatePath()) {
            try {
                OutlookDataObject.CopyDroppedFiles(ea, m_Path, OutlookFilenameCallback);
            } catch (Exception ex) {
                //Util.Print("Copy ex: {0}", ex.Message);
                ErrMsg("Error copying from Outlook:{1}{0}", ex.Message, Environment.NewLine);
            }
        }
    }
}

解决方案

private void FireFileCountChanged() {
                while (!this.IsHandleCreated) // added
                          System.Threading.Thread.Sleep(100); //added

    if (FileCountChanged != null)
        BeginInvoke(new DeferEvent(FireFileCountChangedDeferred), 2);

【问题讨论】:

    标签: c# handle begininvoke


    【解决方案1】:

    您需要检查IsHandleCreated 属性,而不是将Handle 与null 进行比较。读取 Handle 属性本身被视为 UI 操作。

    private void FireFileCountChanged() {
        if (FileCountChanged != null && this.IsHandleCreated)
            BeginInvoke(new DeferEvent(FireFileCountChangedDeferred), 2);
    }
    

    但是,根据重现错误所需采取的复杂步骤,我怀疑这里存在一些表单实例重用问题或其他更复杂的问题,这不仅仅是问题使这个对 BeginInvoke 的调用起作用。

    【讨论】:

    • 解决了!我已将解决方案添加到问题的底部。非常感谢。
    • 我温和地建议您不要使用Thread.Sleep 来等待句柄被创建。基于 HandleCreated 事件的解决方案会更优雅,并且不会永远占用线程。
    • 我会这样做的。但是 FireFileCountChangedDeferred 会被忽略并且文件永远不会添加到列表中吗?
    • 如果你做对了,显然不会。做正确的事情有点棘手,而且有很多边缘情况。我会要求您考虑这一点 - 如果 FireFileCountChanged 被调用并且没有显示 FileCount 的表单,您是否需要更新 UI,或者您可以退出?如果您确实需要更新 UI(例如维护状态),您是否可以重构您的设计,以便您不需要更新 UI,也许通过在其他地方维护状态?
    • 您似乎正在尝试更新名为 AttachmentsControl 的控件的 UI,并且您可能在拖放/复制序列期间的某个时间更改了该控件的可见性。根据上下文,我猜测此控件显示与客户交互关联的附件列表(从 Outlook 拖动)。我上面的问题可以更清楚地说明 - 如果该控件不可见,您是否需要更新它的内容?
    最近更新 更多