C#编译错误:“在创建窗口句柄之前,无法在控件上调用 Invoke 或 BeginInvoke。”

StackOverflow https://stackoverflow.com/questions/513131

我刚刚发布了一个关于如何让委托更新另一个表单上的文本框的问题。就在我以为我使用 Invoke 得到了答案时……这种情况发生了。这是我的代码:

主表格代码:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Data.OleDb;
using System.Collections.Specialized;
using System.Text;
using System.Threading;

delegate void logAdd(string message);

namespace LCR_ShepherdStaffupdater_1._0
{
    public partial class Main : Form
    {
        public Main()
        {
            InitializeComponent();
        }

        public void add(string message)
        {
            this.Log.Items.Add(message);
        }
        public void logAdd(string message)
        {   /////////////////////////// COMPILER ERROR BELOW ///////////
            this.Invoke(new logAdd(add), new object[] { message }); // Compile error occurs here     
        }////////////////////////////// COMPILER ERROR ABOVE ///////////

        private void exitProgramToolStripMenuItem_Click(object sender, EventArgs e) 
        {
            Application.Exit(); 
        }
        private void aboutToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            Form aboutBox = new AboutBox1(); 
            aboutBox.ShowDialog(); 
        }

        private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
        }

        private void settingsToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            settingsForm.settings.ShowDialog();
        }

        private void synchronize_Click(object sender, EventArgs e)
        {
            string message = "Here my message is"; // changed this
            ErrorLogging.updateLog(message);  // changed this
        }

    }

    public class settingsForm 
    {
        public static Form settings = new Settings();
    }

}

记录类代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LCR_ShepherdStaffupdater_1._0
{
    public class Logging
    {
        static Main mainClass = new Main();
        static logAdd logAddDelegate;

        public static void updateLog(string message)
        {
            logAddDelegate = mainClass.logAdd;
            logAddDelegate(message);
        }
    }
}
  • 编译错误:

    InvalidOperationException是未经手的 - 在创建窗口句柄之前,无法在控件上调用或开始。

我已经尝试在日志项上创建句柄...但这不起作用。问题是我不知道我在做什么,我已经搜索了谷歌 广泛地 只能找到模糊的答案。

请告诉我如何在调用此委托之前创建句柄。当你在做的时候,请给我一些方法,让我可以让这段代码变得更简单。例如,我不需要两个 Add 函数...我必须这样做,因为我无法找到要从 Logging 类调用的项目。有更好的方法来完成我需要做的事情吗?

谢谢你!!!

编辑:

我的项目相当大,但这些是导致此特定问题的唯一项目。

日志 是我的 RichTextBox1 (Log.Items.Add(message)) 我将其重命名为 Log,以便更容易重新输入。

我正在从不同的形式调用 updateLog(message) ...让我在这里更新它(尽管我从中调用 updateLog(message) 没有什么区别,但仍然给我这个错误)

你们必须让事情对我来说更简单......并提供示例。我不明白你们在这里所说的一切...我不知道如何使用方法和句柄的调用。我也研究过其中的废话...

第二次编辑:

我相信我已经找到了问题,但不知道如何解决。

在我的日志记录类中,我使用以下代码创建 mainClass:

静态主mainClass = new Main();

我正在为 Main() 创建一个全新的蓝图副本,包括 日志 (我正在尝试更新的richtextbox)

当我调用 updateLog(message) 时,我相信我正在尝试更新 Main() 的第二个实体(也称为 mainClass)上的日志(richtextbox)。当然,这样做会给我抛出这个异常,因为我什至没有看到我正在使用的当前 Main 的副本。

这就是我正在拍摄的目的,感谢其中一位给出答案的人:

Main mainClass = Application.OpenForms.OfType<Main>().First();
logAddDelegate = mainClass.logAdd; 
logAddDelegate(message);

我需要创建 mainClass 而不是使用 new() 运算符,因为我不想创建表单的新蓝图,我希望能够编辑当前表单。

上面的代码不起作用,我什至找不到应用程序。这也是 C# 语法吗?

