views:

976

answers:

4

I have what I believe to be a fairly well structured .NET 3.5 forms application (Unit Tests, Dependency Injection, SoC, the forms simply relay input and display output and don't do any logic, yadda yadda) I am just missing the winforms knowledge for how to get this bit to work.

When a connection to the database is lost - a frequent occurrence - I am detecting and handling it and would like a modal form to pop up, blocking use of the application until the connection is re-established. I am not 100% sure how to do that since I am not waiting for user input, rather I am polling the database using a timer.

My attempt was to design a form with a label on it and to do this:

partial class MySustainedDialog : Form {
 public MySustainedDialog(string msg) {
  InitializeComponent();
  lbMessage.Text = msg;
 }
 public new void Show() {
  base.ShowDialog();
 }

 public new void Hide() {
  this.Close();
 }

}

public class MyNoConnectionDialog : INoConnectionDialog {
            private FakeSustainedDialog _dialog;
 public void Show() {
  var w = new BackgroundWorker();
  w.DoWork += delegate {
   _dialog = new MySustainedDialog("Connection Lost");
   _dialog.Show();
  };
  w.RunWorkerAsync();
 }

 public void Hide() {
  _dialog.Close();
 }
}

This doesn't work since _dialog.Close() is a cross-thread call. I've been able to find information on how to resolve this issue within a windows form but not in a situation like this one where you need to create the form itself.

Can someone give me some advice how to achieve what I am trying to do?

EDIT: Please note, I only tried Background worker for lack of other ideas because I'm not tremendously familiar with how threading for the UI works so I am completely open to suggestions. I should also note that I do not want to close the form they are working on currently, I just want this to appear on top of it. Like an OK/Cancel dialog box but which I can open and close programmatically (and I need control over what it looks like to )

A: 

Might it be simpler to keep all the UI work on the main UI thread rather than using the BackgroundWorker? It's tricky to say without seeing more of your code, but I don't think you should need that.

When you create your timer, you can assign it's Timer.SynchronizingObject to get it to use the main UI thread. And stick with that?

Sorry, can't give a better answer without knowning more about the structure of your program.

Scott Langham
Ok, but I do not want to have to close the current form, I just want the no connection form on top of it. How do I do that?
George Mauer
NoDatabaseForm form = new NoDatabaseForm();form.ShowDialog();That should show it in front of the active form.
Scott Langham
Wherever you implement the timer handler, you'll have to be able to access the form, so you can close it again when the database is back. Maybe all your dependency injection etc is confusing stuff. How about trying to write the smallest app that does this anyway it can, and then put that in your app
Scott Langham
A: 

There is no reason to use a background worker to actually launch the new instance of your form, you can simply do it from the UI thread.

Mitchel Sellers
I am sure, how would I do this though?
George Mauer
Just simply remove the call to the background worker, then just show the form in the code rather than using a delegate.The process to try and restore the connection is what would be spawned to another process.
Mitchel Sellers
+2  A: 

I'm not sure about the correctness of your overall approach, but to specifically answer your question try changing the MySustainedDialog Hide() function to as follows:

    public new void Hide()
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke((MethodInvoker)delegate { this.Hide(); });
            return;
        }

        this.Close();
    }
RickL
Thats the same as recommended when you search for that error, however, notice that MyNoConnectionDialog does not inherit from form and therefore does not have any Invoke or BeginInvoke. How would you do this?
George Mauer
Change the one inside MySustainedDialog, not the one inside MyNoConnectionDialog.You don't need to change the one inside MyNoConnectionDialog because it doesn't directly access any GUI controls.
RickL
A: 

There are two approaches I've taken in similar situations.

One is to operate in the main UI thread completely. You can do this by using a Windows.Forms.Timer instance, which will fire in the main UI thread.

Upside is the simplicity and complete access to all UI components. Downside is that any blocking calls will have a huge impact on user experience, preventing any user interaction whatsoever. So if you need long-running commands that eventually result in a UI action (for example if checking for the database took, say, several seconds), then you need to go cross-thread.

The simplest cross-thread solution from a code perspective is to call the Control.Invoke method from your BackgroundWorker.

Invoke lets you "post" work to a control, essentially saying "plz go use your owning thread to run this."

rice