8

我刚刚发布了一个关于如何让代表更新另一个表单上的文本框的问题。就在我认为我使用 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 未处理 - 在创建窗口句柄之前,不能对控件调用 Invoke 或 BeginInvoke。

我已经尝试在 Log 项上创建一个句柄……但这没有用。问题是我不知道我在做什么,我在谷歌上进行了广泛的搜索,却发现了模糊的答案。

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

谢谢!!!

编辑:

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

Log是我的 RichTextBox1 (Log.Items.Add(message)) 我将其重命名为 Log,以便重新键入。

我正在从不同的表单调用 updateLog(message) ......让我在这里更新它(尽管我从它调用 updateLog(message) 没有区别仍然给我这个错误)

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

第二次编辑:

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

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

静态主 mainClass = new Main();

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

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

这就是我所追求的,感谢其中一位给出答案的人:

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

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

上面的代码不起作用,我什至找不到 Application. 这甚至是 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);
        }
    }
}
4

9 回答 9

12

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

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

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

于 2009-02-04T20:47:10.967 回答
12

对了,我要重新开始了。

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

出现您的问题是因为在创建窗体的窗口之前调用了 logAdd 方法。这意味着在 Form 实例被实例化之后但在 Window 句柄创建之前的启动过程中的某个地方,正在尝试调用 logAdd。如果您向 logAdd 添加断点,您应该能够看到该调用在做什么。您会发现调用是在您在 logger 类中创建的 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);
    }
}
于 2009-02-04T21:53:04.563 回答
2

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

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

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

于 2009-02-04T20:30:50.673 回答
2

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

在您可以调用 BeginInvoke 或 Invoke 之前,必须显示您的表单“Main”(因此创建了一个窗口句柄)。

在这些情况下,我通常做的是将它留给 Form 来确定它是否需要使用对 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 本身负责确定它是否需要异步调用该方法。

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

于 2009-02-04T21:07:44.153 回答
1

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

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

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

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

希望这可以帮助。

于 2009-02-04T21:13:39.323 回答
1

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

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

于 2011-03-02T15:27:28.807 回答
0

logAddDelegate(消息);

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

于 2009-02-04T20:35:25.733 回答
0

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

于 2009-02-04T20:51:31.893 回答
0

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

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}
于 2012-06-15T05:56:56.033 回答