"Cross-thread operation not valid" exception on inner controls

As an aside - to save yourself having to create countless delegate types:

if (panel.InvokeRequired)
{
    panel.Invoke((MethodInvoker) delegate { AddControlToPanel(panel,ctrl); } );
    return;
}

Additionally, this now does regular static checks on the inner call to AddControlToPanel, so you can't get it wrong.


'panel' and 'ctrl' must be created on the same thread, ie. you cannot have panel.InvokeRequired return different value than ctrl.InvokeRequired. That is if both panel and ctrl have the handles created or belong to a container with the handle created. From MSDN:

If the control's handle does not yet exist, InvokeRequired searches up the control's parent chain until it finds a control or form that does have a window handle. If no appropriate handle can be found, the InvokeRequired method returns false.

As it is right now your code is open to race conditions because the panel.InvokeNeeded can return false because the panel is not yet created, then ctrl.InvokeNeeded will certainly return false because most likely ctrl is not yet added to any container and then by the time you reach panel.Controls.Add the panel was created in the main thread, so the call will fail.


Where is pnlFoo being created, and in which thread? Do you know when its handle is being created? If it's being created in the original (non-UI) thread, that's the problem.

All control handles in the same window should be created and accessed on the same thread. At that point, you shouldn't need two checks for whether Invoke is required, because ctrl and panel should be using the same thread.

If this doesn't help, please provide a short but complete program to demonstrate the problem.


Here is a working piece of code :

public delegate void AddControlToPanelDlg(Panel p, Control c);

        private void AddControlToPanel(Panel p, Control c)
        {
            p.Controls.Add(c);
        }

        private void AddNewContol(object state)
        {
            object[] param = (object[])state;
            Panel p = (Panel)param[0];
            Control c = (Control)param[1]
            if (p.InvokeRequired)
            {
                p.Invoke(new AddControlToPanelDlg(AddControlToPanel), p, c);
            }
            else
            {
                AddControlToPanel(p, c);
            }
        }

And here is how I tested it. You need to have a form with 2 buttons and one flowLayoutPanel (I chose this so I didn't have to care about location hwhen dinamically adding controls in the panel)

private void button1_Click(object sender, EventArgs e)
        {
            AddNewContol(new object[]{flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString())});
        }

        private void button2_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(AddNewContol), new object[] { flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString()) });
        }

I that he probem with your exaple is that when you get in the InvokeRequired branch you invoke the same function wou are in, resulting in a strange case of recurssion.


In your own answer you state:

To clarify things: "panel" is created on the base thread and "ctrl" on the new thread

I think this might be the cause of your problem. All UI elements should be created on the same thread (the base one). If you need to create "ctrl" as a consequence of some action in the new thread, then fire an event back to the base thread and do the creation there.


Comments

    Comments are closed.

    Recent Posts