views:

1045

answers:

5

Hi everyone.. I have an interesting problem.. I have a Form that launches another Form (2) through a Button. Before Form2 Closes, it sometimes fires an Event which Invalidates Form 1 and forces Form 1 to refresh it's data. The problem I have is After Form 2 fires the event, Form 1 seems to get it and handles it, and refreshes it's data and Only then Form 2 Closes. I want Form 2 to Fire the event and Close, BEFORE Form1's event handler catches and processes the event. I have a feeling it is related to BackgroundWorker (sort of like SwingUtilities.InvokeLater in Java) .. but I am not that experienced with it ..

public class Frm1{

void LaunchForm2(){
   Frm2 form2 = new Frm2();
   form2.dataChanged += new DataChangeListener(myListener);
   form2.showDialog();
}
private void myListener(){
  //get my data again
}

}

public class Frm2{

 private void Close(){
    if(myDataHasChanged){
      if(dataChanged != null) { 
        dataChanged(); 
      }
      this.Close();
    }
 }
}
A: 

Why don't you raise DataChanged Event in Closed event of form2?

Beatles1692
I thought that'd do it.. but it doesn't .. I raise the event in the Form_Closed event .. and still it raises it and Form1 handles it before Form2 goes fromt he screen..
IM
A: 

You can raise the event after calling Close - calling Form.Close will not exit the method that called it.

EDIT: Try using BeginInvoke to wait for the next message loop before handling the event, like this:

form2.dataChanged += delegate { BeginInvoke(new DataChangeListener(myListener)); };

2nd EDIT: To give Form1 a chance to repaint, call BeginInvoke twice to wait two message loops before updating (One to close Form2, and a second to repaint Form1), like this:

form2.dataChanged += delegate(parameters) { 
    BeginInvoke(new Action(delegate { 
        BeginInvoke(new DataChangeListener(myListener), parameters);
    }));
    //Or,
    BeginInvoke(new Func<Delegate, object[], IAsyncResult>(BeginInvoke),
                new object[] { parameters }
    );
};
SLaks
If I do that, I'm getting a TargetparameterException in Application.Run in my Main()
IM
That means that you need to add parameters. Add any parameters that `myListener` takes to the BeginInvoke call.
SLaks
No..that was targetParameterException all right..
IM
You wre right about the parameter .. i Added that and now the runtime exceptions gone.. and it's working as I want it except for one teeny thing.. there seems to be refresh issue with Form 2 .. it closes but it appears Form1 does not repaint over it immediately.. Only does so AFTER it has processed the event.
IM
@SLaks.. my bad.. it was TargetParameterCountException
IM
+2  A: 

Is there a specific reason why you're using events in this situation?

Expose a property on Form2 that allows you to check whether the data has changed. After the ShowDialog() call returns, check the value of the property and do your update if necessary.

(Edited to remove my now-useless code sample.)

Jon Seigel
How do I ensure that the changes in the 2nd form have been persisted ("Save" has been hit on Form2) before I check the public variable ? .. I deally I want to Invalidate the 1st Form after the 2nd Form has closed.. but only if changes were made on the 2nd form..
IM
ShowDialog will only return after Form2 closes. Therefore, any code that runs after it will run after Form2 is closed.
SLaks
My concern there was.. will the garbageCollector keep the Form 2 object around even after Form2 has been closed ? and for how long ? Besides, I also need an approach that works with both ShowDialog and Show
IM
As long as you have a reference to Form2 (The one you called ShowDialog on), the GC will keep it around. However, if you also want to call `Show`, this won't work.
SLaks
ShowDialog and Show are completely separate problems...
Jon Seigel
Thx for the help th bud.. I should've mentioned abt the Show() in the first post
IM
@Jon Siegel.. I agree.. absolutely.. don't even ask me why my boss is forcing me tosupport both.. doesnt make any sense ..
IM
A: 

Try changing the event call to:

public class Frm2{

 private void Close(){
    if(myDataHasChanged){
      if(dataChanged != null) { 
        dataChanged.BeginInvoke(); 
      }
      this.Close();
    }
 }
}

This won't guarantee that the form closes before myListener() completes. However, it should allow the form to close without waiting for myListener() to complete.

Ed Chapel
This will fire the event on the ThreadPool, not the UI thread, which is unlikely to be what he wants. Also, this leaks memory - when you call `BeginInvoke` on a delegate, you MUST call `EndInvoke`. http://blogs.msdn.com/cbrumme/archive/2003/05/06/51385.aspx
SLaks
+2  A: 

You can do this in OnHandleDestroyed as it it done after final destruction of the window handle. You can be certain that:

  1. No more processing will be done by Frm2
  2. The call will always be fired when the form closes

Thus do something like the following:

public class Frm2 : Form
{
 protected override void OnHandleDestroyed(EventArgs e)
 {
  base.OnHandleDestroyed(e);
  if (myDataHasChanged)
  {
   if (dataChanged != null)
    dataChanged();
  }
 }
 private void Close()
 {
  if (myDataHasChanged)
   this.Close();
 }
}

UPDATE:

Test to verify that HandleDisposed is called prior to returning from ShowDialog():

  bool called = false;

  Form test = new Form();
  test.Shown += delegate (Object o, EventArgs e) { test.Close(); };
  test.HandleDestroyed += delegate(Object o, EventArgs e) { called = true; };
  test.ShowDialog();

  Assert.IsTrue(called);
csharptest.net
Thanks buddy.. but I guess this ,ay nto work because of an inadvertent problem I have introduced.. I meant to do (new Form2()),ShowDialog() but instead since I am using an object of Form2 in Form1, I guess (SLaks comment above) the GC will keep the Form2 handle around until I manually set it to null.. So OnHandleDestroyed will not be fired until then and I will also not get the notification when I want it ..
IM
But I learnt something new.. yeahh for that !
IM
OnHandleDestroyed has nothing to do with Dispose() or GC cleanup. It is fired from the window proc handler on the form when the window receives the WM_DESTROY window message. Thus, Form.Close() will fire this event and should happen immediately prior to the return of ShowDialog().
csharptest.net
When you close form2 (after calling either show or showdialog) the window handle will be destroyed, that is when the OnHandleDestroyed method will be called, you don't have to explicitly null it.
Matt