Storing a Form in a Hashtable

  • Thread starter Thread starter tcomer
  • Start date Start date
T

tcomer

I'm working on a chat client and this is what I'm trying to do when a
message is received:

private void ReceivedMessage_Event(object sender,
ClientEventArgs e)
{
// the ChatWindow class is derived from a simple form
ChatWindow pm;

try
{
if (e.CommandType == CommandType.PrivateMessage)
{
// check to see if there is already a chat window
open
if (this.m_ChatTable.ContainsKey(e.SenderName))
{
// add the new message to the chat window
pm =
(ChatWindow)this.m_ChatTable[e.SenderName];
pm.AddMessage(e.MessageText);
}
else
{
// there is no window open so we have to create
one and show the form and the message
pm = new ChatWindow(e.SenderName,
this.m_userName);
pm.PrivateMessageSent += new
ChatWindow.PrivateMessageEventHandler(ChatWindow_PrivateMessageSent);
pm.FormClosing += new
FormClosingEventHandler(ChatWindow_FormClosing);
this.m_ChatTable.Add(e.SenderName, pm);
pm.AddMessage(e.MessageText);
pm.ShowDialog();
}
}
}
catch (InvalidOperationException ioe)
{
MessageBox.Show(ioe.StackTrace);
}

I'm getting a "System.InvalidOperationException occured in
MyProgram.exe" message. But the exception is only thrown when I close
the ChatWindow that is created in the "else" block. I've placed
try/catch blocks around every statement in my application but it still
goes unhandled. I'm willing to provide any code that may help you
understand what I'm trying to do. Right now this is the code that I
beleive is the most relevant. Please let me know if I need to provide
more information.
 
tcomer said:
I'm working on a chat client and this is what I'm trying to do when a
message is received:

private void ReceivedMessage_Event(object sender,
ClientEventArgs e)
{
// the ChatWindow class is derived from a simple form
ChatWindow pm;

try
{
if (e.CommandType == CommandType.PrivateMessage)
{
// check to see if there is already a chat window
open
if (this.m_ChatTable.ContainsKey(e.SenderName))
{
// add the new message to the chat window
pm =
(ChatWindow)this.m_ChatTable[e.SenderName];
pm.AddMessage(e.MessageText);
}
else
{
// there is no window open so we have to create
one and show the form and the message
pm = new ChatWindow(e.SenderName,
this.m_userName);
pm.PrivateMessageSent += new
ChatWindow.PrivateMessageEventHandler(ChatWindow_PrivateMessageSent);
pm.FormClosing += new
FormClosingEventHandler(ChatWindow_FormClosing);
this.m_ChatTable.Add(e.SenderName, pm);
pm.AddMessage(e.MessageText);
pm.ShowDialog();
}
}
}
catch (InvalidOperationException ioe)
{
MessageBox.Show(ioe.StackTrace);
}

I'm getting a "System.InvalidOperationException occured in
MyProgram.exe" message. But the exception is only thrown when I close
the ChatWindow that is created in the "else" block. I've placed
try/catch blocks around every statement in my application but it still
goes unhandled. I'm willing to provide any code that may help you
understand what I'm trying to do. Right now this is the code that I
beleive is the most relevant. Please let me know if I need to provide
more information.

It would be helpful to see the stack trace from the exception, and the
code for the ChatWindow class.
 
It would be helpful to see the stack trace from the exception, and the
code for the ChatWindow class.

I'm unable to provide the stacktrace info at this point because I'm
unable to catch the exception, but I'll keep looking and I'll post it
if I can find which block is throwing the exception.

Here is the source for the ChatWindow class that I'm using, please let
me know if I need to post more code.

// Description of ChatWindow
public class ChatWindow : Form
{
protected System.Windows.Forms.RichTextBox rtbChatSummary;
protected System.Windows.Forms.RichTextBox rtbMessageText;
protected System.Windows.Forms.Button btnSend;

protected string m_contactName;
protected string m_senderName;

// Events
public event PrivateMessageEventHandler PrivateMessageSent;
public void PrivateMessageSend(MessageEventArgs e)
{
if (PrivateMessageSent != null)
PrivateMessageSent(this, e);
}

// Delegates
public delegate void PrivateMessageEventHandler(object sender,
MessageEventArgs e);


#region InitializeComponent

/**********************************************************************
* Required for Windows Forms designer support.

**********************************************************************/
private void InitializeComponent()
{
this.rtbChatSummary = new
System.Windows.Forms.RichTextBox();
this.rtbMessageText = new
System.Windows.Forms.RichTextBox();
this.btnSend = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// rtbChatSummary
//
this.rtbChatSummary.Anchor =
((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top
| System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.rtbChatSummary.BackColor =
System.Drawing.SystemColors.Window;
this.rtbChatSummary.BorderStyle =
System.Windows.Forms.BorderStyle.FixedSingle;
this.rtbChatSummary.Location = new System.Drawing.Point(7,
4);
this.rtbChatSummary.Name = "rtbChatSummary";
this.rtbChatSummary.ReadOnly = true;
this.rtbChatSummary.ScrollBars =
System.Windows.Forms.RichTextBoxScrollBars.Vertical;
this.rtbChatSummary.Size = new System.Drawing.Size(347,
150);
this.rtbChatSummary.TabIndex = 0;
this.rtbChatSummary.TabStop = false;
this.rtbChatSummary.Text = "";
this.rtbChatSummary.LinkClicked += new
System.Windows.Forms.LinkClickedEventHandler(this.rtbChatSummary_LinkClicked);
//
// rtbMessageText
//
this.rtbMessageText.Anchor =
((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.rtbMessageText.BorderStyle =
System.Windows.Forms.BorderStyle.FixedSingle;
this.rtbMessageText.Location = new System.Drawing.Point(7,
160);
this.rtbMessageText.Name = "rtbMessageText";
this.rtbMessageText.ScrollBars =
System.Windows.Forms.RichTextBoxScrollBars.Vertical;
this.rtbMessageText.Size = new System.Drawing.Size(347,
66);
this.rtbMessageText.TabIndex = 0;
this.rtbMessageText.TabStop = false;
this.rtbMessageText.Text = "";
this.rtbMessageText.LinkClicked += new
System.Windows.Forms.LinkClickedEventHandler(this.rtbMessageText_LinkClicked);
this.rtbMessageText.KeyDown += new
System.Windows.Forms.KeyEventHandler(this.rtbMessageText_KeyDown);
//
// btnSend
//
this.btnSend.Anchor =
((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom
| System.Windows.Forms.AnchorStyles.Right)));
this.btnSend.Font = new System.Drawing.Font("Tahoma",
8.25F, System.Drawing.FontStyle.Bold,
System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.btnSend.Location = new System.Drawing.Point(279, 232);
this.btnSend.Name = "btnSend";
this.btnSend.Size = new System.Drawing.Size(75, 30);
this.btnSend.TabIndex = 0;
this.btnSend.TabStop = false;
this.btnSend.Text = "Send";
this.btnSend.UseVisualStyleBackColor = true;
this.btnSend.Click += new
System.EventHandler(this.btnSend_Click);
//
// ChatWindow
//
this.ClientSize = new System.Drawing.Size(361, 267);
this.Controls.Add(this.btnSend);
this.Controls.Add(this.rtbMessageText);
this.Controls.Add(this.rtbChatSummary);
this.Font = new System.Drawing.Font("Tahoma", 8.25F,
System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point,
((byte)(0)));
this.Name = "ChatWindow";
this.StartPosition =
System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Chat Window";
this.ResumeLayout(false);

}
#endregion

public string Recipient
{
get { return m_contactName; }
}


/**********************************************************************
* Class constructor

**********************************************************************/
public ChatWindow()
{
InitializeComponent();
}


/**********************************************************************
* Class Constructor

**********************************************************************/
public ChatWindow(string contactName, string senderName)
{
InitializeComponent();
this.Text = (contactName + " - Chat Window");
this.m_contactName = contactName;
this.m_senderName = senderName;
}


/**********************************************************************
* Class Constructor

**********************************************************************/
public ChatWindow(string contactName, string senderName, string
messageText)
{
InitializeComponent();
this.Text = (contactName + " - Chat Window");
this.m_contactName = contactName;
this.m_senderName = senderName;
}


/**********************************************************************
* Adds a message to the chat window

**********************************************************************/
public void AddMessage(string messageText)
{
try
{
this.rtbChatSummary.AppendText(this.m_contactName + ":
" + messageText + "\n");
}
catch (InvalidOperationException ioe)
{
MessageBox.Show(ioe.StackTrace);
}
}


/**********************************************************************
* Handles the Click event of the btnSend control.

**********************************************************************/
private void btnSend_Click(object sender, EventArgs e)
{
try
{
this.SendMessage(this.rtbMessageText.Text);
this.rtbMessageText.Focus();
}
catch (InvalidOperationException ioe)
{
MessageBox.Show(ioe.StackTrace);
}
}


/**********************************************************************
* Handles necessary functions when a message is sent.

**********************************************************************/
protected void SendMessage(string messageText)
{
try
{
if (this.rtbMessageText.Text != "")
{
MessageEventArgs messArgs = new
MessageEventArgs(m_senderName, m_contactName, messageText);
this.rtbChatSummary.ScrollToCaret();
this.rtbChatSummary.AppendText(m_senderName + ": "
+ messageText + "\n");
this.rtbMessageText.Text = "";
this.PrivateMessageSend(messArgs);
}
}
catch (InvalidOperationException ioe)
{
MessageBox.Show(ioe.StackTrace);
}
}

public class MessageEventArgs : EventArgs
{
private string clientName;
private string receiverName;
private string messageText;

public string Sender
{
get { return clientName; }
}

public string Recipient
{
get { return receiverName; }
}

public string MessageText
{
get { return messageText; }
}

public MessageEventArgs(string sender, string recipient, string
message)
{
this.clientName = sender;
this.receiverName = recipient;
this.messageText = message;
}
}
 
tcomer said:
I'm unable to provide the stacktrace info at this point because I'm
unable to catch the exception, but I'll keep looking and I'll post it
if I can find which block is throwing the exception.

If you just let your application die, it should print the stack trace
in the resulting error dialog.
 
tcomer said:
I'm unable to provide the stacktrace info at this point because I'm
unable to catch the exception, but I'll keep looking and I'll post it
if I can find which block is throwing the exception.

As well, you should probably set up global exception handlers for your
application. See here for more details:

http://msdn.microsoft.com/library/d...stemAppDomainClassUnhandledExceptionTopic.asp

and here:

http://msdn2.microsoft.com/en-us/library/system.windows.forms.application.threadexception.aspx

You should handle both.
 
Hi,

....and another option if you're using Visual Studio is to select Exceptions
in the Debug menu and click the checkbox next to "Common Language Runtime
Exceptions". Now, as soon as any exception occurs at runtime, the debugger,
as long as it's attached, will break on the line where the exception is
being thrown if source code is available.

Realize though that you might see several exceptions that you were unaware
of before. Some of these exceptions might be acceptable if they're being
caught and handled by your code or framework code, so you don't necessarily
have to go on some major debugging spree ;)
 
If you just let your application die, it should print the stack trace
in the resulting error dialog.

Unfortunately, thats the problem. All I get is the typical
"YourProgram.exe has encountered a problem and needs to close. We are
sorry for the incovenience."

Enabling Just-In-Time debugging gives me "An unhandled
exception('System.InvalidOperationException') has occured in
YourProgram.exe [2584].

The [2584] can't be the line number because the application doesn't
contain that much code. The number also changes each time the exception
is thrown.

I have found out that the exception is only thrown when closing the
ChatWindow that is created in the "else" block of the
ReceivedMessage_Event.
 
Because it is a client/server application I'm unable to produce the
error in debug mode. I can set the server up to immediately send a
message to the client in hopes to create a error immediately, but I'm
not sure if its possible to make a live connection to the server while
in debug mode.
 
Hi,

Are you using Visual Studio?

My suggestion should solve that problem quickly for you so you'll be able to
move onto the good stuff: debugging :)
 
Are you using Visual Studio?

Yes'sir. VS2005

All of your guys' suggestion have been of great help. I was able to get
the stacktrace which is:

----------------------------------------------------------------------------------------------------------------
at System.Threading.SynchronizationContextSwitcher.Undo()
at System.Threading.ExecutionContextSwitcher.Undo()
at System.Threading.ExecutionContext.runFinallyCode(Object userData,
Boolean exceptionThrown)

at
System.Runtime.CompilerServices.RuntimeHelpers.ExecuteBackoutCodeHelper(Object
backoutCode, Object userData, Boolean exceptionThrown)

at
System.Runtime.CompilerServices.ExecuteCodeWithGauranteedCleanup(TryCode
code, CleanupCode backoutCode, Object userData)

at System.Threading.ExecutionContext.RunInternal(ExecutionContext
executionContext, ContextCallback callback, Object state)

at System.Threading.ExecutionContext.Run(ExecutionContext
executionContext, ContextCallback callback, Object state)

at System.Net.ContextAwareResult.Complete(IntPtr userToken)
at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result,
IntPtr userToken)
at
System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(UInt32
errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)

at
System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32
errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
----------------------------------------------------------------------------------------------------------------
 
I also got this while debugging:

System.InvalidOperationException was unhandled
Message="The Undo operation encountered a context that is different
from what was applied in the corresponding Set operation. The possible
cause is that a context was Set on the thread and not reverted(undone)."
 
Hi,

Well it appears to be framework code - I guess it could be a bug.

Instead of showing the client Form on the remoting thread, you might want to
create your own, or even create another AppDomain to run the GUI. This way,
you'll isolate the GUI from the service so if it crashes the service can
remain active. Also, this might alleviate the problem you are having. It
will also allow the method call to return to the remote caller immediately
since you're not using the OneWayAttribute but you're blocking the thread.
 
tcomer said:
Indeed. I'm no expert at mult-threading but I'll keep plugging away and
see if I can't figure out a work-around. Thanks to all of you for the
help, it's highly appreciated.

This is the only information I've found on the problem:
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=173873&SiteID=1

You said the magic word: multi-threading.

I didn't see any threading code in the samples you posted, so I assumed
that you were doing everything on the UI thread. Is the ReceivedMessage
event handler (or whatever it was called) from your first post being
called on a background thread? If it is, then that's your problem: you
can't EVER manipulate UI controls from a background thread. Instead,
you should Invoke back onto the UI thread before doing any monkeying
with the chat forms.

Is that what is going on: is ReceivedMessage effectively being called
on a worker thread?
 
Have you tried using OneWayAttribute?
No, I have not tried that yet.

You said the magic word: multi-threading.

I didn't see any threading code in the samples you posted, so I assumed
that you were doing everything on the UI thread. Is the ReceivedMessage
event handler (or whatever it was called) from your first post being
called on a background thread? If it is, then that's your problem: you
can't EVER manipulate UI controls from a background thread. Instead,
you should Invoke back onto the UI thread before doing any monkeying
with the chat forms.

Is that what is going on: is ReceivedMessage effectively being called
on a worker thread?

Yes, my apoligies. I guess I failed to mention that the chat client is
multi-threaded. I will try to Invoke the ReceivedMessage_Event instead.
 
tcomer said:
No, I have not tried that yet.



Yes, my apoligies. I guess I failed to mention that the chat client is
multi-threaded. I will try to Invoke the ReceivedMessage_Event instead.

Or, so long as ClientEventArgs doesn't point to anything that the
caller will later change, you can just do a BeginInvoke to kick off
showing the dialog to the user, and free the calling thread to go back
to get another message, or whatever it does.

There's a little invocation trick that Jon Skeet mentions somewhere in
his article on multi-threading by which at the start of the routine you
check to see if an invoke is required and if one is you just invoke
yourself:

http://www.yoda.arachsys.com/csharp/threads/
 
Awesome! When the ReceivedMessage_Event is triggered, I just do a
BeginInvoke() which handles the ChatWindow being displayed, and it
seems to solve the problem. I can't tell you how much that helps. My
sincere apoligies for not mentioning multi-threading from the get-go. I
hate programming :(
 
tcomer said:
Awesome! When the ReceivedMessage_Event is triggered, I just do a
BeginInvoke() which handles the ChatWindow being displayed, and it
seems to solve the problem. I can't tell you how much that helps. My
sincere apoligies for not mentioning multi-threading from the get-go. I
hate programming :(

Multi-threading is HARD. My first job was working on embedded,
real-time, multi-tasking systems (telephone switches). It was
painstaking work: think for hours, make a few little changes, and then
test them to death.

In Windows Forms, the iron-clad rule is never do ANYTHING with a UI
component (a control, a form, or a Component) from any thread but the
UI thread. Don't change its properties, don't even QUERY its
properties, and certainly don't create a form or try to show one.

Most of my background threading looks like this:
1. Grab all required information from UI controls and stuff it in a
parameter class.
2. Start the background task and pass the parameter class.
3. Background task uses only information from parameter class and puts
results back into parameter class.
4. Background task BeginInvokes or Invokes back onto UI thread.
5. UI thread method pulls results out of parameter class and updates UI
accordingly.

It seems complex, but once you've done it a few times you get used to
it.

Since you're starting out in a background thread, you can just ignore
items 1 through 3. Just always remember which thread your method is
running on and make sure that anything that runs in the background does
not mess with UI controls.
 
Hi Bruce,

Well, since the OP's code seems to be responding to a remoting event, I
assumed that the code was executing on a ThreadPool thread.
 
Back
Top