views:

1406

answers:

5

I don't know why this is happening, but when I create a new form inside an EventHandler, it disappears as soon as the method is finished.

Here's my code. I've edited it for clarity, but logically, it is exactly the same.

static void Main()
{
    myEventHandler = new EventHandler(launchForm);
    // Code that creates a thread which calls
    // someThreadedFunction() when finished.
}

private void someThreadedFunction()
{
    //Do stuff

    //Launch eventhandler
    EventHandler handler = myEventHandler;
    if (handler != null)
    {
        handler(null, null);
        myEventHandler = null;
    }
}

private void launchForm(object sender, EventArgs e)
{
    mf = new myForm();
    mf.Show();
    MessageBox.Show("Do you see the form?");
}

private myForm mf;
private EventHandler myEventHandler;

The new form displays as long as the MessageBox "Do you see the form?" is there. As soon as I click OK on it, the form disappears.

What am I missing? I thought that by assigning the new form to a class variable, it would stay alive after the method finished. Apparently, this is not the case.

A: 

Is

dbf.Show();

a typo? Is it supposed to be this instead?

mf.Show();

Is it possible that there is another form that you are showing other than the one you intend to show?

Andrew Hare
Yeah - it was a typo. Sorry.
Andrew
+1  A: 

I think if you create a form on a thread, the form is owned by that thread. When creating any UI elements, it should always be done on the main (UI) thread.

TheSean
+4  A: 

I believe the problem is that you are executing the code within the handler from your custom thread, and not the UI thread, which is required because it operates the Windows message pump. You want to use the Invoke method here to insure that the form gets and shown on the UI thread.

private void launchForm(object sender, EventArgs e)
{
    formThatAlreadyExists.Invoke(new MethodInvoker(() =>
    {
        mf = new myForm();
        mf.Show();
        MessageBox.Show("Do you see the form?");
    }));
}

Note that this assumes you already have a WinForms object (called formThatAlreadyExists) that you have run using Application.Run. Also, there may be a better place to put the Invoke call in your code, but this is at least an example of it can be used.

Noldorin
I tried what you suggested. I get a compiler error however:Cannot convert lambda expression to type 'System.Delegate' because it is not a delegate type. And it highlights the second open parentheses after Invoke.
Andrew
@Andrew: Sorry, should have tested. You need to wrap the lambda expression in a MethodInvoker type, since the argument type is System.Delegate. Should work fine now.
Noldorin
Well, that compiled, but now I get this: System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created. at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method,Object[] args) at launchForm(Object sender, EventArgs e)
Andrew
I tried doing: this.Invoke. Maybe that's the problem. All of this code is running from another Windows Form, so I thought this.Invoke should work. I guess I need to create a handle for the initial form, and use that, but I don't know how.
Andrew
@Andrew: I wouldn't think so. Just make sure that the form has been created and loaded before you launch this thread. (i.e. Start it from somewhere like Form_Load rather than the constructor, if that's the case.) The InvalidOperationException error should then disappear.
Noldorin
Actually, the form has long since been created when this code is called. The threaded code is only called when a menu item is chosen.
Andrew
Does "this" refer to this form that has been definitely already been created, or something else?
Noldorin
Well, that's what I'm wondering. "this" should refer to the form that has definitely been created, but in the context it's being called from, it sounds like it might be referring to the new thread.
Andrew
"this" simply refers to the parent class. If you have verified that it does indeed refer to the loaded form, then it's definitely a problem with the point at which you start the thread, I would think.
Noldorin
I really appreciate your help. It's still not working. I think the problem is that the new threaded is created inside of another class. I create an object of that class and it creates it's own thread which calls these event handlers. How can I give the launchForm method a handle to the initial form?
Andrew
Nevermind... I figured it out. My program launches minimized and hidden with just the system tray icon visible. Once I open the initial form window once, I don't get the exception anymore. I'm going to look into creating the window handle before it's visible, but at least now I know that the code does work. Thank you for your help.
Andrew
@Andrew: Glad you've found some sort of solution. :)
Noldorin
+1  A: 

this looks as if you are not on the form sta thread so once you show the form it is gone and the thread finishes it's job it kills it self since there is nothing referenceing the thread. Its not the best solution out there for this but you ca use a showdialog() rather than a show to accomplish it keeping state if you need a code example i use this exact same process for a "loading...." form

public class Loading
{
    public delegate void EmptyDelegate();
    private frmLoadingForm _frmLoadingForm;
    private readonly Thread _newthread;
    public Loading()
    {
        Console.WriteLine("enteredFrmLoading on thread: " + Thread.CurrentThread.ManagedThreadId);
        _newthread = new Thread(new ThreadStart(Load));
        _newthread.SetApartmentState(ApartmentState.STA);
        _newthread.Start();
    }

    public void Load()
    {
        Console.WriteLine("enteredFrmLoading.Load on thread: " + Thread.CurrentThread.ManagedThreadId);
        _frmLoadingForm = new frmLoadingForm();
        if(_frmLoadingForm.ShowDialog()==DialogResult.OK)
        {

        }
    }


    /// <summary>
    /// Closes this instance.
    /// </summary>
    public void Close()
    {
        Console.WriteLine("enteredFrmLoading.Close on thread: " + Thread.CurrentThread.ManagedThreadId);
        if (_frmLoadingForm != null)
        {
            if (_frmLoadingForm.InvokeRequired)
            {
                _frmLoadingForm.Invoke(new EmptyDelegate(_frmLoadingForm.Close));
            }
            else
            {
                _frmLoadingForm.Close();
            }
        }
        _newthread.Abort();
    }
}
public partial class frmLoadingForm : Form
{

    public frmLoadingForm()
    {
        InitializeComponent();
    }
}
Brandon Grossutti
cleaned it up a bit, as i said although I dont recomend what your doing I can understand why you're doing it otherwise I wouldnt have this code handy, this will accomplish what you wantThe frmLoadingForm is in your case myForm, i made this other class to wrap my calls to try and act like this is cleaner code :)
Brandon Grossutti
the console writes give u an idea of how invoke works as well and when it is used so for future threading operations you night try you can see why its failing with your current code
Brandon Grossutti
A: 

You created a window on a non UI thread. When the thread aborts it will take your window along with it. End of story. Perform invoke on the main form passing a delegate which will execute the method that creates the messagebox on the UI thread. Since the MessageBox is a modal window, if dont want the launchForm method to block the background thread, create a custom form with the required UI and call show() on it, not ShowDialog().

Bobby Alexander