65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Silverlight 构建带有多个房间和私聊的 Silverlight Web 聊天室 - 第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (12投票s)

2009年3月17日

CPOL

14分钟阅读

viewsIcon

128355

downloadIcon

4086

在第一部分中,我们构建了一个简单的Silverlight 2 Web聊天室。现在我们将添加功能,使用户能够从多个聊天室中选择,以及与其他用户进行私人聊天。

引言

第一部分中,我们构建了一个简单的Silverlight 2 Web聊天室。现在我们将添加功能,使用户能够从多个聊天室中选择,以及与其他用户进行私人聊天。看看Silverlight Web聊天室的修订快照。

Silverlight Chatroom Part 2

要求

我们将添加一种方式供用户从聊天室列表中选择进入。还将建立与其他用户的私人聊天。旧的要求仍然有效,如下所示:

  • 必须可以随时随地访问,并且无需下载和安装任何组件。这就是为什么我们要创建一个Web聊天室。
  • Web聊天室必须“无闪烁”。您会发现Silverlight中的所有处理都是异步完成的。
  • 我们希望能够使用数据库监控聊天对话。我们将使用 MS SQL Server 存储对话和用户信息。
  • 使用LINQ-to-SQL的动态SQL,而不是存储过程,以实现超快速编码。

数据库

我们将使用现有的数据库,并在PrivateMessage表中添加一个附加字段,如下所示。有关表描述,请参阅第一部分。下面是附加的PrivateMessage表描述。

Database Structure

  • PrivateMessage:包含私人邀请信息。当向另一个用户发送私人聊天邀请时,此处会添加一条记录。

新添加的XAML文件

很简单,因为我们在第一部分中已经开始使用基于其功能的XAML文件,所以我们只需要添加两个(2)个新文件:Rooms.xamlPrivateChat.xaml

  • Rooms.xaml:显示数据库中Rooms表中列出的聊天室列表。因此,要为此Web聊天应用程序添加新房间,只需转到Rooms表并添加任意数量的房间,无需额外编码。
  • PrivateChat.xaml:这是当被另一用户邀请聊天并接受邀请时弹出的私人聊天窗口。

登录更改

我们的登录机制(Login.xaml.cs)有一个非常小的改动。

  1. 当用户通过身份验证后,我们将该用户保存到LoggedInUser表中,如第81-82行所示。请注意,我们传递的唯一信息是用户ID,因为该用户尚未选择房间。第82行(Login.xaml.cs)调用ILinqChatService接口中的一个新方法。
  2.    39     [OperationContract]
       40     void LogInUser(int userID);

    像往常一样,此方法在LinqChatService服务中实现。

      157    void ILinqChatService.LogInUser(int userID)
      158    {
      159        // login the user
      160        LinqChatDataContext db = new LinqChatDataContext();
      161 
      162         LoggedInUser loggedInUser = new LoggedInUser();
      163         loggedInUser.UserID = userID;
      164         db.LoggedInUsers.InsertOnSubmit(loggedInUser);
      165         db.SubmitChanges();
      166     }
  3. 用户不会直接进入第一部分中唯一可用的房间,而是被重定向到房间列表页面(Rooms.xaml),如第88行所示。
  4.     72    void proxy_UserExistCompleted(object sender, 
                   Silverlight2Chat.LinqChatReference.UserExistCompletedEventArgs e)
       73     {
       74         if (e.Error == null)
       75         {
       76             int userID = e.Result;
       77 
       78             if (userID != -1)
       79             {
       80                 // save user to the login table
       81                 LinqChatReference.LinqChatServiceClient proxy = 
                                new LinqChatReference.LinqChatServiceClient();
       82                 proxy.LogInUserAsync(userID);
       83 
       84                 // go to the chatroom page
       85                 App app = (App)Application.Current;
       86                 app.UserID = userID;
       87                 app.UserName = TxtUserName.Text;
       88                 app.RedirectTo(new Rooms());
       89             }
       90             else
       91             {
       92                 TxtbNotfound.Visibility = Visibility.Visible; 
       93             }
       94         }
       95     }

选择房间

Rooms.xaml的界面非常简单。它包含两个(2)个控件:一个用于显示标题的TextBlock,以及一个容纳垂直堆叠的可用房间列表的StackPanel

Choose a Room

    1 <UserControl x:Class="Silverlight2Chat.Rooms"
    2     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    3     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    4     Width="600" Height="340">
    5     <Grid x:Name="LayoutRoot" Background="White" ShowGridLines="False">
    6         <Grid.RowDefinitions>
    7             <RowDefinition Height="10" />       <!-- padding -->
    8             <RowDefinition Height="38" />       <!-- title -->
    9             <RowDefinition Height="10" />       <!-- padding -->
   10             <RowDefinition Height="*" />        <!-- rooms -->
   11             <RowDefinition Height="10" />       <!-- padding -->
   12         </Grid.RowDefinitions>
   13 
   14         <Grid.ColumnDefinitions>
   15             <ColumnDefinition Width="10" />     <!-- padding -->
   16             <ColumnDefinition Width="*" />      <!-- rooms -->
   17             <ColumnDefinition Width="10" />     <!-- padding -->
   18         </Grid.ColumnDefinitions>
   19 
   20         <TextBlock Text="Choose a Room" Grid.Row="1" 
                 Grid.Column="1" FontSize="22" Foreground="Navy" />
   21 
   22         <StackPanel x:Name="SpnlRoomList" 
                 Orientation="Vertical" Grid.Row="3" 
                 Grid.Column="1" />
   23     </Grid>
   24 </UserControl>

