Note: As usual in about a week, I will have this article shown in VB 9.0 code.  So check back later.
    
    
Introduction:
    
    In Part 1 we built a very simple, flicker-free, frame-less, web chat application using ASP.Net 3.5, LINQ-to-SQL, AJAX and MS SQL Server, with your choice of either C# 3.5 or VB 9.0 in about 2 hours.  As promised,
    we will add functionality to our previous chatroom application so that users will have the ability to chat with each other privately.  You can read and download the code of Part 1 in 
C# 3.5 here, 
    or in 
VB 9.0 here.  
    
    Here's a snapshot of the Chatroom area.  
    
    
    
 
    
    
    I noticed while coding for part 2 that the names (in black) of the chatters didn't show in the chatroom messages, so I added them real quickly.  Although I have
    no doubt that you could have as easily done so, but here's the code revision anyway:
    
    
          201    if (message.User.Sex.ToString().ToLower() == "m")
          202        sb.Append("<img src='Images/manIcon.gif' style='vertical-align:middle' alt=''> <span style='color: black; font-weight: bold;'>" + 
          203            message.User.Username + ":</span>  " + message.Text + "</div>");
          204    else
          205        sb.Append("<img src='Images/womanIcon.gif' style='vertical-align:middle' alt=''> <span style='color: black; font-weight: bold;'>" + 
          206            message.User.Username + ":</span>  " + message.Text + "</div>");
     
       
    Database Revision/Addition
    
    
    We will need a table to hold information regarding users that sends invitations to other chatters, so they can chat privately.  As shown below, I added a table called PrivateMessage.
    
    
 Chat With Another User Privately
    
    
    Chat With Another User Privately
	
	
	There are several things that happens when we send someone an invitation to chat privately. 
	
	
	
		-    
			To start sending another chatter an invitation to chat, we simply click the chatter's user name from the list of the users, shown in a box to the right, on the 
		    Chatroom.aspx page.  This should open another window, the ChatWindow.aspx.  A new window opens because of the additional code shown below:
		    
 
 The ChatWindow.aspx opens or pops up:
 
   
 
 Using the code below:
 
 		  		    		  
                  155    sb.Append(userIcon + "<a href=# onclick=\"window.open('ChatWindow.aspx?FromUserId=" + Session["ChatUserID"] +  156            "&ToUserId=" + loggedInUser.User.UserID + "&Username=" + loggedInUser.User.Username +   157            "','','width=400,height=200,scrollbars=no,toolbars=no,titlebar=no,menubar=no'); isLostFocus = 'true';\">" +   158            loggedInUser.User.Username + "</a><br>"); 
 
 
 
- 
    		To send an invitation, we first need to send a message to the other chatter on the newly popped-up window (ChatWindow.aspx), just like other chat applications out there.  When you send
		    a message to let's say "John Doe" from this window for the very first time, information is saved in the PrivateMessage table.  So, just to make it clear, 
		    all messages sent after the first one to "John Doe" will not be saved in the PrivateMessage table.  
		    
 
 		  		    		  
                   53         // This is where we send a private chat invitation to other chatters    54         private void InsertPrivateMessage()    55         {   56             // if the private message is sent to this user already,     57             // don't send it again    58             if (String.IsNullOrEmpty(lblMessageSent.Text))    59             {   60                 // if any private message is found based on the     61                 // from user id, or the to user id, then this     62                 // private message will not be inserted    63                 PrivateMessage privateMessage = new PrivateMessage();    64                 privateMessage.UserID = Convert.ToInt32(lblFromUserId.Text);    65                 privateMessage.ToUserID = Convert.ToInt32(lblToUserId.Text);    66     67                 LinqChatDataContext db = new LinqChatDataContext();    68                 db.PrivateMessages.InsertOnSubmit(privateMessage);    69                 db.SubmitChanges();    70     71                 // make sure to assign any value to this label    72                 // to confirm that a message is sent to this user    73                 lblMessageSent.Text = ConfigurationManager.AppSettings["ChatWindowMessageSent"];    74             }    75         } 
 
 
 
