1

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!

Community
  • 1
  • 1
Tizz
  • 820
  • 1
  • 15
  • 31

2 Answers2

1

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(); }));
}
LarsTech
  • 80,625
  • 14
  • 153
  • 225
  • Apologies, you are correct. In my cutting out of non important code I put code in the wrong place. The WaitForExit is in the BackgroundWorker (I updated the code above). This is so the Background worker can wait, while the main thread can accept button clicks. I want the user to be able to click the same buttons on the main form while form2 is open. – Tizz Mar 27 '12 at 22:35
  • @Tizz It *seems* like all you need in your `DoWork` method is `p.Start` and `p.WaitForExit` and then subscribe to the `RunWorkerCompleted` event and move all of your `Form2` stuff there. It seems like at that point, nothing is happening in a background thread and is demanding the user's attention on the UI thread. – LarsTech Mar 27 '12 at 23:38
  • Isnt RunWorkerCompleted on the same BackgroundWorker thread? Wont I get the same results? – Tizz Mar 29 '12 at 01:04
  • @Tizz No, it fires on the main thread just like the `ProgressChanged` event does to update the progress bar. – LarsTech Mar 29 '12 at 01:07
  • Cool, ok, I see it working. So the idea of this was to have this dialog pop up (Form2) and the user would still be able to click on Form1 ... this wont allow that to happen. – Tizz Mar 29 '12 at 02:21
  • @Tizz `ShowDialog()` will prevent the user from interacting with the other forms. – LarsTech Mar 29 '12 at 12:25
  • I feel like we are kinda going in circles. I know that ShowDialog will prevent interacting with other forms, and Show will immediately continue with the thread. Changing to Show will Close Form1 too soon. I need user input into Form2 (Its basically a 'do you want to save' dialog) and Form1 has a button where you can open config files and edit them after you closed notepad. I want the user to be able to access the config files after they closed Notepad because I dont want to force them to have to config before they exit notepad (some dont realize, which is why i needed Form2). Does that help? – Tizz Mar 29 '12 at 15:45
  • @Tizz I updated the example again. You obviously can't call `ShowDialog` then (your code was, which confused me). You also can't call `this.Close()` from there either since you are waiting for the user to close form2 first. So just subscribe to the form2 closing event and close the main form when that happens. – LarsTech Mar 29 '12 at 17:01
0

Another workaround can be, if you set the topmost to false and then set it back to true. It is strange, but it works. So the code could be the following for a form to be show with descending oppacity in a simple background thread:

for (int i = 0; i < 50; i++)
{
     ppaForm.SetBitmap(bitmap, (byte)(255 - i * 5));
     ppaForm.Show();
     ppaForm.TopMost = false;
     ppaForm.TopMost = true;

     Thread.Sleep(6);
}

In this case ppaForm is a PerPixelAlphaForm, for which you can find a description here.

vik.barca
  • 41
  • 1
  • 4