0

I am trying to find a way to change the parent of a thread of a winform back to the GUI thread. Right now i have to create the form from another thread but accessing become impossible from the main form that has the reference to it.

here a sample of what i have

public partial class MainForm : Form
{
    // local instance of the sub form
    private ViewForm SubForm { get; set;} = null;

    public MainForm()
    {
        InitializeComponent();

        Task.Run(() =>
        {
            // set the sub form             
            SubForm = new ViewForm();
        }

        // call the rest of the initialization of main form
        InitializeCustomControls();
    }

    private void OpenViewWindow_Click(object sender, EventArgs e)
    {
        // if the window is instanciated
        if (SubForm != null)
        {
            SubForm.Show();                
        }
    }        
}

The ViewForm window is not a Form object. it's a custom third party window. It has a lot of controls and templates mixed with themes. The sole call to a new empty constructor can take up to 7 seconds hence why i need to create it on another thread while i continue loading my main window.

Right now i can call any method in the window except .Show() which always fail due to thread creation restriction. I would like to stay away from creating the thread as an endless running thread that will wait and read some object to will tell him when to show and hide the window.

the is the .Show() error :

Cross-thread operation not valid: Control 'ViewForm' accessed from a thread other than the thread it was created on.

I did try the following instead but it still freeze my interface :

Task.Run(() =>
{
    // set the sub form   
    this.Invoke(new MethodInvoker(delegate
    {
        SubForm = new ViewForm();
    }));
}

What i would like is something like a fire and forget instantiation of a GUI object.

jjj
  • 1,136
  • 3
  • 18
  • 28
Franck
  • 4,438
  • 1
  • 28
  • 55
  • I didn't read your whole post, but the title of your question and the first sentence stood out. The answer is: you should *never* run UI code on anything other than the main thread to begin with. It's a golden rule in UI programming. So, if you follow that, then you won't have this problem. – rory.ap Feb 03 '17 at 18:33
  • 2
    If you've got a 3rd party form that freezes the UI thread for 7 seconds on startup, then it has a major bug in it. Either don't use that buggy 3rd party form, or have them fix the bug. – Servy Feb 03 '17 at 18:34
  • @rory.ap While that was my same first reaction, the buggy code is apparently in 3rd party code, so it's not a rule the OP is in a position to enforce. – Servy Feb 03 '17 at 18:35
  • `if (SubForm != null){ SubForm.BeginInvoke(new MethodInvoker(delegate { SubForm.Show()); } }` Just need to use the Invoke from the control running on the secondary thread. – Gusman Feb 03 '17 at 18:39
  • See if you can use [BackgroundWorker](https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx). Its a thread pool built to work with WinForms that automatically handles cross-thread operations on UI elements so you don't have to get the Invoke stuff involved. I *think* it'll handle your problem, but I'm not certain. – Cody Feb 03 '17 at 18:39
  • @Servy You are correct. The control is apparently 'working as intended'. It just has 1000 times more visual elements and also offer animations and stuff like it that is simply not existent on core winforms. I have to use it and it's not my choice. – Franck Feb 03 '17 at 18:46
  • @Gusman That doesn't solve the problem. The thread that the form is created on isn't the main UI thread; it cannot be invoked to, and even if you could, that still wouldn't fix the problem. – Servy Feb 03 '17 at 18:48
  • @Cody If the author of the 3rd party app used a BGW properly to do their non-UI work in a non-UI thread, then yes, that might help. But a BGW doesn't solve the problem presented here. – Servy Feb 03 '17 at 18:48
  • @Franck If you have to use it, then you'll have to talk to that 3rd party and try to get them to fix their buggy code. – Servy Feb 03 '17 at 18:49
  • @Servy using the Invoke of a control created on a secondary thread will execute the code on the secondary thread which created the control and it will work with no cross thread exception. – Gusman Feb 03 '17 at 18:49
  • @Gusman Assuming that thread even has a message loop, which this one does not, so no, it has no way of sending messages to the (non-existent) message loop. – Servy Feb 03 '17 at 18:50
  • @Servy Already done. answer = `Working as design` – Franck Feb 03 '17 at 18:51
  • @Franck Those are your two options, if neither of them is available to you, then you have no remaining options. – Servy Feb 03 '17 at 18:51
  • @Servy You are right, my bad :( – Gusman Feb 03 '17 at 18:55
  • @Sevy so it seems you weren't right, it works, lookt at this: http://stackoverflow.com/questions/2073076/possible-to-construct-form-on-background-thread-then-display-on-ui-thread I've tested it and works without problem – Gusman Feb 03 '17 at 19:09
  • @Franck here is an example on how to do it: http://pastebin.com/Cd8BfnZm – Gusman Feb 03 '17 at 19:12
  • @Gusman thaks but it's not working with that form. As soon as the thread ends the window disappear. When i get to the click event the object is not null and calling the `.Show()` does not throw the exception anymore indeed but the window does no show itself either. I guess i am stuck with the slow buggy control after all. – Franck Feb 03 '17 at 19:25
  • @Franck Look at the example, the trick is to ShowDialog() instead of Show(), it will retain the thread alive until the form is closed. – Gusman Feb 03 '17 at 19:26

1 Answers1

0

Here´s a solution using a BackGroundWorker:

public partial class MainForm : Form
{
    // local instance of the sub form
    private ViewForm SubForm { get; set;} = null;

    public MainForm()
    {
        InitializeComponent();
        backGroundWorker1.RunWorkerAsync();
        InitializeCustomControls();
    }

    private void OpenViewWindow_Click(object sender, EventArgs e)
    {
        // if the window is instanciated
        if (SubForm != null)
        {
            SubForm.Show();                
        }
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        SubForm = new ViewForm();
    }
}

Tested creating two forms, the MainForm pretty much like the one in your sample and the SubView form with a Threading.Sleep(10000) on the constructor.

Josh Part
  • 2,154
  • 12
  • 15
  • this works perfectly and i don't need to have the form displayed like @Gusman trick. This will be very usefull as i have a couple of these window to preload to be ready when needed. – Franck Feb 06 '17 at 16:21