- 
		    The information saved in the PrivateMessage table is then retrieved from the main chatroom page (Chatroom.aspx) every seven (7) seconds or whenever you hit
		    the send button, checking if another user(s) have sent you an invitation to chat privately.  If an entry is found, the hidden panel server control's visible attribute
		    is then set to "true", showing the invitation to chat privately.
		    
 
 Here's the code that checks if an invitation to chat privately is sent to you:
 
 		  		    		  
                  237         /// <summary>   238         /// Check if anyone invited me to chat privately   239         /// </summary>   240         private void GetPrivateMessages()   241         {  242             LinqChatDataContext db = new LinqChatDataContext();   243    244             var privateMessage = (from pm in db.PrivateMessages   245                                   where pm.ToUserID == Convert.ToInt32(Session["ChatUserID"])   246                                   select pm).SingleOrDefault();   247    248             if (privateMessage != null)   249             {  250                 lblChatNowUser.Text = privateMessage.User.Username;   251                 btnChatNow.OnClientClick =    252                     "window.open('ChatWindow.aspx?FromUserId=" + Session["ChatUserID"] +   253                     "&ToUserId=" + privateMessage.UserID + "&Username=" + privateMessage.User.Username +    254                     "&IsReply=yes','','width=400,height=200,scrollbars=no,toolbars=no,titlebar=no,menubar=no'); isLostFocus = 'true';";   255    256                 pnlChatNow.Visible = true;   257    258    259                 db.PrivateMessages.DeleteOnSubmit(privateMessage);   260                 db.SubmitChanges();   261             }   262         } 
 
 
 Here's a snapshot of the panel server control showing that someone has invited you to chat privately:
 
   
 
 
- 
			You will notice that there are two buttons on the invitation box, the "Chat Now" and "Cancel" buttons.  Whether you click the Chat Now or the Cancel button, 
			the entry on the PrivateMessage table for you and the user that invited you will be deleted (Line 259 and 260 shown above) and the panel's visible attribute will be 
			set to "false", and this is primarily what the Cancel button does.  On the other hand, if you click the Chat Now button, a new window will pop-up (Lines 251-254 shown above)
			and you can now start chatting privately with the user that invited you to chat.  Code is shown below from the Chatroom.aspx code file:
			
 
 		  		    		  
                  264         protected void BtnChatNow_Click(object sender, EventArgs e)   265         {  266             pnlChatNow.Visible = false;   267         }   268    269         protected void BtnCancel_Click(object sender, EventArgs e)   270         {  271             pnlChatNow.Visible = false;   272         } 
 
 
 As usual, I was in a hurry to finish this code.  One suggestion would be, when the user clicks the Cancel button, send the other user who sent the invitation
			a message saying that you declined the invitation to chat privately.  This can easily be done on the Cancel button's click event.  Maybe I'll add this 
			in Part 3.
 
 
Difference Between Chatting Privately and Chatting Corporately
	Other than the obvious missing list of users when chatting privately, the main difference code-wise is; we're sending the message to a specific user.  Remember in Part 1 that the ToUserID
	property of the Message object/table is not being filled or passed, now it's still optional to assign a value in this property.  But, if you want to chat privately with someone, the
	ToUserID must be assigned.  See code below from the ChatWindow.aspx code file:
	
	
	
		  		    		  
		   53         // This is where we send a private chat invitation to other chatters
           54         private void InsertPrivateMessage()
           55         {
           56             // if the private message is sent to this user already, 
           57             // don't send it again
           58             if (String.IsNullOrEmpty(lblMessageSent.Text))
           59             {
           60                 // if any private message is found based on the 
           61                 // from user id, or the to user id, then this 
           62                 // private message will not be inserted
           63                 PrivateMessage privateMessage = new PrivateMessage();
           64                 privateMessage.UserID = Convert.ToInt32(lblFromUserId.Text);
           65                 privateMessage.ToUserID = Convert.ToInt32(lblToUserId.Text);
           66 
           67                 LinqChatDataContext db = new LinqChatDataContext();
           68                 db.PrivateMessages.InsertOnSubmit(privateMessage);
           69                 db.SubmitChanges();
           70 
           71                 // make sure to assign any value to this label
           72                 // to confirm that a message is sent to this user
           73                 lblMessageSent.Text = ConfigurationManager.AppSettings["ChatWindowMessageSent"];
           74             }
           75         }
  		  
	 
	
	Focusing The Current Window 
	
	In Part 1 we focused the Send button by setting the defaultbutton attribute of the form tag to the ID of the Send button.  This made a continous focus on the Send button
	so that when we hit the "Enter key" on our keyboard, the message is sent, there's no need to manually click the Send button with your mouse to send a message.  Interestingly enough,
	we cannot do this when another window opens up as a child or children window because the focus will keep returning back to the parent window.  Or if we also set the defaultbutton
	attribute of the form tag to the ID of the Send button in the child window (ChatWindow.aspx), the focus will go back and forth from the Chatroom.aspx to the ChatWindow.aspx, and 
	vice versa.  
	
	So why does this happen?
	
	
		- 
			The window will refocus the Send button, therefore focusing back on the window itself every single time there's a post back.  The post back occurs 
			every 7 seconds on the timer tick event.
			
 
 