如果我能让上面的代码工作,我想我可以解决我的问题,并在经过几个小时的寻找答案后最终解决这个问题。

最终编辑:

感谢下面的一位用户,我明白了这一点。这是我更新的代码:

主表格代码:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Data.OleDb;
using System.Collections.Specialized;
using System.Text;
using System.Threading;

delegate void logAdd(string message);

namespace LCR_ShepherdStaffupdater_1._0
{
    public partial class Main : Form
    {
        private static Main mainFormForLogging;
        public static Main MainFormForLogging
        {
            get
            {
                return mainFormForLogging;
            }
        }

        public Main()
        {
            InitializeComponent();
            if (mainFormForLogging == null)
            {
                mainFormForLogging = this;
            }
        }

        public void add(string message)
        {
            this.Log.Items.Add(message);
        }
        public void logAdd(string message)
        {
            this.Log.BeginInvoke(new logAdd(add), new object[] { message });
        }

        private void exitProgramToolStripMenuItem_Click(object sender, EventArgs e) 
        {
            Application.Exit(); 
        }
        private void aboutToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            Form aboutBox = new AboutBox1(); 
            aboutBox.ShowDialog(); 
        }

        private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
        }

        private void settingsToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            settingsForm.settings.ShowDialog();
        }

        private void synchronize_Click(object sender, EventArgs e)
        {
            add("test");
            Logging.updateLog("testthisone");
            //DatabaseHandling.createDataSet();
        }

    }

    public class settingsForm 
    {
        public static Form settings = new Settings();
    }

}

记录类代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LCR_ShepherdStaffupdater_1._0
{
    public class Logging
    {

        static Main mainClass = Main.MainFormForLogging;
        static logAdd logAddDelegate;

        public static void updateLog(string message)
        {
            logAddDelegate = mainClass.logAdd;
            logAddDelegate(message);
        }
    }
}
有帮助吗?

解决方案

好吧,我要重新开始了。

为了了解正在发生的情况,您需要了解 .NET 和 Windows 之间的关系。.NET 在 Windows 上运行,并包装了许多本机 Win32 概念,如窗口、列表视图、编辑框(标准文本框的 Win32 名称)。这意味着您可以拥有 TextBox 或 Form 的有效 .NET 实例,但还没有该项目(EditBox 或 Window)的底层 Windows 版本。当 HandleCreated 为 true 时,将创建该项目的 Windows 版本。

发生您的问题是因为某些原因导致在创建表单窗口之前调用 logAdd 方法。这意味着在启动过程中,在实例化 Form 实例之后但在创建 Window 句柄之前,某个地方正在尝试调用 logAdd。如果您向 logAdd 添加断点,您应该能够看到该调用正在执行什么操作。您会发现,调用是在您在记录器类中创建的 Main 实例上进行的,而不是在实际运行的 Main 实例上进行的。由于记录器实例永远不会显示,因此不会创建窗口句柄,因此您会收到错误。

应用程序运行的一般方式是在启动方法中调用Application.Run(new Main()),该方法通常位于Program类中并称为Main。您需要您的记录器指向 main 的这个实例。

有多种方法可以获取表单的实例,每种方法都有自己的注意事项,但为了简单起见,您可以将实例暴露给 Main 类本身。例如:

public partial class Main : Form
{
    private static Main mainFormForLogging;
    public static Main MainFormForLogging
    {
        get
        {
            return mainFormForLogging;
        }
    }

    public Main()
    {
        InitializeComponent();

        if (mainFormForLogging == null)
        {
            mainFormForLogging = this;
        }
    }

    protected void Dispose(bool disposing)
    {
         if (disposing)
         {
             if (this == mainFormForLogging)
             {
                mainFormForLogging = null;
             }
         }

         base.Dispose(disposing);
    }
}

其他提示

我过去使用以下方法解决了这个问题:

private void invokeOnFormThread(MethodInvoker method)
{
    if (IsHandleCreated)
         Invoke(new EventHandler(delegate { method(); }));
    else
        method();
}

