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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/513131/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 06:20:10  来源:igfitidea点击:

C# compile error: "Invoke or BeginInvoke cannot be called on a control until the window handle has been created."

c#delegatesinvokehandleruntime-error

提问by OneShot

I just posted a question about how to get a delegate to update a textbox on another form. Just when I thought I had the answer using Invoke...this happens. Here is my code:

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

Main Form Code:

主要表格代码:

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

}

Logging Class Code:

日志类代码:

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);
        }
    }
}
  • Compile Error:

    InvalidOperationException was unhandled - Invoke or BeginInvoke cannot be called on a control until the window handle has been created.

  • 编译错误:

    InvalidOperationException 未处理 - 在创建窗口句柄之前无法在控件上调用 Invoke 或 BeginInvoke。

I already tried to create a handle on the Log item...but that didn't work. The problem is I have NO CLUE what I am doing and I have searched Google extensivelyonly to find vague answers.

我已经尝试在日志项上创建一个句柄……但这没有用。问题是我不知道我在做什么,我已经广泛搜索谷歌只是为了找到模糊的答案。

Please tell me how to create the handle before I invoke this delegate. While you are at it, give me some ways I can make this code more simple. For example, I dont want two Add functions... I had to do that because there was no way for me to find an item to invoke from the Logging class. Is there a better way to accomplish what I need to do?

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

Thank you!!!

谢谢!!!

EDIT:

编辑:

My project is fairly large, but these are the only items causing this specific problem.

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

Logis my RichTextBox1 (Log.Items.Add(message)) I renamed it to Log so it is easier to retype.

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

I am calling updateLog(message) from a different form though...let me update that in here (although it makes no difference where I call updateLog(message) from it still gives me this error)

我正在从不同的形式调用 updateLog(message) ……让我在这里更新它(尽管我从它调用 updateLog(message) 的地方没有区别仍然给我这个错误)

You guys are going to have to make things more simpler for me...and provide examples. I don't understand HALF of everything you guys are saying here...I have no clue on how to work with Invoking of methods and Handles. I've researched the crap out of it too...

你们将不得不让我的事情变得更简单……并提供示例。我不明白你们在这里说的所有内容的一半......我不知道如何使用方法和句柄的调用。我也研究过它的废话......

SECOND EDIT:

第二次编辑:

I believe I have located the problem, but do not know how to fix it.

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

In my logging class I use this code to create mainClass:

在我的日志类中,我使用此代码创建 mainClass:

static Main mainClass = new Main();

static Main mainClass = new Main();

I am creating a entirely new blueprint replica to Main(), including Log(the richtextbox I am trying to update)

我正在为 Main() 创建一个全新的蓝图副本,包括Log(我正在尝试更新的富文本框)

When I call updateLog(message) I believe I am trying to update the Log (richtextbox) on the second entity of Main() otherwise known as mainClass. Of course, doing so will throw me this exception because I haven't even seen that replica of the current Main I am using.

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

This is what I am shooting for, thanks to one of the people that gave an answer:

这就是我要拍摄的内容,感谢其中一位给出答案的人:

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

I need to create mainClass not with the new() operator because I dont want to create a new blueprint of the form I want to be able to edit the current form.

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

The above code doesn't work though, I can't even find Application. Is that even C# syntax?

但是上面的代码不起作用,我什至找不到 Application. 这甚至是 C# 语法吗?

If I can get the above code to work, I think I can resolve my issue and finally lay this problem to rest after a couple of HOURS of seeking for answers.

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

FINAL EDIT:

最终编辑:

I figured it out thanks to one of the users below. Here is my updated code:

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

Main Form Code:

主要表格代码:

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

}

Logging Class Code:

日志类代码:

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

采纳答案by Jeff Yates

Right, I'm going to start again.

对了,我又要开始了。

In order to understand what is happening, you need to understand how .NET and Windows relate to one another. .NET runs on Windows and wraps many of the native, Win32 concepts like a window, a listview, an editbox (the Win32 name for a standard textbox). This means that you can have a valid .NET instance of a TextBox or a Form, but not have the underlying Windows version of that item (EditBox, or Window) yet. When HandleCreated is true, the Windows version of the item is created.

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

Your issue is occurring because something is leading to the logAdd method being called before the Form's Window has been created. This means somewhere during your startup after the Form instance has been instantiated but before the Window handle has been created, something is trying to call logAdd. If you add a breakpoint to logAdd, you should be able to see what is doing that call. What you will find is that the call is being made on the Main instance you create in your logger class and NOT the Main instance that is actually running. As the logger instance never gets shown, the window handle is not created, and so you get your error.

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

