Search code examples
c#formsclickoncebackgroundworkertopmost

Windows.Form topmost called from a workerthread doesnt work when ClickOnce is enabled and published


I have a main thread that is a form, that starts another application, in this case Notepad, then I spawn off a BackgroundWorker that waits for Notepad to be closed. When its closed, the BackgroundWorker shows another Form to display, topmost, to the user. This Form needs to be non-modal, so that the user can click on some buttons on the main thread dialog. The problem is this form (Form2, from the BackgroundWorker) is NOT TopMost, even though I set it to true. It works when I hit F5, but when I publish, as a ClickOnce application, to my server, form2 is no longer TopMost. I have tired Form2.Topmost = true, BringToFront, Activate, "MakeTopMost" from What is powerful way to force a form to bring front? .... nothing seems to work.

I even tried to get the handle of the main form, and use that as the parent of form2, but I'm getting "InvalidOperationException: Cross-thread operation not valid: Control 'Form2' accessed from a thread other than the thread it was created on."

Here is a code snippet:

public partial class Form1 : Form
{
    System.Diagnostics.Process p = new System.Diagnostics.Process();
    private BackgroundWorker endApplicationBackgroundWorker= new BackgroundWorker();

    public Form1(string[] args)
    {
        endApplicationBackgroundWorker.DoWork += new DoWorkEventHandler(endApplicationBackgroundWorker_DoWork);

        p.StartInfo.FileName = "notepad";
        p.Start();

        endApplicationBackgroundWorker.RunWorkerAsync();

        //Quit here so we can accept user inputs (button pushes ..)
    }

    private void endApplicationBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        p.WaitForExit();

        Form2 form2 = new Form2();
        form2.TopMost = true;

        System.Diagnostics.Process[] procs = System.Diagnostics.Process.GetProcessesByName(form1ProcessName);
        if (procs.Length != 0)
        {
            IntPtr hwnd = procs[0].MainWindowHandle;
            if (form2.ShowDialog(new WindowWrapper(hwnd)) == DialogResult.OK)
            {
                // process stuff
            }
        }

        this.Close();
    }
}

Any other ideas? Or can someone fix my code above? I have been dealing with this issue for weeks now and getting flustered.

Thanks!


Solution

  • Any work you do in a BackgroundWorker's DoWork method after calling the RunWorkerAsync procedure is NOT running on the UI thread, but your code is creating a form in the background.

    Forms are UI elements, so this won't work:

    private void endApplicationBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
      Form2 form2 = new Form2();
      form2.TopMost = true;
      // etc..
      if (form2.ShowDialog(new WindowWrapper(hwnd)) == DialogResult.OK)
      {
        // process stuff
      }
    }
    

    From the comments, you should subscribe to the RunWorkerCompleted event to show your second form. Also, you can't call the Close method either since you are trying to keep Form2 alive without a ShowDialog call, so try subscribing to the Form_Closing() event of the second form to notify when the main form should be closed, too:

    public Form1(string[] args)
    {
      endApplicationBackgroundWorker.DoWork += 
        new DoWorkEventHandler(endApplicationBackgroundWorker_DoWork);
      endApplicationBackgroundWorker.RunWorkerCompleted += 
        new RunWorkerCompletedEventHandler(endApplicationBackgroundWorker_RunWorkerCompleted);
    
      p.StartInfo.FileName = "notepad";
      endApplicationBackgroundWorker.RunWorkerAsync();
    }
    
    private void endApplicationBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
      p.Start();
      p.WaitForExit();
    }
    
    private void endApplicationBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
      Form2 form2 = new Form2();
      form2.TopMost = true;
      form2.FormClosing += new FormClosingEventHandler(form2_FormClosing);    
      form2.Show(this);
    }
    
    private void form2_FormClosing(object sender, FormClosingEventArgs e)
    {
      this.BeginInvoke(new MethodInvoker(delegate { this.Close(); }));
    }