views:

5265

answers:

8

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:

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.

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 extensively only 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?

Thank you!!!

EDIT:

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

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

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)

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:

static Main mainClass = new Main();

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

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.

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.

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

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);
        }
    }
}
+2  A: 

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.

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.

Jekke
But my textbox is created! I can see it before I click the button. If it isn't created...how do I "create it"! Cause I have NO clue...
OneShot
You aren't calling Invoke on your TextBox, you are calling it on the Form. If this is getting called BEFORE the form is created, or AFTER it has been destroyed, you will get this error.
Jeff Yates
I am trying to add something to the TextBox that exists on the second Version of main because I do "Main myClass = new Main()" I need to figure out how to create myClass while not having to create a new blueprint of Main().
OneShot
@OneShot: see my other answer for a possible solution to this.
Jeff Yates
A: 

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

SDX2000
How could it not be loaded...before I click the button I am looking straight at the TextBox.
OneShot
+3  A: 

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 invokeOnFormThread instead 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.

Michael Meadows
Ill see if I can convert that to my code...im not exactly sure what all of that does and why I should do it though. So far I can't figure out how to use that yet.
OneShot
This is clever. It lets logic tests run without a form.
Rick Minerich
A: 

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.

Jamie Penney
+2  A: 

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.

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).

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:

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.

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.

Chris Holmes
Thanks! Yeah I know it wont fix that...how can a simple RichTextBox1 (Log) not be a valid or a loaded form though???
OneShot
It's not the textbox that is invalid. It's the Form. The Form has to have its handle created before you make any calls to it. The handle ONLY gets created when the form gets shown. Try some Console.WriteLines and see if you can determine if/when the calls are happening and in what order.
Chris Holmes
I found out that in my Logging class I am creating a NEW instance of my MainClass...thats why it isnt created yet. Now I have to figure out how to instead of making a new instance of MainClass in my loggingclass, to make a new instance of the same class.
OneShot
+1  A: 

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.

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:

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.

Hope this helps.

GWLlosa
My God I think this may do it, I haven't gotten it to work yet though. I cant seem to find Application.OpenForms...where is that??
OneShot
System.Windows.Forms.Application.OpenForms
GWLlosa
http://stackoverflow.com/questions/513131/c-compile-error-invoke-or-begininvoke-cannot-be-called-on-a-control-until-the-w/513529#513529He said it better than I did.
GWLlosa
+4  A: 

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.

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.

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.

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:

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);
    }
}
Jeff Yates
Okay so you are telling me:In my Logging class, the fact that I create a NEW object of Main has nothing to do with it? I am simply saying, I believe that I am trying to edit the TextBox that resides on the Main mainClass = new Main(); I dont see the new Main, so the TextBox on it is never loaded.
OneShot
No, your new Main has everything to do with it. See the code example I just added on how you might resolve this.
Jeff Yates
I finally did it. Thank you so very much, you are my hero.
OneShot
No problem. Apologies for burying you in jargon before - it can be difficult to aim answers at the appropriate technical level and it gets frustrating for me when I can't communicate what I mean (my fault, not yours). I'm glad I could help. Onward and best of luck.
Jeff Yates
This answer is what I would have written, where I as capable of clear communication. If anyone has trouble reading my answer, just read this one instead. :)
GWLlosa
+1 for the tip to add breakpoint, that helped me solve the problem. Sometimes I forget the most obvious things...
Miel
A: 

Thanks so much for the answer. You guys saved my day!

mdashali