在代码文件(Rooms.xaml.cs)中,我们首先在第26行通过检查用户名来检查用户是否已登录。我们也可以同样检查用户ID而不是用户名。然后在第29行获取聊天室。如第一部分所述,因为我们尝试从客户端服务代理(WCF服务)获取值,所以我们在调用“Async”事件之前调用客户端代理的“Completed”事件,第35-36行。proxy_GetRoomsCompleted事件从Room表中检索所有可用房间,并将它们分配给一个HyperlinkButton,显示在第39-55行。请注意,第50行调用了HyperlinkButtonClick事件。当用户单击聊天室列表中列出的某个房间时,HyperlinkButtonClick事件仅“记住”房间ID和房间名称(第62-64行),然后将用户重定向到聊天室页面(Chatroom.xaml)。有关WCF服务基础知识的更多信息,请阅读第一部分。

   20     public Rooms()
   21     {
   22         InitializeComponent();
   23 
   24         App app = (App)Application.Current;
   25 
   26         if (String.IsNullOrEmpty(app.UserName))
   27             app.RedirectTo(new Login());
   28 
   29         GetChatRooms();  
   30     }
   31 
   32     private void GetChatRooms()
   33     {
   34         LinqChatReference.LinqChatServiceClient proxy = 
                new LinqChatReference.LinqChatServiceClient();
   35         proxy.GetRoomsCompleted += new 
                EventHandler<Silverlight2Chat.LinqChatReference.
                GetRoomsCompletedEventArgs>(proxy_GetRoomsCompleted);
   36         proxy.GetRoomsAsync();
   37     }
   38 
   39     void proxy_GetRoomsCompleted(object sender, 
              Silverlight2Chat.LinqChatReference.GetRoomsCompletedEventArgs e)
   40     {
   41         if (e.Error == null)
   42         {
   43             ObservableCollection<LinqChatReference.RoomContract> rooms = e.Result;
   44 
   45             foreach (var room in rooms)
   46             {
   47                 HyperlinkButton linkButton = new HyperlinkButton();
   48                 linkButton.Name = room.RoomID.ToString();
   49                 linkButton.Content = room.Name;
   50                 linkButton.Click += new RoutedEventHandler(linkButton_Click);
   51 
   52                 SpnlRoomList.Children.Add(linkButton);
   53             }
   54         }
   55     }
   56 
   57     void linkButton_Click(object sender, RoutedEventArgs e)
   58     {
   59         HyperlinkButton linkButton = sender as HyperlinkButton;
   60 
   61         // assign the room 
   62         App app = (App)Application.Current;
   63         app.RoomID = Convert.ToInt32(linkButton.Name);
   64         app.RoomName = linkButton.Content.ToString();
   65 
   66         // redirect
   67         app.RedirectTo(new Chatroom());
   68     }

聊天室页面

您可能已经注意到,Chatroom.xaml页面的GUI(图形用户界面)也有一个非常小的修订。GUI修订主要与功能无关。登录用户名已移至标题上方,位于StackPanel中,现在是灰色的(第25行)。在“注销”按钮上方添加了一个“选择其他房间”按钮(第30行),也位于StackPanel中。请注意,我们正在使用一个简单的StackPanel(第43-44行)来显示用户列表,而不是像第一部分中使用的ItemsControl数据模板控件,我将在本文稍后解释原因。

    1     <UserControl x:Class="Silverlight2Chat.Chatroom"
    2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    4         Width="600" Height="346">
    5         <Grid x:Name="LayoutRoot" Background="White" 
                    ShowGridLines="False" Loaded="LayoutRoot_Loaded">
    6             <Grid.RowDefinitions>
    7                 <RowDefinition Height="10" />       <!-- padding -->
    8                 <RowDefinition Height="46" />       <!-- title -->
    9                 <RowDefinition Height="10" />       <!-- padding -->
   10                 <RowDefinition Height="*" />        <!-- messages, userlist -->
   11                 <RowDefinition Height="10" />       <!-- padding -->       
   12                 <RowDefinition Height="26" />       <!-- message text box, send button -->
   13                 <RowDefinition Height="10" />       <!-- padding -->
   14             </Grid.RowDefinitions>
   15 
   16             <Grid.ColumnDefinitions>
   17                 <ColumnDefinition Width="10" />     <!-- padding -->
   18                 <ColumnDefinition Width="*" />      <!-- messages, message text box-->
   19                 <ColumnDefinition Width="10" />     <!-- padding -->
   20                 <ColumnDefinition Width="120" />    <!-- user list, send button-->
   21                 <ColumnDefinition Width="10" />     <!-- padding -->
   22             </Grid.ColumnDefinitions>
   23 
   24             <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1">
   25                 <TextBlock x:Name="TxtbLoggedInUser" FontSize="10" 
                          Foreground="Gray" FontWeight="Bold" 
                          Margin="0,0,0,4" />
   26                 <TextBlock x:Name="TxtbRoomName" 
                          FontSize="24" Foreground="Navy" />    
   27             </StackPanel>
   28 
   29             <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="3">
   30                 <Button x:Name="BtnChooseRoom" 
                          Content="Choose Other Room" FontSize="10" 
                          Click="BtnChooseRoom_Click" Margin="0,0,0,4" />
   31                 <Button x:Name="BtnLogOut" Content="Log Out" 
                          FontSize="10" Click="BtnLogOut_Click" />
   32             </StackPanel>
   33 
   34             <ScrollViewer x:Name="SvwrMessages" Grid.Row="3" Grid.Column="1" 
   35                           HorizontalScrollBarVisibility="Hidden" 
   36                           VerticalScrollBarVisibility="Visible" 
                                BorderThickness="2">
   37                 <StackPanel x:Name="SpnlMessages" Orientation="Vertical" />
   38             </ScrollViewer>
   39 
   40             <ScrollViewer x:Name="SvwrUserList" 
                          Grid.Row="3" Grid.Column="3" 
   41                     HorizontalScrollBarVisibility="Auto" 
   42                     VerticalScrollBarVisibility="Auto" BorderThickness="2">
   43                 <StackPanel x:Name="SpnlUserList" Orientation="Vertical">
   44                 </StackPanel>
   45             </ScrollViewer>
   46 
   47             <StackPanel Orientation="Horizontal" 
                           Grid.Row="5" Grid.Column="1" >
   48                 <TextBox x:Name="TxtMessage" 
                           TextWrapping="Wrap" KeyDown="TxtMessage_KeyDown"  
   49                      ScrollViewer.VerticalScrollBarVisibility="Visible" 
   50                      ScrollViewer.HorizontalScrollBarVisibility="Disabled"
   51                      Width="360"
   52                      BorderThickness="2" Margin="0,0,10,0"/>  
   53 
   54                 <ComboBox x:Name="CbxFontColor" Width="80">
   55                     <ComboBoxItem Content="Black" Foreground="White" 
                                Background="Black" IsSelected="True" />
   56                     <ComboBoxItem Content="Red" Foreground="White" Background="Red" />
   57                     <ComboBoxItem Content="Blue" Foreground="White" Background="Blue" />
   58                 </ComboBox>
   59             </StackPanel>
   60 
   61             <Button x:Name="BtnSend" Content="Send" 
                      Grid.Row="5" Grid.Column="3" Click="BtnSend_Click" />
   62         </Grid>
   63     </UserControl>

