Show dialog in response to message

  • Thread starter Thread starter WP
  • Start date Start date
W

WP

Disclaimer: I'm new at C# so please be gentle. :-)
I'm writing a simple card game where two players play against each
other through a middle hand they both connect to. In the client (the
player) I read messages from the middle hand in an asynchronous
fashion. I ran into a problem when I tried to implement the player
repsone to the play card message and I need your help.
When the player gets the play card message I want to display a simple
dialog box displaying the cards he or she has available for play.
However, during runtime I get an InvalidOperationException when I
attempt to show the dialog. The more precise error message is, and
here I translate to english: The action between threads is not valid.
The control collected/gathered from another thread than the one it was
created on. Is it because the control (the dialog) tries to use an
object, created by the "main" form, holding the players cards? How
should I solve this in a proper way?

Thanks for reading and thanks for any replies!

- Eric
 
[...]
However, during runtime I get an InvalidOperationException when I
attempt to show the dialog. The more precise error message is, and
here I translate to english: The action between threads is not valid.
[...]

The short answer: you need to use Control.Invoke() or
Control.BeginInvoke().

Do a Google search on those method names or on the English language
version of the text for the exception you're seeing.

Pete
 
[...]
However, during runtime I get an InvalidOperationException when I
attempt to show the dialog. The more precise error message is, and
here I translate to english: The action between threads is not valid.
[...]

The short answer: you need to use Control.Invoke() or
Control.BeginInvoke().

Do a Google search on those method names or on the English language
version of the text for the exception you're seeing.

Pete

Thanks for the quick reply, Pete. After creating dialog and seeing the
exception when calling ShowDialog(), I checked if InvokeRequired was
true and it was false so I didn't think that was the solution. I will
give it another try and post back. Thanks!

- Eric
 
Thanks for the quick reply, Pete. After creating dialog and seeing the
exception when calling ShowDialog(), I checked if InvokeRequired was
true and it was false so I didn't think that was the solution. I will
give it another try and post back. Thanks!

How did you check InvokeRequired? Did you use an existing control? Or
did you create a dialog form and then check the property on that form?

Basically, without a concise-but-complete code sample from you, it's
impossible to say exactly what you're doing wrong. But your original
description sounds exactly like the cross-thread exception that's thrown
when you try to access GUI components on the wrong thread, and the
solution for that is calling Invoke() or BeginInvoke().

Pete
 
How did you check InvokeRequired? Did you use an existing control? Or
did you create a dialog form and then check the property on that form?

I checked it on the dialog after creating it.
Basically, without a concise-but-complete code sample from you, it's
impossible to say exactly what you're doing wrong. But your original
description sounds exactly like the cross-thread exception that's thrown
when you try to access GUI components on the wrong thread, and the
solution for that is calling Invoke() or BeginInvoke().

Pete

Here's the part of the messager handler that responds to the play card
message. I must be
misunderstanding something about Invoke, because it didn't work.

try
{
PlayCardDialog dlg = new PlayCardDialog(this.my_cards,
placed_card);

Trace.WriteLine(dlg.InvokeRequired.ToString());

foo_delegate foo = new foo_delegate(dlg.ShowDialog);

this.tracebox.WriteLine(dlg.InvokeRequired.ToString()); // Prints
False

//if (dlg.ShowDialog(this) == DialogResult.OK)
if ((DialogResult)dlg.Invoke(foo, this) == DialogResult.OK)
{
this.tracebox.WriteLine("play card");
}
}
catch (Exception e)
{
this.tracebox.WriteLine(e.ToString());
}

This code is from the function that is called asynchronously when a
message arrives.
this.my_cards is created in the main form when the program is
launched. It's that's what's causing problems? I have no problem
modifying in response to other messages.

Thanks!
 
Here's the part of the messager handler that responds to the play card
message. I must be
misunderstanding something about Invoke, because it didn't work.