称呼 invokeOnFormThread 而不是调用。如果已经创建了句柄,它将仅使用表单的线程,否则它将使用调用者的线程。

当您收到此错误时,几乎总是意味着您在实际创建控件或窗体之前尝试对其进行操作。

在 WinForms 中,GUI 元素有两个半独立的生命周期:作为内存中的类和操作系统中的实体。因此,可以引用 .net 中尚未实际创建的控件。“正在创建的句柄”是指操作系统为控件分配一个编号,以允许程序操纵其属性。

在这种情况下,可以通过在窗体加载事件结束时设置一个标志并仅在设置该标志后尝试操作窗体的控件来消除大多数错误。

这是运行时错误,而不是编译器错误。

您的窗体“Main”必须先显示(因此创建一个窗口句柄),然后才能调用 BeginInvoke 或 Invoke。

在这些情况下,我通常所做的是将其留给表单来确定是否需要使用对 BeginInvoke 或 Invoke 的调用。您可以通过调用 InvokeRequired 进行测试(查看 MSDN)。

因此,对于初学者来说,我将摆脱 Loggin 类的 updateLog 方法中的 logAddDelegate 调用。只需直接调用表单即可添加日志。就像这样:

public partial class Main : Form
{
    public Main()
    {
        InitializeComponent();
    }

    private delegate void AddNewLogMessageEventHandler(string message);

    public void AddLogMessage(string message)
    {
        object[] args = new object[1];
        args[0] = message;

        if (InvokeRequired)
            BeginInvoke(new AddNewLogMessageEventHandler(AddLog), args);
        else
            Invoke(new AddNewLogMessageEventHandler(AddLog), args);
    }

    private void AddLog(string message)
    {
        this.Log.Items.Add(message);
    }
 }

}

所以你可以看到,Form本身负责确定是否需要异步调用该方法。

但是,这仍然无法修复您的运行时错误,因为您在显示表单之前调用了该表单。您可以检查表单的句柄是否为空,这至少可以让您验证您是否正在处理有效的表单。

如果您在尚未“显示”的窗口上调用,则很容易发生该错误。您确定没有使用 Logging 类中的代码(特别是第一行)创建主类的第二个实例吗?您调用登录的主表单可能不是您正在查看的主表单。如果您想检查,请在日志记录调用中添加对“MainClass.Show()”的调用。如果弹出主表单的第二个副本,则问题在于您的日志记录类没有引用表单的正确“实例”。

将课程视为“蓝图”。该类的每个实例(使用“new”一词创建)都是根据蓝图创建的另一个对象。仅仅因为两个对象(在本例中为您的两个主要形式)共享相同的蓝图,并不意味着您可以互换使用它们。在这种情况下,您已经有了一个主表单,并且您想要“重用”它。你可以试试:

MainClass myMainForm = Application.OpenForms.OfType<MainClass>().First();
logAddDelegate = myMainForm.logAdd; 
logAddDelegate(message);

在您的日志函数中而不是您当前拥有的函数中。不同之处在于,对 Application.OpenForms.OfType().First 的调用将进入您的应用程序,并检索您所看到的实际主表单(从技术上讲,它将检索它的第一个实例)并对其进行调用形式,直接。

希望这可以帮助。

这是为了防止其他人遇到这种情况。我的问题:VB.net:"在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke"。 我关闭了一个表单,该表单有一个事件处理程序,我调用它来更新委托,但没有删除事件处理程序。

我做了什么:当我关闭表单时,我删除了所有处理程序,并在打开表单时将它们分配回来。它解决了问题。

logAddDelegate(消息);

我认为您是在引发 Form_Load 事件之前调用此函数。修复您的代码以在调用 logAddDelegate(...) 之前等待表单加载,即在调用 Logging.updateLog() 之前

这是你的确切代码吗?你正在呼唤 this.Log.Items.Add(message); 在您的 add(string) 方法中,但您的日志记录类称为 Logging,而不是 Log。您是否还有另一种名为 Log 的表格?如果在调用 add 方法时尚未创建该表单,您将收到此异常。

我找到了 InvokeRequired 不可靠,所以我只是使用

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top