The general way an application runs is to call Application.Run(new Main()) in your startup method, which is usually in the Program class and called Main. You need your logger to point to this instance of main.

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

There are several ways to get the instance of the form, each with its own caveats, but for simplicity you could expose the instance off the Main class itself. For example:

有多种方法可以获取表单的实例,每种方法都有自己的注意事项,但为简单起见,您可以将实例暴露在 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);
    }
}

回答by Yes - that Jake.

When you get this error, it almost always means that you've attempted to act on a control or form before it was actually created.

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

In WinForms, GUI elements have two semi-independent lives: as classes in memory and as entities in the operating system. As such, it's possible to reference a control in .net that hasn't actually been created yet. The "handle being created" refers to having a number assigned to the control by the OS to allow programs to manipulate its properties.

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

In this case, most errors can be eliminated by setting a flag at the end of the form's load event and only attempting to manipulate the form's controls after that flag has been set.

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

回答by Autodidact

logAddDelegate(message);

日志添加委托(消息);

I think you are calling this before the Form_Load event has been raised. Fix your code to wait for the form to load before calling logAddDelegate(...) i.e. before calling Logging.updateLog()

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

回答by Michael Meadows

I have solved this in the past using the following method:

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

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

Call invokeOnFormThreadinstead of Invoke. It will only use the form's thread if a handle has already been created, otherwise it will use the caller's thread.

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

回答by Jamie Penney

Is this your exact code? You are calling this.Log.Items.Add(message);in your add(string) method, but your logging class is called Logging, not Log. Have you got another form called Log perhaps? If that form hasn't been created when you call the add method, you will get this exception.

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

回答by Chris Holmes

It's a runtime error, not a compiler error.

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

Your Form, "Main", has to be displayed (hence a window handle created) before you can make calls to BeginInvoke or Invoke on it.

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

What I usually do in these situations is leave it up to the Form to determine if it needs to use a call to BeginInvoke or Invoke. You can test that with a call to InvokeRequired (check MSDN).

在这些情况下,我通常做的是让 Form 来确定它是否需要使用对 BeginInvoke 或 Invoke 的调用。您可以通过调用 InvokeRequired 来测试(查看 MSDN)。

So for starters, I'd get rid of the logAddDelegate call in the Loggin class's updateLog method. Just make a straight call to the form to add a log. Like so:

所以对于初学者来说,我会去掉 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);
    }
 }

}

}

So you can see, the Form itself is in charge of determining if it needs to call the method asynchronously or not.

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

However, this still won't fix your runtime error, because you're making a call to the form before its been displayed. You can check to see if the form's Handle is null or not, and that will at least allow you to verify whether or not you're dealing with a valid Form.

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

回答by GWLlosa

That error tends to happen if you invoke on a window that has not yet been 'shown'. Are you sure you're not creating a second instance of the main class with your code in the Logging class (specifically, the first line)? It may be that the main form you are calling log on is NOT the main form you are looking at. If you want to check, add a call to "MainClass.Show()" just inside your logging call. If you get a second copy of the main form popping up, then the problem is that your logging class is not referencing the right 'instance' of your form.

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

Think of the class as a 'blueprint'. Each instance of the class (created with the word 'new') is another object, created from the blueprint. Just because two objects (in this case, your two main forms) share the same blueprint, doesn't mean that you can use them interchangeably. In this case, you already have a main form, and you want to 're-use' it. You can try:

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

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

inside your log function instead of what you currently have. The difference is that the call to Application.OpenForms.OfType().First will go into your application, and retrieve the ACTUAL main form that you are seeing (technically, it will retrieve the first instance of it) and make your invocation on that form, directly.

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

Hope this helps.

希望这可以帮助。

回答by Lucy

This is to help in case any other person got caught up with this. My problem: VB.net: “Invoke or BeginInvoke cannot be called on a control until the window handle has been created.” I closed a form which has a handler for the event am calling to update the delegate, without removing the handler for the event.

这是为了在任何其他人遇到此问题时提供帮助。我的问题:VB.net:“在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。” 我关闭了一个表单,该表单具有用于更新委托的事件处理程序,但没有删除该事件的处理程序。

What I did: When I close the form, I removed all the handlers, and assigned them back when i opened the form. It solved the problem.

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

回答by Mathieu

I found the InvokeRequirednot reliable, so I simply use

我发现InvokeRequired不可靠,所以我只是使用

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}