这里的绝大多数更改都是为了支持与其他用户的私人消息以及离开当前房间以选择其他房间。因此,我们将直接深入探讨与其他用户进行私人聊天的细节。

  1. 获取用户:如上所述,我们将ItemsControl更改为简单的StackPanel,用于显示用户列表。主要原因之一是,我们希望除了当前登录用户(即您)之外,所有用户都显示为HyperlinkButton(第77-107行)。很简单,想法是:您不希望单击自己的名字而不小心与自己聊天。
  2.    64     void proxy_GetUsersCompleted(object sender, 
                   Silverlight2Chat.LinqChatReference.GetUsersCompletedEventArgs e)
       65     {
       66         if (e.Error == null)
       67         {
       68             ObservableCollection<LinqChatReference.UserContract> users = e.Result;
       69             SpnlUserList.Children.Clear();
       70 
       71             foreach (var user in users)
       72             {
       73                 // show the current user as a non-clickable text only
       74                 // all other users should be hyperlinks
       75                 App app = (App)Application.Current;
       76 
       77                 if (user.UserID == app.UserID)
       78                 {
       79                     TextBlock tb = new TextBlock();
       80                     tb.Text = user.UserName;
       81                     tb.Foreground = new SolidColorBrush(Colors.Black);
       82                     tb.FontWeight = FontWeights.Bold;
       83 
       84                     SpnlUserList.Children.Add(tb);
       85                 }
       86                 else
       87                 {
       88                     HyperlinkButton hb = new HyperlinkButton();
       89                     hb.Content = user.UserName;
       90 
       91                     // build the absolute url
       92                     Uri url = System.Windows.Browser.HtmlPage.Document.DocumentUri;
       93                     string link = url.OriginalString;
       94                     int lastSlash = link.LastIndexOf('/') + 1;
       95                     link = link.Remove(lastSlash, link.Length - lastSlash) +
       96                         "Chatroom.aspx?fromuserid=" + app.UserID.ToString() +
       97                         "&fromusername=" + app.UserName +
       98                         "&touserid=" + user.UserID +
       99                         "&tousername=" + user.UserName +
      100                         "&isinvited=false";
      101 
      102                     // build the hyperlink
      103                     hb.TargetName = "_blank";
      104                     hb.NavigateUri = new Uri(link);
      105 
      106                     SpnlUserList.Children.Add(hb);
      107                 }  
      108             }
      109         }
      110     }

    要私密地与某人聊天,我希望在单击用户列表中的某个用户时打开一个新的浏览器。目前,Silverlight的HyperlinkButton控件只能分配绝对URL。因此,要打开一个新浏览器,我们需要在浏览器中操作当前URL(上面的第92-100行)来为每个用户构建一个链接。请注意,上面的第96行显示,当单击此用户时,它将打开“Chatroom.aspx”ASP.NET页面。这是因为,正如我们在第一部分中所讨论的,所有XAML用户控件都将由一个(1)ASP.NET页面托管,该页面就是Chatroom.aspx页面。由于新浏览器意味着聊天应用程序的新实例,所以我们遇到的第一个XAML用户控件是App.xaml。那么,如何加载PrivateChat.xaml用户控件呢?我们当然不希望每次单击另一个用户进行私人聊天时都输入用户名和密码。

    这就是为什么我们在上面第96-100行中看到的一些查询字符串键/值对被分配给每个用户链接。例如“fromusername”、“touserid”等。我们将使用相应的值来自动登录App.xaml用户控件中的用户。“isinvited”键(第100行)仅表示您是邀请另一个用户私密聊天的人,而不是被邀请的人,isinvited=false;。我稍后会详细介绍我们如何使用此值。

    让我们跳转到App.xaml用户控件,看看这个过程是如何工作的。您会注意到,我们添加了一些公共属性来保存和存储我们的应用程序级值。通过查看下面的突出显示的的代码,您可以立即猜到,在用户列表中的Chatroom.xaml用户控件的HyperlinkButton(上面)中看到的查询字符串键/值对是直接相关的。

       17     public int UserID { get; set; }
       18     public string UserName { get; set; }
       19     public int ToUserID { get; set; }
       20     public string ToUserName { get; set; }
       21     public DateTime TimeUserJoined { get; set; }
       22     public int RoomID { get; set; }
       23     public string RoomName { get; set; }
       24     public bool IsInvited { get; set; }
       25     public DateTime TimeUserSentInviation { get; set; }

    下面的第64-69行从查询字符串中检索键/值对,然后将它们分配给App.xaml用户控件的公共属性。要加载PrivateChat用户控件而不是Login用户控件,我们只需检查URL中的至少一个值,表明该用户已打开私人聊天窗口。在这种情况下,我们只是检查第61行中的“fromusername”键。第43-53行确定加载哪个XAML用户控件。

       38     private void Application_Startup(object sender, StartupEventArgs e)
       39     {
       40         this.RootVisual = rootGrid;
       41 
       42         // check if it's a private chat request
       43         if (IsPrivateChatRequest())
       44         {
       45             // open private chat instead of login page
       46             rootGrid.Children.Add(new PrivateChat());
       47         }
       48         else
       49         {
       50             // start at the login page
       51             // this.RootVisual = rootGrid;
       52             rootGrid.Children.Add(new Login());
       53         }
       54     }
       55 
       56     private bool IsPrivateChatRequest()
       57     {
       58         Uri uri = System.Windows.Browser.HtmlPage.Document.DocumentUri;
       59         IDictionary<string, string> queryString = 
                     System.Windows.Browser.HtmlPage.Document.QueryString;
       60 
       61         if (uri.ToString().Contains("fromusername"))
       62         {
       63             // set all the app wide variables
       64             App app = (App)Application.Current;
       65             app.UserID = Convert.ToInt32(queryString["fromuserid"]);
       66             app.UserName = queryString["fromusername"];
       67             app.ToUserID = Convert.ToInt32(queryString["touserid"]);
       68             app.ToUserName = queryString["tousername"];
       69             app.IsInvited = Convert.ToBoolean(queryString["isinvited"]);
       70 
       71             try
       72             {
       73                 app.TimeUserSentInviation = 
                           Convert.ToDateTime(queryString["timeusersentinvitation"]);    
       74             }
       75             catch { } 
       76 
       77             return true;
       78         }
       79         else
       80         {
       81             return false;
       82         }
       83     }
  3. 响应私人聊天邀请。现在回到Chatroom.xaml用户控件。我们刚才讨论了打开私人聊天窗口以邀请他人私密聊天的过程。此时,您尚未发送邀请,只是打开了私人聊天窗口。您发送的第一个消息发送给其他用户时,就会发出邀请。稍后在本文的PrivateChat用户控件部分,我将讨论这个过程。目前,我将讨论被邀请者(您邀请的其他人)如何响应您的邀请。
  4. 我们在TimerControlTick事件中检查来自其他用户的私人消息邀请。在此事件中,我们调用GetPrivateMessages方法来检查任何邀请。这当然会像上面讨论的那样,通过客户端代理调用我们的WCF服务。

      261     void TimerTick(object sender, EventArgs e)
      262     {
      263         GetMessages();
      264         GetUsers();
      265         GetPrivateMessages();
      266     }
      267 
      268     private void GetPrivateMessages()
      269     {
      270         // get the private message invitations sent to me by other chatters
      271         LinqChatReference.LinqChatServiceClient proxy = 
                       new LinqChatReference.LinqChatServiceClient();
      272         proxy.GetPrivateMessageInvitesCompleted += new 
                    EventHandler<Silverlight2Chat.LinqChatReference.
                    GetPrivateMessageInvitesCompletedEventArgs>(
                    proxy_GetPrivateMessageInvitesCompleted);
      273         proxy.GetPrivateMessageInvitesAsync(_userID);
      274     }

    下面显示了获取私人消息邀请的实现,它与ILinqChatService接口的其他实现一起,在LinqChatService.svc服务中。

      275     List<PrivateMessageContract> ILinqChatService.GetPrivateMessageInvites(int toUserID)
      276     {
      277         List<PrivateMessageContract> pmContracts = new List<PrivateMessageContract>();
      278         LinqChatDataContext db = new LinqChatDataContext();
      279 
      280         var pvtMessages = from pm in db.PrivateMessages
      281                           where pm.ToUserID == toUserID
      282                           select new { pm.PrivateMessageID, pm.UserID, 
                      pm.User.Username, pm.ToUserID, pm.TimeUserSentInvitation };
      283 
      284         if (pvtMessages.Count() > 0)
      285         {
      286             foreach(var privateMessage in pvtMessages)
      287             {
      288                 PrivateMessageContract pmc = new PrivateMessageContract();
      289                 pmc.PrivateMessageID = privateMessage.PrivateMessageID;
      290                 pmc.UserID = privateMessage.UserID;
      291                 pmc.UserName = privateMessage.Username;
      292                 pmc.ToUserID = privateMessage.ToUserID;
      293                 pmc.TimeUserSentInvitation = privateMessage.TimeUserSentInvitation;
      294 
      295                 pmContracts.Add(pmc);
      296             }
      297         }
      298 
      299         return pmContracts;
      300     }

    每次邀请都会导致显示一个Silverlight Popup控件,如下图所示。

    Invitation to Chat Privately

    构建此Popup控件的每一项邀请并导致弹出的代码可以在下面的GetPrivateMessage Completed事件中找到。我知道您在想,“显示一个非常简单的弹出控件需要这么多代码吗?”答案是肯定的。更令人惊讶的是,每个按钮(关闭和立即聊天)都有自己的Click事件代码,因此代码会更长(第346行和第357行)。下面显示的只是在Popup控件的子控件Grid控件中添加了一个TextBlock控件,一个虚拟的聊天按钮控件,一个关闭按钮控件和一个聊天HyperlinkButton控件。Popup控件设置为显示或设置为可见,如第285行所示。

    虚拟的聊天按钮控件用作实际的聊天HyperlinkButton控件的“背景”控件,以便我们可以使HyperlinkButton控件看起来像一个按钮。这只是我的偏好,您也可以轻松地将HyperlinkButton控件的Content属性设置为显示一个按钮,甚至设计HyperlinkButton以显示成按钮。您可以看到,ButtonHyperlinkButton位于同一位置(第316行和第328行)。

      276     void proxy_GetPrivateMessageInvitesCompleted(object sender, 
                   Silverlight2Chat.LinqChatReference.GetPrivateMessageInvitesCompletedEventArgs e)
      277     {
      278         ObservableCollection<LinqChatReference.PrivateMessageContract> invitations = e.Result;
      279 
      280         foreach (var invitation in invitations)
      281         {
      282             Popup popUp = new Popup();
      283             Grid grid = new Grid();
      284             popUp.Child = grid;
      285             popUp.IsOpen = true;
      286             popUp.Name = "PopUpInvitation" + invitation.PrivateMessageID.ToString();
      287 
      288             // add popup to the root grid
      289             LayoutRoot.Children.Add(popUp);
      290 
      291             grid.Width = 200;
      292             grid.Height = 100;
      293             grid.HorizontalAlignment = HorizontalAlignment.Center;
      294 
      295             // pop-up border
      296             Border border = new Border();
      297             border.BorderBrush = new SolidColorBrush(Colors.Black);
      298             border.BorderThickness = new Thickness(2);
      299             border.CornerRadius = new CornerRadius(8);
      300             border.Background = new SolidColorBrush(Colors.White);
      301 
      302             // pop-up text
      303             App app = (App)Application.Current;
      304 
      305             TextBlock textBlock = new TextBlock();
      306             textBlock.Text = app.UserName  + " wants to chat privately.";
      307             textBlock.HorizontalAlignment = HorizontalAlignment.Center;
      308             textBlock.VerticalAlignment = VerticalAlignment.Top;
      309             textBlock.Margin = new Thickness(8);
      310 
      311             // accept button - background only
      312             Button btnAccept = new Button();
      313             btnAccept.Width = 100;
      314             btnAccept.Height = 24;
      315             btnAccept.HorizontalAlignment = HorizontalAlignment.Left;
      316             btnAccept.VerticalAlignment = VerticalAlignment.Bottom;
      317             btnAccept.Margin = new Thickness(8);
      318 
      319             // accept hyperlink - put on top of the accept button
      320             HyperlinkButton hpBtn = new HyperlinkButton();
      321             hpBtn.Name = "HbtnChatNow" + invitation.PrivateMessageID.ToString();
      322             hpBtn.Width = 100;
      323             hpBtn.Height = 22;
      324             hpBtn.Content = "     Chat Now    ";
      325             hpBtn.Foreground = new SolidColorBrush(Colors.Green);
      326             hpBtn.Background = new SolidColorBrush(Colors.Transparent);
      327             hpBtn.HorizontalAlignment = HorizontalAlignment.Left;
      328             hpBtn.VerticalAlignment = VerticalAlignment.Bottom;
      329             hpBtn.Margin = new Thickness(8);
      330 
      331             // build the absolute url
      332             Uri url = System.Windows.Browser.HtmlPage.Document.DocumentUri;
      333             string link = url.OriginalString;
      334             int lastSlash = link.LastIndexOf('/') + 1;
      335             link = link.Remove(lastSlash, link.Length - lastSlash) +
      336                 "Chatroom.aspx?fromuserid=" + app.UserID.ToString() +
      337                 "&fromusername=" + app.UserName +
      338                 "&touserid=" + invitation.UserID +
      339                 "&tousername=" + invitation.UserName +
      340                 "&isinvited=true" +
      341                 "&timeusersentinvitation=" + invitation.TimeUserSentInvitation.ToString();
      342 
      343             // build the hyperlink
      344             hpBtn.TargetName = "_blank";
      345             hpBtn.NavigateUri = new Uri(link);
      346             hpBtn.Click += new RoutedEventHandler(hpBtn_Click);
      347 
      348             // close button
      349             Button btnClose = new Button();
      350             btnClose.Name = "BtnClose" + invitation.PrivateMessageID.ToString();
      351             btnClose.Width = 50;
      352             btnClose.Height = 24;
      353             btnClose.Content = "Close";
      354             btnClose.HorizontalAlignment = HorizontalAlignment.Right;
      355             btnClose.VerticalAlignment = VerticalAlignment.Bottom;
      356             btnClose.Margin = new Thickness(8);
      357             btnClose.Click += new RoutedEventHandler(btnClose_Click);
      358 
      359             // add to grid
      360             grid.Children.Add(border);
      361             grid.Children.Add(textBlock);
      362             grid.Children.Add(btnAccept);
      363             grid.Children.Add(hpBtn);
      364             grid.Children.Add(btnClose);
      365 
      366             // delete private message invation from database
      367             LinqChatReference.LinqChatServiceClient proxy = new LinqChatReference.LinqChatServiceClient();
      368             proxy.DeletePrivateMessageAsync(invitation.PrivateMessageID);
      369         }
      370     }

    单击关闭按钮和立即聊天HyperlinkButton时,它们的功能几乎相同,如它们各自的Click事件所示。它们只是获取要关闭的Popup控件的名称,然后通过执行popUp.IsOpen = false来关闭它们。但是,立即聊天HyperlinkButton也会在浏览器中打开PrivateChat.xaml用户控件,这与我们之前打开新浏览器邀请某人私密聊天的方式非常相似。这是因为我们使用了与上面第332-341行所示几乎相同的代码,唯一的区别是:“isinvited”的值(第340行中为“true”,表示您是被邀请私密聊天的人),并且在第341行添加了另一个键/值对,“timeusersentinvitation”,它表示其他人向您发送私人聊天邀请的时间。我们将使用“timeusersentinvitation”的值来获取自对方发送聊天邀请以来的所有私人消息。我稍后会详细介绍。

      372     void hpBtn_Click(object sender, RoutedEventArgs e)
      373     {
      374         // get the name of the pop-up to close
      375         // based from the name of the hyperlink button
      376         HyperlinkButton button = sender as HyperlinkButton;
      377         string privateMessageID = button.Name.Replace("HbtnChatNow", "");
      378 
      379         Popup popUp = (Popup)LayoutRoot.FindName("PopUpInvitation" + privateMessageID);
      380         popUp.IsOpen = false;
      381     }
      382 
      383     void btnClose_Click(object sender, RoutedEventArgs e)
      384     {
      385         // get the name of the pop-up to close
      386         // based from the name of the button
      387         Button button = sender as Button;
      388         string privateMessageID = button.Name.Replace("BtnClose", "");
      389 
      390         Popup popUp = (Popup) LayoutRoot.FindName("PopUpInvitation" + privateMessageID);
      391         popUp.IsOpen = false;
      392     }