- 
			The parent and child windows share Sessions.  You will notice in Part 1 that if you open two or more instances of the Chatroom.aspx window, 
			the focus does not go back and fort these windows even though the defaultbutton attribute is set for the Send button.  This is 
			because the windows are instances of the browser, and therefore does not share Session with each other.
			
 
 
	
	The solution:
	
	
	We need to be able to focus the Message TextBox (where you type your messages) and the Send button dynamically and on demand.  To do this, we need to be able to tell 
	which window is currently in focus, is it the main chat room (Chatroom.aspx)?  Or is it one of the opened private chat windows (ChatWindow.aspx)?  Listed below are the 
	events from which we need to set the focus of our message text box and the send button:
	
	
	
		- 
			Shown below is the reusable method that we will call to set the focus from different events in our code.  Why is the code longer in the ChatWindow.aspx code file?  Because
			we're assuming that each Chatroom.aspx that is open are separate instances of the browser, therefore, each Chatroom.aspx window that is 
			open only need one marker, "MainWindow".  On the other hand, each ChatWindow.aspx that is open shares the same Session as the parent, and are not a separate instances of the
			browser, this is why we need to know which of these ChatWindows is supposed to receive the focus.
			
 
 From the Chatroom.aspx:
 
 		  		    		  
                  274         private void FocusThisWindow()   275         {  276             form1.DefaultButton = "btnSend";   277             form1.DefaultFocus = "txtMessage";   278             Session["DefaultWindow"] = "MainWindow";   279         } 
 
 
 From the ChatWindow.aspx:
 
 		  		    		  
                  141         private void FocusThisWindow()   142         {  143             string chatWindowToFocus = lblFromUserId.Text + "_" + lblToUserId.Text;   144    145             if (Session["DefaultWindow"].ToString() == chatWindowToFocus)   146             {  147                 form1.DefaultButton = "btnSend";   148                 form1.DefaultFocus = "txtMessage";   149             }   150         } 
 
 
 