What line of code in that example throws the exception?

Basically, you're doing it wrong. The reason that InvokeRequired returns
"false" is that you're checking it on the same thread in which you create
the dialog itself. Since ShowDialog() will run its own message pump, you
don't have any immediate problems with respect to creating the dialog on a
thread other than your main GUI thread. So in theory, the "false" return
value is actually correct.

However, if in the thread for your dialog you attempt to do anything that
might interact with another of your GUI components, then the cross-thread
exception will be thrown. As an example, I don't know what "tracebox" is,
but if it's a GUI component, then if the WriteLine() method doesn't itself
deal with cross-thread issues, then that will throw an exception.

The thing that I believe is actually causing the problem is the call to
ShowDialog() itself. You are passing a reference to some other Form, it
looks like, as the owner for the dialog. And since the dialog is being
created on a thread other than that form's owning thread, the original
form is not a legitimate owner for the dialog you're creating.

Basically, you need to use Invoke() or BeginInvoke() to call the method in
which all that code you posted is contained, or (if that method contains
things unrelated to showing the dialog) a new method that has as its only
job that of showing the dialog. Invoke() would probably work, but IMHO
you should probably use BeginInvoke(), because you wrote that the code is
being executed in some sort of i/o handling code. Blocking your i/o in
order to show a message dialog is probably not a good design choice.

Pete
 
What line of code in that example throws the exception?

Basically, you're doing it wrong. The reason that InvokeRequired returns
"false" is that you're checking it on the same thread in which you create
the dialog itself. Since ShowDialog() will run its own message pump, you
don't have any immediate problems with respect to creating the dialog on a
thread other than your main GUI thread. So in theory, the "false" return
value is actually correct.

However, if in the thread for your dialog you attempt to do anything that
might interact with another of your GUI components, then the cross-thread
exception will be thrown. As an example, I don't know what "tracebox" is,
but if it's a GUI component, then if the WriteLine() method doesn't itself
deal with cross-thread issues, then that will throw an exception.

The thing that I believe is actually causing the problem is the call to
ShowDialog() itself. You are passing a reference to some other Form, it
looks like, as the owner for the dialog. And since the dialog is being
created on a thread other than that form's owning thread, the original
form is not a legitimate owner for the dialog you're creating.

Basically, you need to use Invoke() or BeginInvoke() to call the method in
which all that code you posted is contained, or (if that method contains
things unrelated to showing the dialog) a new method that has as its only
job that of showing the dialog. Invoke() would probably work, but IMHO
you should probably use BeginInvoke(), because you wrote that the code is
being executed in some sort of i/o handling code. Blocking your i/o in
order to show a message dialog is probably not a good design choice.

Pete

Thanks for your detailed explanation, Pete. You have been really
helpful and I appreicate it alot. I moved everything into a separate
function which I called using a delegate and BeginInvoke() and now it
seems to work just fine.

Now I just need to find a way to associate a combobox list entry with
an object reference, that was possible in raw Win32 programming as I
recall. :)

I will also look into if I can get exceptions to be displayed in
english...

- Eric
 
Thanks for your detailed explanation, Pete. You have been really
helpful and I appreicate it alot. I moved everything into a separate
function which I called using a delegate and BeginInvoke() and now it
seems to work just fine.

You're welcome...glad you could get it to work.
Now I just need to find a way to associate a combobox list entry with
an object reference, that was possible in raw Win32 programming as I
recall. :)

In .NET it's even easier than it is in unmanaged code.

You are probably currently adding strings to your ComboBox, but you can
add anything you want. You should just add whatever object it is you want
to the ComboBox. The control will display the object using the ToString()
method (which you should override so that it displays something
appropriate) or you can use the ComboBox.DisplayMember property to set
which property of the object's you're adding to the ComboBox it should use
(and it will then get the value of that property and use ToString() to get
the display text from the value).