PrivateChat用户控件

Private Chat Window

如您所见,此控件看起来与Chatroom用户控件相同,除了省略了某些控件,例如标题、注销按钮等。并且由于相似性,发送消息或接收消息的方式相同,只是它们只发送给一个人。下面显示的是PrivateChat用户控件的GUI代码。

    1     <UserControl x:Class="Silverlight2Chat.PrivateChat"
    2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    4         Width="440" Height="280">
    5         <Grid x:Name="LayoutRoot" Background="White" 
                       ShowGridLines="False" Loaded="LayoutRoot_Loaded">
    6             <Grid.RowDefinitions>
    7                 <RowDefinition Height="10" />       <!-- padding -->
    8                 <RowDefinition Height="*" />        <!-- messages -->
    9                 <RowDefinition Height="10" />       <!-- padding -->       
   10                 <RowDefinition Height="26" />       <!-- message text box, send button -->
   11                 <RowDefinition Height="10" />       <!-- padding -->
   12             </Grid.RowDefinitions>
   13 
   14             <Grid.ColumnDefinitions>
   15                 <ColumnDefinition Width="10" />     <!-- padding -->
   16                 <ColumnDefinition Width="*" />      <!-- messages, message text box-->
   17                 <ColumnDefinition Width="10" />     <!-- padding -->
   18             </Grid.ColumnDefinitions>
   19 
   20             <ScrollViewer x:Name="SvwrMessages" Grid.Row="1" Grid.Column="1" 
   21                           HorizontalScrollBarVisibility="Hidden" 
   22                           VerticalScrollBarVisibility="Visible" BorderThickness="2">
   23                 <StackPanel x:Name="SpnlMessages" Orientation="Vertical" />
   24             </ScrollViewer>
   25 
   26             <StackPanel Orientation="Horizontal" Grid.Row="3" Grid.Column="1" >
   27                 <TextBox x:Name="TxtMessage" 
                           TextWrapping="Wrap" KeyDown="TxtMessage_KeyDown"  
   28                      ScrollViewer.VerticalScrollBarVisibility="Visible" 
   29                      ScrollViewer.HorizontalScrollBarVisibility="Disabled"
   30                      Width="280"
   31                      BorderThickness="2" Margin="0,0,10,0"/>  
   32 
   33                 <ComboBox x:Name="CbxFontColor" Width="60" Margin="0,0,10,0">
   34                     <ComboBoxItem Content="Black" Foreground="White" 
                             Background="Black" IsSelected="True" />
   35                     <ComboBoxItem Content="Red" Foreground="White" Background="Red" />
   36                     <ComboBoxItem Content="Blue" Foreground="White" Background="Blue" />
   37                 </ComboBox>
   38 
   39                 <Button x:Name="BtnSend" Content="Send" Click="BtnSend_Click" Width="60" />
   40             </StackPanel>
   41 
   42         </Grid>
   43     </UserControl>
  1. 邀请其他人进行私人聊天。当您单击Chatroom.xaml页面上的某个用户时,此私人聊天窗口将出现。要邀请某人进行私人聊天,您首先需要向该用户发送至少一条消息。第201-205行。
  2.   185     private void BtnSend_Click(object sender, RoutedEventArgs e)
      186     {
      187         SendMessage();
      188     }
      189 
      190     private void SendMessage()
      191     {
      192         if (!String.IsNullOrEmpty(TxtMessage.Text))
      193         {
      194             InsertMessage();
      195             GetPrivateMessages();
      196 
      197             // send an invitation to chat only if you're the one that
      198             // is sending the invitation, this means that you clicked
      199             // one of the users in the user list in the main chat
      200             // and did not click the invitation to chat pop up
      201             if (_isInvitationToChatSent == false && _isInvited == false)
      202             {
      203                 InviteOtherUserToChat();
      204                 _isInvited = true;
      205             }
      206         }
      207     }

    上面第203行调用的方法如下所示。因为我们不期望WCF服务(如下所示)返回任何内容,所以我们不需要调用InsertPrivateMessageInvite对象的Completed事件。简而言之,我们只需要调用此客户端代理的“Async”方法。

      214     private void InviteOtherUserToChat()
      215     {
      216         LinqChatReference.LinqChatServiceClient proxy = 
                       new LinqChatReference.LinqChatServiceClient();
      217         proxy.InsertPrivateMessageInviteAsync(_fromUserID, _toUserID);
      218     }

    上面第217行的Async WCF调用会调用LinqChatService.cs中找到的WCF服务方法。请注意,上面的异步调用和下面的方法具有相同的签名。下面的服务将在PrivateMessage表中插入一条记录。

      243     void ILinqChatService.InsertPrivateMessageInvite(int userID, int toUserID)
      244     {
      245         // first check if an invitation to chat has already
      246         // been sent to the particular user "toUserID"
      247         LinqChatDataContext db = new LinqChatDataContext();
      248 
      249         var count = (from pvtm in db.PrivateMessages
      250                      where pvtm.UserID == userID &&
      251                      pvtm.ToUserID == toUserID
      252                      select new { pvtm.PrivateMessageID }).Count();
      253 
      254         if (count == 0)
      255         {
      256             // no invitation was found
      257             PrivateMessage pm = new PrivateMessage();
      258             pm.UserID = userID;
      259             pm.ToUserID = toUserID;
      260             pm.TimeUserSentInvitation = DateTime.Now;
      261 
      262             db.PrivateMessages.InsertOnSubmit(pm);
      263 
      264             try
      265             {
      266                 db.SubmitChanges();
      267             }
      268             catch (Exception)
      269             {
      270                 throw;
      271             }
      272         } 
      273     }
  3. 发送私人消息。在聊天室中,我们获取所有用户正在聊天的消息。在这里,我们只应获取两个私密聊天用户之间的消息。这很容易解决。在Chatroom.xaml用户控件中,当我们向Message表中插入消息时,“_toUserID”设置为null。在这里,我们只需将该值设置为我们正在尝试聊天的用户。_toUserID值在此用户控件的构造函数中设置,该值从App.xaml的公共属性中检索。
  4.    19     DispatcherTimer timer;
       20     private bool _isTimerStarted;
       21     private bool _isWithBackground = false;
       22     private int _lastMessageId = 0;
       23     private int _fromUserID;
       24     private int _toUserID;
       25     private bool _isInvited;
       26     private bool _isInvitationToChatSent = false;
       27     private DateTime _timeUserSentInvitation;
       28 
       29     public PrivateChat()
       30     {
       31         InitializeComponent();
       32 
       33         App app = (App)Application.Current;
       34 
       35         if (String.IsNullOrEmpty(app.UserName))
       36         {
       37             app.RedirectTo(new Login());
       38         }
       39         else
       40         {
       41             _fromUserID = app.UserID;
       42             _toUserID = app.ToUserID;
       43             _isInvited = app.IsInvited;
       44 
       45             if (_isInvited)
       46                 _timeUserSentInvitation = app.TimeUserSentInviation;
       47             else
       48                 _timeUserSentInvitation = DateTime.Now;
       49         }
       50     }

    这使得发送消息给其他人非常简单。

       68     private void InsertMessage()
       69     {
       70         LinqChatReference.LinqChatServiceClient proxy = 
                     new LinqChatReference.LinqChatServiceClient();
       71         proxy.InsertMessageAsync(null, _fromUserID, _toUserID, 
                    TxtMessage.Text, CbxFontColor.SelectionBoxItem.ToString());
       72     }
  5. 获取私人消息。就像发送私人消息给其他人一样,我们需要填充_fromUserID_toUserID变量,以便只检索用于独占聊天用户的消息。我们从“邀请”用户首次发送私人聊天邀请时需要填充的另一条信息被赋给了_timeUserSentInvitation变量。这样,我们将只检索从那时开始的消息。
  6.    74     private void GetPrivateMessages()
       75     {
       76         LinqChatReference.LinqChatServiceClient proxy = 
                      new LinqChatReference.LinqChatServiceClient();
       77         proxy.GetPrivateMessagesCompleted += new 
                    EventHandler<Silverlight2Chat.LinqChatReference.
                    GetPrivateMessagesCompletedEventArgs>(proxy_GetPrivateMessagesCompleted);
       78         proxy.GetPrivateMessagesAsync(_timeUserSentInvitation, 
                    _lastMessageId, _fromUserID, _toUserID);
       79     }

    这里的GetPrivateMessagesCompleted事件的作用与Chatroom.xaml用户控件中的GetMessagesCompleted事件相同,因此我将不花时间解释它。有关Completed事件的更多信息,请参阅第一部分。上面的Async代理代码调用LinqChatService.svc.cs中找到的WCF服务GetPrivateMessages。我想强调的一点是构建查询的方式,特别是第68-69行中的代码。我们正在检索私密聊天用户之间发送的(“发送给”)任何一方的消息。

       62     List<MessageContract> ILinqChatService.GetPrivateMessages(DateTime 
                      timeUserSentInvitation, int messageID, int fromUserId, int toUserId)
       63     {
       64         LinqChatDataContext db = new LinqChatDataContext();
       65 
       66         var messages = (from m in db.Messages
       67                         where 
       68                         ((m.UserID == fromUserId && m.ToUserID == toUserId) || 
       69                         (m.ToUserID == fromUserId && m.UserID == toUserId)) && 
       70                         m.TimeStamp >= timeUserSentInvitation &&
       71                         m.MessageID > messageID 
       72                         orderby m.TimeStamp ascending
       73                         select new { m.MessageID, m.Text, 
                                               m.User.Username, m.TimeStamp, m.Color });
       74 
       75         List<MessageContract> messageContracts = new List<MessageContract>();
       76 
       77         foreach (var message in messages)
       78         {
       79             MessageContract messageContract = new MessageContract();
       80             messageContract.MessageID = message.MessageID;
       81             messageContract.Text = message.Text;
       82             messageContract.UserName = message.Username;
       83             messageContract.Color = message.Color;
       84             messageContracts.Add(messageContract);
       85         }
       86 
       87         return messageContracts;
       88     }
  7. 调整私人聊天窗口大小。在撰写本文时,Silverlight 2不幸地没有在指定大小打开新窗口的功能。请记住,我们仅使用一个ASP.NET页面托管所有XAML用户控件。因此,要在PrivateChat.xaml用户控件加载时动态调整此ASP.NET页面的大小,我们需要某种指示,某种将信息从XAML用户控件传递到ASP.NET页面的方法。幸运的是,我们可以编写一些JavaScript技巧来完成这项工作。使用JavaScript,我们可以检查Chatroom.aspx页面上至少一个查询字符串键/值对。在这种情况下,我们将检查“fromusername”键。如果该键存在,我们将窗口(Chatroom.aspx)的大小调整为640 x 460。
  8. 9 <script type="Text/javascript">
    10 window.onload = function() 
    11 {
    12 ResizeWindow();
    13 document.getElementById('Xaml1').focus();
    14 }
    15 
    16 function ResizeWindow() 
    17 {
    18 var fromusername = GetQueryString("fromusername");
    19 
    20 if (fromusername != null) 
    21 {
    22 // resize the screen
    23 window.resizeTo(640, 460);
    24 } 
    25 }
    26 
    27 function GetQueryString(variable) 
    28 {
    29 var query = window.location.search.substring(1);
    30 var vars = query.split("&");
    31 
    32 for (var i = 0; i < vars.length; i++) 
    33 {
    34 var pair = vars[i].split("=");
    35 
    36 if (pair[0] == variable)
    37 return pair[1];
    38 }
    39 }
    40 </script>
    