- 
			Every single time a window loads, or opens, whether it be the Chatroom.aspx or the ChatWindow.aspx, we need to set the focus on
			the Page Load event under the !IsPostBack validation.  
			
 
 From the Chatroom.aspx, we simply call the method:
 
 		  		    		  
                   12         protected void Page_Load(object sender, EventArgs e)    13         {   14             if (!IsPostBack)    15             {                  .                   .    29                 this.FocusThisWindow();                   .                   . 
 
 
 But from the ChatWindow.aspx, we need to remember which chat window must receive the focus:
 
 		  		    		  
                   12         protected void Page_Load(object sender, EventArgs e)    13         {                  .                   .    16             if (!IsPostBack)    17             {                  .                   .    30                 string chatWindowToFocus = lblFromUserId.Text + "_" + lblToUserId.Text;    31                 Session["DefaultWindow"] = chatWindowToFocus;    32                 this.FocusThisWindow();                   .                   . 
 
 
 
- 
			In the OnClick event of the Send button.  Both Chatroom.aspx and ChatWindow.aspx code file have similar code here.
			
 
 		  		    		  
                   58         protected void BtnSend_Click(object sender, EventArgs e)    59         {   60             if (txtMessage.Text.Length > 0)    61             {                      .                       .    66                 this.FocusThisWindow(); 
 
 
 
- 
			In the tick event of the Timer Control.
			
 
 From the Chatroom.aspx page:
 
 		  		    		  
                   70         protected void Timer1_OnTick(object sender, EventArgs e)    71         {                  .                   .    76             if (Session["DefaultWindow"] != null)    77             {   78                 if (Session["DefaultWindow"].ToString() == "MainWindow")    79                 {   80                     this.FocusThisWindow();    81                 }    82             }    83         } 
 
 
 From the ChatWindow.aspx page:
 
 		  		    		  
                  131         protected void Timer1_OnTick(object sender, EventArgs e)   132         {                  .                   .   135             if (Session["DefaultWindow"] != null)   136             {  137                 this.FocusThisWindow();   138             }   139         } 
 
 
 
- 
			And lastly, in the onclick event of the Message TextBox control.  The only catch here is that; the Message Text Box control does not have
			a server side onclick event.  So we'll have to do this from the client-side, and from the client, we will call a server-side method.  In Part 1,
			we implemented a callback feature so that when a chatter closes his browser, we're still able to log that user out by catching a client-side event
			that calls to a server-side method, where the actual logging out is done.  We will do a similar code here.
			
 
 From the TextBox, we will call a client-side function, shown below:
 
 		  		    		  
                
				< asp:TextBox
				Id="txtMessage"
				onkeyup="ReplaceChars()"
				
				onclick="FocusMe()"
				onfocus="SetToEnd(this)"
				runat="server"
				MaxLength="100"
				Width="500px"
				/>
 
 
 Here's the client-side function that is called from the onclick event of the Text Box control.  Notice that it's calling
			another method.  This is a call-back method called asynchronously with the client-side function, which we will wire-up from the server side.
 
 		  		    		  
				
				function 
				FocusMe()
					{ FocusThisWindowCallBack( 'FocusThisWindow');} 
 
 
 In the code file (code-behind), because we're implementing the ICallbackEventHandler (see Part 1 for explanation), we can easily 
			register scripts for the call back functionality.
 
 		  		    		  
                   36         // create a call back reference so that we can refocus to this window when the cursor is placed in the message text box    37         string focusWindowCallBackReference = Page.ClientScript.GetCallbackEventReference(this, "arg", "FocusThisWindow", "");    38         string focusThisWindowCallBackScript = "function FocusThisWindowCallBack(arg, context) { " + focusWindowCallBackReference + "; }";   39         Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "FocusThisWindowCallBack", focusThisWindowCallBackScript, true); 
 
 
 Now, the magic happens in the ICallbackEventHandler's RaiseCallbackEvent method.  The control was passed here when
			we called the FocusThisWindowCallBack('FocusThisWindow') from the client-side.  The "eventArgument" works as as comma-delimited
			string variable, so if you passed more than one string as a parameter from the client-side function, all will be caught 
			by this variable.  Note that all callback methods from the client-side will pass through the RaiseCallbackEvent.  With
			that said, in Part 1, we did not pass any parameter(s) from the Log Out callback method, because there's only one method that
			will pass through here.  But now both the Log Out and the Message Text Box control's client-side onclick 
			event will use the RaiseCallbackEvent.  In short, we will differentiate who did the call back by checking the eventArgument's value.
 
 From the Chatroom.aspx code file:
 
 		  		    		  
                  288         /// <summary>   289         /// We're doing 2 things here now so we want to validate whether we're trying to "LogOut" or "FocusThisWindow"   290         /// based on the eventArgument parameter that is passed via a JavaScript callback method   291         /// </summary>   292         void  System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)   293         {  294             _callBackStatus = "failed";   295    296             if (!String.IsNullOrEmpty(eventArgument))   297             {  298                 // put back the focus on this window   299                 if (eventArgument == "FocusThisWindow")   300                 {  301                     this.FocusThisWindow();   302                 }   303             }   304    305             if (!String.IsNullOrEmpty(eventArgument))   306             {  307                 if (eventArgument == "LogOut")   308                 {  309                     // log out the user by deleting from the LoggedInUser table   310                     LinqChatDataContext db = new LinqChatDataContext();   311    312                     var loggedInUser = (from l in db.LoggedInUsers   313                                         where l.UserID == Convert.ToInt32(Session["ChatUserID"])   314                                         && l.RoomID == Convert.ToInt32(lblRoomId.Text)   315                                         select l).SingleOrDefault();   316    317                     db.LoggedInUsers.DeleteOnSubmit(loggedInUser);   318                     db.SubmitChanges();   319    320                     // insert a message that this user has logged out   321                     this.InsertMessage("Just logged out! " + DateTime.Now.ToString());  322                 }   323             }   324    325             _callBackStatus = "success";   326         } 
 
 
 From the ChatWindow.aspx code file:
 
 		  		    		  
                  159         void System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)   160         {  161             if (!String.IsNullOrEmpty(eventArgument))   162             {  163                 if (eventArgument == "FocusThisWindow")   164                 {  165                     string chatWindowToFocus = lblFromUserId.Text + "_" + lblToUserId.Text;   166                     Session["DefaultWindow"] = chatWindowToFocus;   167                     this.FocusThisWindow();   168                 }   169             }   170         } 
 
 
 
Last Words:
	Adding features to this web chat application using ASP.Net 3.5, LINQ-to-SQL, and ASP.Net AJAX surely made coding a whole lot faster, and therefore fun.  Adding
	this private imming or chatting functionality took about an hour to complete.  Doing this in ASP.Net 2.0 would have taken at least 3 times longer to complete.  So kudos to
	the ASP.Net team.  I'm planning to write a Part 3, but I'm not sure what to write about or what functionalities to add, so if you have any idea of the functionalities that I can add, please email me.
 
            
             As always, the code and the article are provided "As Is", there is absolutely no warranties. Use at your own risk.