ITFreak
ITFreak

Reputation: 93

C++/CLI WinForms: BeginInvoke Error

I am unable to figure out the reason for this error:

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

Here is my (stripped) code:

private: delegate void MyDelegate(Object^ openFileDialog1);
ListView^ myDelegate;

private: void import_links(Object^ openFileDialog1) {
            myDelegate = (gcnew System::Windows::Forms::ListView());
            myDelegate->BeginInvoke( gcnew MyDelegate( this, &Form1::import_links ), openFileDialog1);

            //do some work here
}
private: System::Void Import_LinkClicked(System::Object^  sender, System::Windows::Forms::LinkLabelLinkClickedEventArgs^  e) {
        OpenFileDialog^ openFileDialog1 = gcnew OpenFileDialog;

        if ( openFileDialog1->ShowDialog() == System::Windows::Forms::DialogResult::OK )
        {
            Thread^ importThread = gcnew Thread(gcnew ParameterizedThreadStart(this,&Form1::import_links));
            importThread->Start(openFileDialog1);
        }
    }

Please let me know the solution.

Upvotes: 0

Views: 2151

Answers (1)

Hans Passant
Hans Passant

Reputation: 942000

        myDelegate = (gcnew System::Windows::Forms::ListView());

Basic problems with this statement:

  • It is not a delegate, it is a ListView object
  • Controls have a strong association with the thread on which they are created. Creating a control on a worker thread like you do is never correct
  • A control requires a Parent to become visible and useful, it doesn't have one
  • A control requires the thread to run a dispatcher loop so it can get messages. Such a thread must call Application::Run(). Your worker thread doesn't
  • The underlying window for a control is created lazily, when needed to become visible. Since it doesn't have a parent and isn't visible, there wasn't any need to create the window yet. So it won't have a valid Handle property, as the exception tells you
  • BeginInvoke() ensures that the invoked code runs on the thread that owns the control. Since it is the worker thread that owns it, BeginInvoke() can never actually invoke to another thread

You already have a reference to an object that is owned by the correct thread. It is this. So correct code would look like:

void import_links(Object^ openFileDialog1) {
    if (this->InvokeRequired) {
       this->BeginInvoke( gcnew MyDelegate( this, &Form1::import_links ), openFileDialog1);
    }
    else {
        //do some work here
    }
}

But do note the ultimate fallacy, you created a worker thread and the only thing it does is call this->BeginInvoke(). That takes a fraction of a microsecond. Creating a thread to do so little work is never useful.

Refactor your code, use a BackgroundWorker. Let its DoWork event handler only do the kind of things that are expensive, like importing a file. Let its RunWorkerCompleted event only do the kind of things that need to happen on the UI thread, such as displaying the result of the import and hiding the "I'm working on it" notification.

Upvotes: 3

Related Questions