选择其他房间

您需要离开当前房间才能进入另一个房间。您可以单击Chatroom.xaml用户控件中带有“选择其他房间”文本的按钮。下面显示了Click事件。

  406     private void BtnChooseRoom_Click(object sender, RoutedEventArgs e)
  407     {
  408         timer.Stop();
  409         App app = (App)Application.Current;
  410 
  411         // leave the room to choose another room
  412         LinqChatReference.LinqChatServiceClient proxy = 
                 new LinqChatReference.LinqChatServiceClient();
  413         proxy.LeaveRoomAsync(_userID, _roomId, app.UserName);
  414 
  415         // redirect to the rooms page
  416         app.RedirectTo(new Rooms());
  417     }

上面的代理客户端方法调用下面的WCF服务方法。该方法与注销方法唯一的区别在于,这里我们只在LoggedInUser表中设置RoomID = null,而在注销方法中,我们完全从LoggedInUser表中删除该用户。

  194     void ILinqChatService.LeaveRoom(int userID, int roomID, string username)
  195     {
  196         // leave the room by setting room id = null
  197         LinqChatDataContext db = new LinqChatDataContext();
  198 
  199         var loggedInUser = (from l in db.LoggedInUsers
  200                             where l.UserID == userID
  201                             && l.RoomID == roomID
  202                             select l).SingleOrDefault();
  203 
  204         loggedInUser.RoomID = null;
  205         db.SubmitChanges();
  206 
  207         // insert user "left the room" text
  208         Message message = new Message();
  209         message.RoomID = roomID;
  210         message.UserID = userID;
  211         message.ToUserID = null;
  212         message.Text = username + " left the room.";
  213         message.Color = "Gray";
  214         message.TimeStamp = DateTime.Now;
  215 
  216         db.Messages.InsertOnSubmit(message);
  217         db.SubmitChanges();
  218     }

由于这是一个Web聊天应用程序,您当然可以打开多个浏览器,在每个浏览器中登录,然后同时进入不同的房间。

最后的话

希望您从本文中学到了东西。我没有讨论WCF(Windows Communication Foundation)的基础知识,也没有讨论聊天室消息的格式,因为这些以及其他内容可以在第一部分中找到。本文旨在学习使用Silverlight 2和MS SQL Server建立受监控的私人聊天的过程。有很多方面可以改进。例如,为了提高性能,您可以将所有数据库调用更改为使用存储过程而不是动态SQL。与其过于啰嗦,您还可以将数据库调用合并成一次操作,而不是多次操作,例如在Chatroom.xaml用户控件的TimerTick事件中找到的方法。

一如既往,代码和文章按“原样”提供,绝对没有任何保证。请自行承担使用风险。

注意:原文可以在这里找到:http://www.junnark.com/Articles/Build-a-Silverlight-Web-Chatroom-with-Multiple-Rooms-and-Private-Chat-Part-2.aspx

© . All rights reserved.