Doing it that way, then all you need to do is cast the object reference
for any item in the ComboBox back to the class that you know it is, and
you're done.

Pete
 
You're welcome...glad you could get it to work.


In .NET it's even easier than it is in unmanaged code.

You are probably currently adding strings to your ComboBox, but you can
add anything you want. You should just add whatever object it is you want
to the ComboBox. The control will display the object using the ToString()
method (which you should override so that it displays something
appropriate) or you can use the ComboBox.DisplayMember property to set
which property of the object's you're adding to the ComboBox it should use
(and it will then get the value of that property and use ToString() to get
the display text from the value).

Doing it that way, then all you need to do is cast the object reference
for any item in the ComboBox back to the class that you know it is, and
you're done.

Pete

Yes, I was indeed adding strings, I didn't realise I could add just
about anything, very convenient. Thanks! I'm sure I'm under-using .Net
in a whole bunch of other places as well, heh.

Anyway, I noticed a new problem. The dialog I show in response to the
message appears on the taskbar, and I don't like that. I checked my
other dialog, which is invoked from a menu, and it also appears on the
taskbar. How do I make them not do that? I still want to require the
user to dismiss the dialog before being able to interface with the
main program again, I just don't want the dialogs on the taskbar.

I'm using the ShowDialog() overload which takes a parent argument (I
believe). I tried setting the parent property in the dialog
constructor but that threw an exception.

Hope you're still monitoring this thread. :)

- Eric
 
[...]
Yes, I was indeed adding strings, I didn't realise I could add just
about anything, very convenient. Thanks! I'm sure I'm under-using .Net
in a whole bunch of other places as well, heh.

Get used to it. I still have that problem. :)
Anyway, I noticed a new problem. The dialog I show in response to the
message appears on the taskbar, and I don't like that. [...]

Hope you're still monitoring this thread. :)

I did (obviously) see your message. But you should consider the
possibility that even though a question is related to the same task your
previous question was in reference to, it may be better to start a new
thread. There's a bit of a gray area, but IMHO it's better to err on the
side of starting a new thread, if your follow-up doesn't directly pertain
to or involve any information that was posted previously in the thread.

All that said, you will probably want to set the Form.ShowInTaskbar
property to "false". And as another general tip: it's always a good idea
to just scan through the members of a class. The .NET API is reasonably
well-designed (not perfect, but pretty good) and in a well-designed API,
you can often easily figure out how to do something simply by looking at
the interface exposed by the objects. I think if you'd seen that
property, you'd have immediately recognized it as the one you want. :)

Pete
 
[...]
Yes, I was indeed adding strings, I didn't realise I could add just
about anything, very convenient. Thanks! I'm sure I'm under-using .Net
in a whole bunch of other places as well, heh.

Get used to it. I still have that problem. :)
Anyway, I noticed a new problem. The dialog I show in response to the
message appears on the taskbar, and I don't like that. [...]
Hope you're still monitoring this thread. :)

I did (obviously) see your message. But you should consider the
possibility that even though a question is related to the same task your
previous question was in reference to, it may be better to start a new
thread. There's a bit of a gray area, but IMHO it's better to err on the
side of starting a new thread, if your follow-up doesn't directly pertain
to or involve any information that was posted previously in the thread.

All that said, you will probably want to set the Form.ShowInTaskbar
property to "false". And as another general tip: it's always a good idea
to just scan through the members of a class. The .NET API is reasonably
well-designed (not perfect, but pretty good) and in a well-designed API,
you can often easily figure out how to do something simply by looking at
the interface exposed by the objects. I think if you'd seen that
property, you'd have immediately recognized it as the one you want. :)

Pete

Oh, yes, that solves it, I should have found that myself, you're
right. I was just so into the "parent"-thing. For my next question I
will make sure to start a new thread altogehter. I will become better
at helping myself as I learn more, this is just my fourth C#-program
and I only got some books two days ago. Thanks very much for the help!

- Eric
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top