Modal Dialog help in C# [modified]
-
I have found a condition where my dialog does not stay modal. I'm using the following snipit of code to create my modal dialog... public void ShowMyDialogBox() { MessageForm testDialog = new MessageForm(); // Show testDialog as a modal dialog and determine if DialogResult = OK. if (testDialog.ShowDialog(this) != DialogResult.OK) { Console.WriteLine("ERROR Broke Modal DialogBox\n"); } testDialog.Dispose(); } This method is called when my serial communications callback function gets called. When the message dialog is displayed, I can still click on the parent form to move it around, or perform button click actions. The only thing I can think of at this point is it might be a threading issue, where the callback is running in a different thread than the main thread. Can anyone help me? McSmack -- modified at 20:59 Wednesday 28th June, 2006
-
I have found a condition where my dialog does not stay modal. I'm using the following snipit of code to create my modal dialog... public void ShowMyDialogBox() { MessageForm testDialog = new MessageForm(); // Show testDialog as a modal dialog and determine if DialogResult = OK. if (testDialog.ShowDialog(this) != DialogResult.OK) { Console.WriteLine("ERROR Broke Modal DialogBox\n"); } testDialog.Dispose(); } This method is called when my serial communications callback function gets called. When the message dialog is displayed, I can still click on the parent form to move it around, or perform button click actions. The only thing I can think of at this point is it might be a threading issue, where the callback is running in a different thread than the main thread. Can anyone help me? McSmack -- modified at 20:59 Wednesday 28th June, 2006
McSmack, I would agree with your assessment, that it is a threading issue where the callback is running in a different thread than the main thread. This would cause the two dialogs to act as you mention. Here is a way to test that to see if it is the case, and also a way to fix it if it is. First, to test if they are running on different threads 1) Add the following line of code to the constructor of your main parent form:
System.Threading.Thread.CurrentThread.Name = "Main Thread";
This will give a name to the main thread so that we can identify it and distinguish it from another thread. 2) Now, inside the dialog which you want to show (the
MessageForm
dialog), add this line to your functionShowMyDialogBox
:System.Diagnostics.Trace.WriteLine("Thread is named: " + System.Threading.Thread.CurrentThread.Name);
This will write a line to the trace output which gives you the name of the thread which is calling
ShowDialog
on your second form. Now, look at the trace output (one way is to run the program in debug mode and then look at the output in Visual Studio) and see if you see "Thread is named: Main Thread" when yourShowMyDialogBox
function is called. If it is, then the dialog is being displayed on the main thread after all and something else is wrong. If it is not the same thread, then you will see something like "Thread is name: " - just blank, because the thread is unnamed. Second, marshaling back to the main thread If the issue is indeed that the second dialog is displayed on a different thread, here is the way you can fix yourShowMyDialogBox
function so that you can marshal back to the main thread:public void ShowMyDialogBox()
{
if(this.InvokeRequired)
{
this.BeginInvoke(new MethodInvoker(this.ShowMyDialogBox));
return;
}// Now we must be on the main thread - so the rest of the function is the same ...
}
Basically what this is doing is checking (via
InvokeRequired
) whether the thread which is executing theShowMyDialogBox
function is the same thread which created the main form. If it is not, thenInvokeRequired
will be true, and the functionBeginInvoke
will post a message which the main thread will receive. TheBeginInvoke
will cause the main thread to execute the code contained in the methodShowMyDialogBox
. Thus, when the main t -
McSmack, I would agree with your assessment, that it is a threading issue where the callback is running in a different thread than the main thread. This would cause the two dialogs to act as you mention. Here is a way to test that to see if it is the case, and also a way to fix it if it is. First, to test if they are running on different threads 1) Add the following line of code to the constructor of your main parent form:
System.Threading.Thread.CurrentThread.Name = "Main Thread";
This will give a name to the main thread so that we can identify it and distinguish it from another thread. 2) Now, inside the dialog which you want to show (the
MessageForm
dialog), add this line to your functionShowMyDialogBox
:System.Diagnostics.Trace.WriteLine("Thread is named: " + System.Threading.Thread.CurrentThread.Name);
This will write a line to the trace output which gives you the name of the thread which is calling
ShowDialog
on your second form. Now, look at the trace output (one way is to run the program in debug mode and then look at the output in Visual Studio) and see if you see "Thread is named: Main Thread" when yourShowMyDialogBox
function is called. If it is, then the dialog is being displayed on the main thread after all and something else is wrong. If it is not the same thread, then you will see something like "Thread is name: " - just blank, because the thread is unnamed. Second, marshaling back to the main thread If the issue is indeed that the second dialog is displayed on a different thread, here is the way you can fix yourShowMyDialogBox
function so that you can marshal back to the main thread:public void ShowMyDialogBox()
{
if(this.InvokeRequired)
{
this.BeginInvoke(new MethodInvoker(this.ShowMyDialogBox));
return;
}// Now we must be on the main thread - so the rest of the function is the same ...
}
Basically what this is doing is checking (via
InvokeRequired
) whether the thread which is executing theShowMyDialogBox
function is the same thread which created the main form. If it is not, thenInvokeRequired
will be true, and the functionBeginInvoke
will post a message which the main thread will receive. TheBeginInvoke
will cause the main thread to execute the code contained in the methodShowMyDialogBox
. Thus, when the main t -
Hello Alexander, Is it also possible to Invoke a Method with Parameters? ShowMyDialogBox(bool btest, ....) I had some problems compiling this. Just for Info. Thanks for your time, Martin -- modified at 1:35 Thursday 29th June, 2006
Martin, You won't be able to invoke a method with parameters if you are using the
MethodInvoker
delegate. Here is what MSDN says aboutMethodInvoker
: "MethodInvoker provides a simple delegate that is used to invoke a method with a void parameter list. This delegate can be used when making calls to a control's invoke method, or when you need a simple delegate but don't want to define one yourself." SinceMethodInvoker
has a void parameter list, and since the function you specify must have the same parameters as the delegate in question, you won't be able to invoke a method with parameters using it. In order to do this, you need to define your own delegate, which has the correct argument list. Let's suppose that the function you want to invoke byBeginInvoke
has two parameters, abool
and aString
, and that it looks like this:public void ShowMyDialogBox(bool bTest, String message)
Now, you would define your delegate like this:
public delegate void MyDelegate(bool bTest, String message)
The name of the delegate is completely optional, as are the names of the parameters. We could have called the boolean parameter anything we want (e.g.
bBoolParam
) and we could have called the string parameter anything we want. Now we would invoke this as follows:BeginInvoke(new MyDelegate(ShowMyDialogBox), new object [] { varToBeParam1, varToBeParam2 });
So now, if we put that together, our
ShowMyDialogBox
function would look something like this (assuming the scenario which McSmack had laid out):public void ShowMyDialogBox(bool bTest, String message)
{
if(this.InvokeRequired)
{
this.BeginInvoke(new this.MyDelegate(this.ShowMyDialogBox), new object [] { bTest, message });
return;
}
// now we're on the main thread and can do processing as normal
// the parameters also have their proper values
...
}Hope that helps! Feel free to respond if you have any more questions about this, or if you do not understand something in the answer. Sincerely, Alexander Wiseman
-
McSmack, I would agree with your assessment, that it is a threading issue where the callback is running in a different thread than the main thread. This would cause the two dialogs to act as you mention. Here is a way to test that to see if it is the case, and also a way to fix it if it is. First, to test if they are running on different threads 1) Add the following line of code to the constructor of your main parent form:
System.Threading.Thread.CurrentThread.Name = "Main Thread";
This will give a name to the main thread so that we can identify it and distinguish it from another thread. 2) Now, inside the dialog which you want to show (the
MessageForm
dialog), add this line to your functionShowMyDialogBox
:System.Diagnostics.Trace.WriteLine("Thread is named: " + System.Threading.Thread.CurrentThread.Name);
This will write a line to the trace output which gives you the name of the thread which is calling
ShowDialog
on your second form. Now, look at the trace output (one way is to run the program in debug mode and then look at the output in Visual Studio) and see if you see "Thread is named: Main Thread" when yourShowMyDialogBox
function is called. If it is, then the dialog is being displayed on the main thread after all and something else is wrong. If it is not the same thread, then you will see something like "Thread is name: " - just blank, because the thread is unnamed. Second, marshaling back to the main thread If the issue is indeed that the second dialog is displayed on a different thread, here is the way you can fix yourShowMyDialogBox
function so that you can marshal back to the main thread:public void ShowMyDialogBox()
{
if(this.InvokeRequired)
{
this.BeginInvoke(new MethodInvoker(this.ShowMyDialogBox));
return;
}// Now we must be on the main thread - so the rest of the function is the same ...
}
Basically what this is doing is checking (via
InvokeRequired
) whether the thread which is executing theShowMyDialogBox
function is the same thread which created the main form. If it is not, thenInvokeRequired
will be true, and the functionBeginInvoke
will post a message which the main thread will receive. TheBeginInvoke
will cause the main thread to execute the code contained in the methodShowMyDialogBox
. Thus, when the main tThanks for the tremendous help Alexander! Indeed, my modal dialog was being run in another thread. So after applying your solution it worked, however, my old thread is still running and executing code as well as the new main thread which is executing the same code. I'm not a thread expert. Is there a way to terminate the old thread after the this.BeginInvoke() is called? Thanks again for the help. McSmack
-
Martin, You won't be able to invoke a method with parameters if you are using the
MethodInvoker
delegate. Here is what MSDN says aboutMethodInvoker
: "MethodInvoker provides a simple delegate that is used to invoke a method with a void parameter list. This delegate can be used when making calls to a control's invoke method, or when you need a simple delegate but don't want to define one yourself." SinceMethodInvoker
has a void parameter list, and since the function you specify must have the same parameters as the delegate in question, you won't be able to invoke a method with parameters using it. In order to do this, you need to define your own delegate, which has the correct argument list. Let's suppose that the function you want to invoke byBeginInvoke
has two parameters, abool
and aString
, and that it looks like this:public void ShowMyDialogBox(bool bTest, String message)
Now, you would define your delegate like this:
public delegate void MyDelegate(bool bTest, String message)
The name of the delegate is completely optional, as are the names of the parameters. We could have called the boolean parameter anything we want (e.g.
bBoolParam
) and we could have called the string parameter anything we want. Now we would invoke this as follows:BeginInvoke(new MyDelegate(ShowMyDialogBox), new object [] { varToBeParam1, varToBeParam2 });
So now, if we put that together, our
ShowMyDialogBox
function would look something like this (assuming the scenario which McSmack had laid out):public void ShowMyDialogBox(bool bTest, String message)
{
if(this.InvokeRequired)
{
this.BeginInvoke(new this.MyDelegate(this.ShowMyDialogBox), new object [] { bTest, message });
return;
}
// now we're on the main thread and can do processing as normal
// the parameters also have their proper values
...
}Hope that helps! Feel free to respond if you have any more questions about this, or if you do not understand something in the answer. Sincerely, Alexander Wiseman
Hello Alexander, Thanks for your help. I think I did everything correct so far. Here is my code: private void XXXChanged(object sender, XXXEventArgs e) { reaAct = e; MethodInvoker mi = new MethodInvoker(YYYChanged); //Here is the error point (missing the Argument) Invoke(mi); } private delegate void ActionDelegate(XXXEventArgs e); private void YYYChanged(XXXEventArgs e) { if (InvokeRequired) { // We're not in the UI thread, so we need to call BeginInvoke BeginInvoke(new ActionDelegate(YYYChanged), new object[]{e}); return; } ... } I think I should use the other Constructor of the MethodInvoker, but I don't know how. :(( Thank's for your time and help. Martin -- modified at 12:12 Thursday 29th June, 2006
-
Hello Alexander, Thanks for your help. I think I did everything correct so far. Here is my code: private void XXXChanged(object sender, XXXEventArgs e) { reaAct = e; MethodInvoker mi = new MethodInvoker(YYYChanged); //Here is the error point (missing the Argument) Invoke(mi); } private delegate void ActionDelegate(XXXEventArgs e); private void YYYChanged(XXXEventArgs e) { if (InvokeRequired) { // We're not in the UI thread, so we need to call BeginInvoke BeginInvoke(new ActionDelegate(YYYChanged), new object[]{e}); return; } ... } I think I should use the other Constructor of the MethodInvoker, but I don't know how. :(( Thank's for your time and help. Martin -- modified at 12:12 Thursday 29th June, 2006
Martin, If you want to pass arguments to the method that you invoke, you cannot use
MethodInvoker
. The other constructor ofMethodInvoker
won't help you. The function which you specify with the delegate has to have the same signature as the delegate, and because the MethodInvoker delegate has a void parameter list, the method you invoke with it must also have a void parameter list. I'm not exactly clear about what yourXXXChanged
function is supposed to be doing. However, if you want to invoke theYYYChanged
function from it and change back to the main thread, then you can do exactly the same thing you did in theYYYChanged
function, like this:private void XXXChanged(object sender, XXEventArgs e)
{
if(InvokeRequired)
{
BeginInvoke(new ActionDelegate(YYYChanged), new object [] { e } );
return;
}
...
}If you do not need to change threads and you want to call the function
YYYChanged
directly fromXXXChanged
then I see no reason to use theInvoke
function. Let me know if that helps you out. If not, could you be a little more specific with what you want theXXXChanged
function to do? Sincerely, Alexander Wiseman -
Martin, If you want to pass arguments to the method that you invoke, you cannot use
MethodInvoker
. The other constructor ofMethodInvoker
won't help you. The function which you specify with the delegate has to have the same signature as the delegate, and because the MethodInvoker delegate has a void parameter list, the method you invoke with it must also have a void parameter list. I'm not exactly clear about what yourXXXChanged
function is supposed to be doing. However, if you want to invoke theYYYChanged
function from it and change back to the main thread, then you can do exactly the same thing you did in theYYYChanged
function, like this:private void XXXChanged(object sender, XXEventArgs e)
{
if(InvokeRequired)
{
BeginInvoke(new ActionDelegate(YYYChanged), new object [] { e } );
return;
}
...
}If you do not need to change threads and you want to call the function
YYYChanged
directly fromXXXChanged
then I see no reason to use theInvoke
function. Let me know if that helps you out. If not, could you be a little more specific with what you want theXXXChanged
function to do? Sincerely, Alexander Wiseman -
Thanks for the tremendous help Alexander! Indeed, my modal dialog was being run in another thread. So after applying your solution it worked, however, my old thread is still running and executing code as well as the new main thread which is executing the same code. I'm not a thread expert. Is there a way to terminate the old thread after the this.BeginInvoke() is called? Thanks again for the help. McSmack
There is a way to terminate a thread, but it is not generally the best way to deal with a thread which needs to be stopped. The best way is to simply exit out of the function which the thread is processing, which should stop the thread. This may be different, however, depending on how your second thread was created. So, how is your second thread created? From your first post, it sounds like you are not explicitly creating the thread (with code like
Thread myThread = new Thread(new ThreadStart(SomeFunctionToProcess));
). It sounds rather like a thread is being created by a component you are using and that thread executes the callback code, part of which is calling theShowMyDialogBox
function. If you do not know how the second thread is being created, try this: inside the callback function (the function which calls theShowMyDialogBox
function) make sure that the function ends after the call toShowMyDialogBox
. Also make sure that after the call toBeginInvoke
in theShowMyDialogBox
function you have thereturn
keyword - this will force the secondary thread to quit theShowMyDialogBox
after it tells the main thread to process the function with theBeginInvoke
function. Final question: what is the method signature of your callback function? Specifically, what are the parameters? If you can get me the answers to these few questions, I should be able to help you out. Sincerely, Alexander Wiseman -
There is a way to terminate a thread, but it is not generally the best way to deal with a thread which needs to be stopped. The best way is to simply exit out of the function which the thread is processing, which should stop the thread. This may be different, however, depending on how your second thread was created. So, how is your second thread created? From your first post, it sounds like you are not explicitly creating the thread (with code like
Thread myThread = new Thread(new ThreadStart(SomeFunctionToProcess));
). It sounds rather like a thread is being created by a component you are using and that thread executes the callback code, part of which is calling theShowMyDialogBox
function. If you do not know how the second thread is being created, try this: inside the callback function (the function which calls theShowMyDialogBox
function) make sure that the function ends after the call toShowMyDialogBox
. Also make sure that after the call toBeginInvoke
in theShowMyDialogBox
function you have thereturn
keyword - this will force the secondary thread to quit theShowMyDialogBox
after it tells the main thread to process the function with theBeginInvoke
function. Final question: what is the method signature of your callback function? Specifically, what are the parameters? If you can get me the answers to these few questions, I should be able to help you out. Sincerely, Alexander WisemanThanks in advance for all your help. One of the programmers here, created the serial DLL I am using. In that library he has a notify delegate which I subscribe to. His delegate has a couple of parameters which allows me to retrieve the data. Because I deal with a few different communication entities, I created a class which encapsulates the serial DLL, as well as some other specific comm stuff. In my class, I have // Callback delegate that subscribers must implement
public delegate void DataReceivedHandler();
public event DataReceivedHandler OnDataReceived;
where OnDataReceived gets called inside my subscribed function with the serial library. Sorry for the longwinded description. So the answer to one of your questions is that once the library receives data, it creates a "notify" thread and calls my subscribed function which is in my comm class. The code is below private void SerialConnection_PacketReceived(object sender, ReceivedPacket pkt)
{
// If anyone is subscribed to callback, notify them
if( OnDataReceived != null )
{
m_receivedPacket = pkt;
m_dataSizeReadIn = (int)m_receivedPacket.packet.Length;
Buffer.BlockCopy( m_receivedPacket.packet, 0, m_buffer, 0, m_dataSizeReadIn );
// Call Subscriber
OnDataReceived();
}
}
The serial class (which is also a form) is a member of my main form. The ShowDialogBox() resides in my Main Form. But I could easily move it to serial class. What do you think? Regards, McSmack -
Thanks in advance for all your help. One of the programmers here, created the serial DLL I am using. In that library he has a notify delegate which I subscribe to. His delegate has a couple of parameters which allows me to retrieve the data. Because I deal with a few different communication entities, I created a class which encapsulates the serial DLL, as well as some other specific comm stuff. In my class, I have // Callback delegate that subscribers must implement
public delegate void DataReceivedHandler();
public event DataReceivedHandler OnDataReceived;
where OnDataReceived gets called inside my subscribed function with the serial library. Sorry for the longwinded description. So the answer to one of your questions is that once the library receives data, it creates a "notify" thread and calls my subscribed function which is in my comm class. The code is below private void SerialConnection_PacketReceived(object sender, ReceivedPacket pkt)
{
// If anyone is subscribed to callback, notify them
if( OnDataReceived != null )
{
m_receivedPacket = pkt;
m_dataSizeReadIn = (int)m_receivedPacket.packet.Length;
Buffer.BlockCopy( m_receivedPacket.packet, 0, m_buffer, 0, m_dataSizeReadIn );
// Call Subscriber
OnDataReceived();
}
}
The serial class (which is also a form) is a member of my main form. The ShowDialogBox() resides in my Main Form. But I could easily move it to serial class. What do you think? Regards, McSmackMcSmack, Sorry for the late response. I'm not sure I have enough information to accurately answer your question, but let me ask you two things to clarify: 1) What symptoms cause you to think that the secondary thread is still running? 2) How is the thread created in the serial library? If you don't know, could you point me to the place on this site that has the serial library so I can check the code? I think what may be happening is that your secondary thread is ending, but then more data is sent to the serial port, so your callback function is getting called again and probably causing another dialog box to be displayed. If this is the case, then inside your
ShowMyDialogBox
function (but after you marshal back to the main thread (i.e. after theInvokeRequired
statement) you should check if a dialog box has already been created, and if it has, then do nothing. The best way to do this is to make your secondary dialog variable a member variable of the main form and simply check if the dialog is already created. If it is, don't open another one. Let me know if that helps point you in the right direction. We should be able to work this out! Sincerely, Alexander Wiseman -
McSmack, Sorry for the late response. I'm not sure I have enough information to accurately answer your question, but let me ask you two things to clarify: 1) What symptoms cause you to think that the secondary thread is still running? 2) How is the thread created in the serial library? If you don't know, could you point me to the place on this site that has the serial library so I can check the code? I think what may be happening is that your secondary thread is ending, but then more data is sent to the serial port, so your callback function is getting called again and probably causing another dialog box to be displayed. If this is the case, then inside your
ShowMyDialogBox
function (but after you marshal back to the main thread (i.e. after theInvokeRequired
statement) you should check if a dialog box has already been created, and if it has, then do nothing. The best way to do this is to make your secondary dialog variable a member variable of the main form and simply check if the dialog is already created. If it is, don't open another one. Let me know if that helps point you in the right direction. We should be able to work this out! Sincerely, Alexander WisemanHey Alexander, Hopefully I can answer these questions to the fullest 1) What symptoms cause you to think that the secondary thread is still running? I'm most likely wrong, but it seems to be still running. My main form has a TextBox window that gives me updates of where the data control is at, and my ShowMyDialogBox() displays a transparent modal dialog over the main form. When I place the BeginInvoke(new MethodInvoker( this.ShowMyDialogBox)) line in my ShowMyDialogBox() method as you suggested, the dialog comes up and it is modal, but I can still see that the code is still running, displaying me updates in the main form's TextBox. 2) How is the thread created in the serial library? If you don't know, could you point me to the place on this site that has the serial library so I can check the code? Here are the lines in the library that create the thread.. rcvNotifyThread = new Thread(new ThreadStart(RunRcvNotifyThread)); rcvNotifyThread.Priority = ThreadPriority.Normal; rcvNotifyThread.Name = "Notify " + portName; rcvNotifyThread.Start(); // Start the new thread =========================================================== When you said that the MethodInvoker is just a delegate, it got me thinking. So I played around with it and found a good place to bring the secondary thread back to the main thread. Upon entry of my callback function, I decide to place your solution here since the method that runs all my data processing matches the MethodInvoker delegate. See below public void ReceiveDataFromSerial(); //Just here to show you the prototype // the method that implements the delegated funtionality public void ProcessIncomingData( ) { string testString = "Thread is named: " + System.Threading.Thread.CurrentThread.Name; System.Diagnostics.Trace.WriteLine(testString); Console.WriteLine(testString); //If not in the main thread, move there and let old thread end if(this.InvokeRequired) { this.BeginInvoke(new MethodInvoker(this.ReceiveDataFromSerial)); return; } // Process Data ReceiveDataFromSerial(); // This function process all } This seems to do the trick. I will keep testing it to make sure everything is sound. I truly appreciate all your help Alexander. Regards, McSmack
-
Hey Alexander, Hopefully I can answer these questions to the fullest 1) What symptoms cause you to think that the secondary thread is still running? I'm most likely wrong, but it seems to be still running. My main form has a TextBox window that gives me updates of where the data control is at, and my ShowMyDialogBox() displays a transparent modal dialog over the main form. When I place the BeginInvoke(new MethodInvoker( this.ShowMyDialogBox)) line in my ShowMyDialogBox() method as you suggested, the dialog comes up and it is modal, but I can still see that the code is still running, displaying me updates in the main form's TextBox. 2) How is the thread created in the serial library? If you don't know, could you point me to the place on this site that has the serial library so I can check the code? Here are the lines in the library that create the thread.. rcvNotifyThread = new Thread(new ThreadStart(RunRcvNotifyThread)); rcvNotifyThread.Priority = ThreadPriority.Normal; rcvNotifyThread.Name = "Notify " + portName; rcvNotifyThread.Start(); // Start the new thread =========================================================== When you said that the MethodInvoker is just a delegate, it got me thinking. So I played around with it and found a good place to bring the secondary thread back to the main thread. Upon entry of my callback function, I decide to place your solution here since the method that runs all my data processing matches the MethodInvoker delegate. See below public void ReceiveDataFromSerial(); //Just here to show you the prototype // the method that implements the delegated funtionality public void ProcessIncomingData( ) { string testString = "Thread is named: " + System.Threading.Thread.CurrentThread.Name; System.Diagnostics.Trace.WriteLine(testString); Console.WriteLine(testString); //If not in the main thread, move there and let old thread end if(this.InvokeRequired) { this.BeginInvoke(new MethodInvoker(this.ReceiveDataFromSerial)); return; } // Process Data ReceiveDataFromSerial(); // This function process all } This seems to do the trick. I will keep testing it to make sure everything is sound. I truly appreciate all your help Alexander. Regards, McSmack
Excellent! I'm glad you got it working. The purpose of my second question was to make sure that the serial library was explicitly spawning a new thread and not asynchronously invoking a delegate function. I see by the code you put there that it is indeed explicitly creating the thread, so you probably don't want to kill the thread. I think you solution is perfect, and I understand now why you had said that the thread was still running. Switching to the main thread before doing any of the data processing seems to be the best way to go. Let me know if you run into any more problems :cool: Sincerely, Alexander Wiseman