locked
Invoke or BeginInvoke cannot be called on a control until the window handle has been created. RRS feed

  • Question

  • Hi there! I'm aware that delegates and the like are needed to access a control from another thread, but even with this in place, I get that exception. Here is an overview of my setup:

     

    There is a form with a text box, not initially shown to the user, that collects and displays messages from the various threads running in my program. A button on the main form labelled "Show/Hide Messages" is supposed to show/hide the messages form. Several threads are started to initalize network communications etc and write to the messages form by way of Invoke and a custom WriteMessage(string msg) method. That method is passed to the class containing the threads through a delegate called WriteCallback. This works fine because I know that the text box of the messages form contains the messages sent to it when I step through the code.

     

    The Problem: As soon as I press the Show/Hide button, the exception is thrown. This is confusing and doesn't make sense--the messages form is a member variable of the main form's class and so is running on the same thread as the button. I even confirmed this using the Threads window of Visual Studio. Why does this happen and how do I fix it? This is really important because my company is depending on this program to test equipment. Thanks so much for any help. Feel free to ask for more info about the situation.

     

    --Alan

    Tuesday, August 7, 2007 10:08 PM

Answers

  • You cannot send a message to a window/form, which does not exist yet. Control.Invoke or control.BeginInvoke post a message to the message queue by means of the API function PostMessage(hWnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr) and the first argument of this message (hWnd) is the handle of the window, which shall receive the message. Because this window does not exist when you press the button, the system cannot post the message, which is exactly what the error message tells you.

    Wednesday, August 8, 2007 7:54 AM
  • It depends on how you implemented the Hide command.  If you actually close the form and use Invoke/BeginInvoke on that form, you would indeed get that error.  Windows Forms needs the Handle to figure out which thread to marshal the method call to.  Any form or control qualifies for use with the Invoke() call, use your main form instead (usually "this").  Better yet, just don't call Invoke() if there is no form to display the data.  Implement the FormClosing event handler for the form to set the reference to null.
    Wednesday, August 8, 2007 2:50 PM
  • Why not just collect the messages in the main form - for example by means of the StringBuilder class, and then show this string when you open the display window?

    Friday, August 10, 2007 7:51 AM

All replies

  • You cannot send a message to a window/form, which does not exist yet. Control.Invoke or control.BeginInvoke post a message to the message queue by means of the API function PostMessage(hWnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr) and the first argument of this message (hWnd) is the handle of the window, which shall receive the message. Because this window does not exist when you press the button, the system cannot post the message, which is exactly what the error message tells you.

    Wednesday, August 8, 2007 7:54 AM
  • It depends on how you implemented the Hide command.  If you actually close the form and use Invoke/BeginInvoke on that form, you would indeed get that error.  Windows Forms needs the Handle to figure out which thread to marshal the method call to.  Any form or control qualifies for use with the Invoke() call, use your main form instead (usually "this").  Better yet, just don't call Invoke() if there is no form to display the data.  Implement the FormClosing event handler for the form to set the reference to null.
    Wednesday, August 8, 2007 2:50 PM
  • Well, here is a code snippet that might help (I've taken out code that doesn't have to do with my problem):

     

    This is the Messages Form file:

    Code Snippet

    namespace Data_Generator

    {

    public partial class MessagesForm : Form

    {

     

    public MessagesForm()

    {

    InitializeComponent();

    }

     

    public void Write(string message)

    {

    // Set the text color and write the text.

    if (messagesRichTextBox.InvokeRequired)

    {

    messagesRichTextBox.Invoke(new CommunicationsSupportClass.WriteDelagate(Write), message, source);

    }

    else

    {

    messagesRichTextBox.SelectedText += message.Trim() + "\n";

    }

    }

    }

    }

     

     

    This is part of the CommunicationsSupportClass:

    Code Snippet

    namespace Communications_Support

    {

    public class CommunicationsSupportClass

    {

    public delegate void WriteDelagate(string message);

    private static Thread automationController;

     

    public static void InitializeCommunications(WriteDelagate write)

    {

    // Set up some variables.

    writeMessage = write;

     

    automationController = new Thread(new ThreadStart(AutomationControllerServer));

    automationController.Start();

    }

     

    private static void AutomationControllerServer()

    {

    writeMessage("Automation controller server is now initialized.");

    }

    }

    }

     

     

    This is part of the main form:

    Code Snippet

    namespace Data_Generator

    {

    public partial class DataGeneratorForm : Form

    {

    private MessagesForm messages;

     

    private void DataGeneratorForm_Load(object sender, EventArgs e)

    {

    // Set up the Messages window.

    messages = new MessagesForm();

     

    // Set up the network connections.

    CommunicationsSupportClass.InitializeCommunications(messages.Write);

    }

     

    private void messagesCheckBox_CheckedChanged(object sender, EventArgs e)

    {

    // Show or hide the messages window.

    if (messagesCheckBox.Checked)

    {

    if (messages.InvokeRequired)

    {

    messages.Invoke(new MessagesForm.ShowCallback(ShowMessages));

    }

    else

    {

    messages.Show();

    }

    }

    else

    {

    if (messages.InvokeRequired)

    {

    messages.Invoke(new MessagesForm.ShowCallback(HideMessages));

    }

    else

    {

    messages.Hide();

    }

    }

    }

    }

    }

     

     

     

    This is pretty much my setup. I do creat an instance of the messages form before I start any threads; why isn't the handle being created then? How do I create the handle so that I can write to the text box before it is shown? Thanks again.
    Wednesday, August 8, 2007 7:06 PM
  • I don't see you calling messages.Show() in the Load event.  Your messageCheckBox' CheckedChanged event incorrectly uses message.InvokeRequired.  That's not necessary, it would always return false.  However, without having called Show(), the Handle value of messages will be null and your code will bomb.

    The way you are doing it now, you'll have to call messages.Show() immediately to avoid the crash.  A better approach would be to not create messages until the user clicks the checkbox.  But that requires you to reorganize your code.
    Wednesday, August 8, 2007 8:52 PM
  • So basically this means that I can't do anything with the messages form until I show it. I can't have it shown when the program starts up so there's got to be another way. I'm thinking maybe I can have a queue of messages and when the form is shown, I have an OnShow event handler that will dequeue the messages onto the text box. Is that genius or what?

    Wednesday, August 8, 2007 9:40 PM
  • Why not just collect the messages in the main form - for example by means of the StringBuilder class, and then show this string when you open the display window?

    Friday, August 10, 2007 7:51 AM