Создание приложения веб-чата на Silverlight 2 - Отделенный код Chatroom.xaml.cs

ОГЛАВЛЕНИЕ

Отделенный код Chatroom.xaml.cs

1.    Когда пользователь перенаправляется на пользовательский управляющий элемент Chatroom.xaml из пользовательского управляющего элемента Login.xaml, проверяется вошел ли пользователь, путем проверки любого из значений, хранимых в App.xaml. Было решено проверить имя пользователя (строка 34). Если это значение пустое, то пользователь еще не вошел, и поэтому должен быть перенаправлен на страницу входа (строка 36).

32     App app = (App)Application.Current;
33
34     if (String.IsNullOrEmpty(app.UserName))
35     {
36         app.RedirectTo(new Login());
37     }
38     else
39     {
40         _userID = app.UserID;
41         _timeUserJoined = DateTime.Now;
42         TxtbLoggedInUser.Text = app.UserName;
43     }

2.    Так как Grid по имени "LayoutRoot" является корневым или главным контейнером для всех остальных управляющих элементов, событие LayoutRoot_Loaded вызывается при загрузке Grid. Оно имитирует событие Page_load страницы ASP.NET.

46     private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
47     {           
48         TxtMessage.Focus();
49         InsertNewlyJoinedMessage();
50         GetUsers();
51         SetTimer();
52     }

Как видно, при загрузке сетки происходит несколько вещей. Первое – активизация управляющего элемента TxtMessage, куда вводятся сообщения. Команда TxtMessage.Focus(), показанная выше в строке 48, не работает сама по себе. Надо сделать несколько вещей, чтобы активизировать этот управляющий элемент при загрузке сетки.

Первое, что надо сделать вместе с кодом TxtMessage.Focus(), - активизировать управляющий элемент Silverlight ASP.NET в главной веб-странице Chatroom.aspx. Как показано ниже, можно активизировать управляющий элемент Silverlight ASP.NET с помощью JavaScript.

<html xmlns="http://www.w3.org/1999/xhtml" style="height:100%;">
<head id="Head1" runat="server">
    <title>Silverlight 2 Chatroom</title>
    <script type="Text/javascript">
        window.onload = function ()
        {
            document.getElementById('Xaml1').focus();
        }
    </script>
</head>

<body style="height:100%;margin:0; padding:0; width: 100%;">
    <form id="form1" runat="server" style="height:100%;">
       <asp:ScriptManager ID="ScriptManager1" runat="server" />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <div style="width: 100%; text-align:center; height: 100%;">
                    <asp:Silverlight ID="Xaml1" runat="server"
                       Source="~/ClientBin/Silverlight2Chat.xap"
                       MinimumVersion="2.0.31005.0"
                       Width="600" Height="100%" />
                </div>
            </ContentTemplate>
        </asp:UpdatePanel>
    </form>
</body>
</html>

3.    Асинхронный доступ к службе WCF с помощью посредника: Все следующие методы и/или события в файле отделенного кода Chatroom.xaml.cs обращаются к созданной ранее службе WCF:

o    InsertNewlyJoinedMessage()
o    GetUsers()
o    InsertMessage()
o    GetMessages()
o    BtnLogOut_Click

Обратите внимание, что вся обработка производится в приложении Silverlight асинхронно. При получении значений, поступающих из службы WCF в приложение Silverlight, надо создать обработчик события "Completed" для события Completed  посредника WCF и вызвать метод "Async" посредника WCF. В качестве примере рассмотрен метод GetUsers() в Chatroom.xaml.cs.

Строка 57 и строки 61-68 не нужны, если не извлекаются никакие значения из службы WCF, как при вставке значения в базу данных. Однако в данном случае извлекаются пользователи, и извлеченные значения присваиваются в управляющих элементах Silverlight. При завершении вызова GetUsersAsync вызывается обработчик события GetUsersCompleted. Извлеченные пользователи (или извлеченное значение) затем присваиваются аргументам события e.Result обработчика события GetUserCompleted. Тип возвращаемой переменной e.Result – динамический, зависящий от извлекаемого значения. При извлечении целого значения это будет тип int; при извлечении строкового значения это будет тип string, и т.д. В данном случае извлекается коллекция, поэтому она присваивается типу ObservableColletion.
Сигнатура метода GetUsersAsync в строке 58 идентична сигнатуре метода ILinqChatService.GetUsers в службе WCF; так вызывается метод службы WCF.

54     private void GetUsers()
55     {
56         LinqChatReference.LinqChatServiceClient proxy =
                       new LinqChatReference.LinqChatServiceClient();
57         proxy.GetUsersCompleted +=
              new EventHandler<Silverlight2Chat.LinqChatReference.
              GetUsersCompletedEventArgs>(proxy_GetUsersCompleted);
58         proxy.GetUsersAsync(_roomId, _userID);
59     }
60
61     void proxy_GetUsersCompleted(object sender,
              Silverlight2Chat.LinqChatReference.GetUsersCompletedEventArgs e)
62     {
63         if (e.Error == null)
64         {
65             ObservableCollection<LinqChatReference.UserContract> users = e.Result;
66             ItmcUserList.ItemsSource = users;
67         }
68     }

Метод GetUsers вызывается в службе WCF (LinqChatService.svc.cs). Он вызвал GetUsers, и не был создан обработчик события GetUsersCompleted или метод GetUsersAsync.

62      List<UserContract> ILinqChatService.GetUsers(int roomID, int userID)

4.    Отправка и прием сообщений: Сообщения отправляются при нажатии клавиши «Ввод клавиатуры» или при нажатии кнопки «Отправить». При нажатии клавиши возврата каретки на клавиатуре или кнопки «Отправить» выполняются еще две вещи вместе с сохранением сообщения в базе данных: извлекаются сообщения из базы данных и извлекаются пользователи из базы данных.

209     private void SendMessage()
210     {
211         if(!String.IsNullOrEmpty(TxtMessage.Text))
212         {
213             InsertMessage();
214             GetMessages();
215             GetUsers();
216         }
217     }

Таймер устанавливается при загрузке главной Grid(сетка).

77     private void SetTimer()
78     {
79         timer = new DispatcherTimer();
80         timer.Interval = new TimeSpan(0, 0, 0, 3, 0);
81         timer.Tick += new EventHandler(TimerTick);
82         timer.Start();
83
84         _isTimerStarted = true;
85     }

Каждые 3 секунды, если не нажимается клавиша «Ввод клавиатуры», вызывается событие срабатывания таймера, чтобы извлечь сообщения и извлечь пользователей из базы данных.

219     void TimerTick(object sender, EventArgs e)
220     {
221         GetMessages();
222         GetUsers();
223     }

Таймер останавливается при каждом вводе сообщения и затем возобновляется при нажатии клавиши «Ввод». Это позволяет остановить таймер на основании обновления управляющего элемента TxtMessage при вводе с клавиатуры.

186     private void TxtMessage_KeyDown(object sender, KeyEventArgs e)
187     {
188         if (e.Key == Key.Enter)
189         {
190             SendMessage();
191             timer.Start();
192             _isTimerStarted = true;
193         }
194         else
195         {
196             if (_isTimerStarted)
197             {
198                 timer.Stop();
199                 _isTimerStarted = false;
200             }
201         }
202     }

5.    Установка полосы прокрутки внизу сообщений: Для установки полосы прокрутки внизу сообщений передается максимальное значение двойной точности в член ScrollToVerticalOffset управляющего элемента XAML просмотрщика прокрутки.

179     private void SetScrollBarToBottom()
180     {
181         // установить полосу прокрутки внизу
182         SvwrMessages.UpdateLayout();
183         SvwrMessages.ScrollToVerticalOffset(double.MaxValue);
184     }

6.    Демонстрация сообщений в управляющем элементе просмотра прокрутки: Пожалуй, это самая интересная часть руководства. На нее ушло больше всего времени при программировании приложения чата Silverlight 2. Получение всех сообщений и присвоение их в управляющий элемент ListBox XAML не оправдывает себя. Должна быть возможность делать разный цвет или оттенок для пользователя и сообщения в одной и той же строке. Также нужен чередующийся фон для каждого сообщения. Это достигается путем написания дополнительного кода для списка пользователей. При этом не приходится извлекать все сообщения для текущего раздела; извлекаются только еще не извлеченные сообщения, потому что при добавлении сообщения в управляющий элемент панели с помощью данного метода пользовательский интерфейс запоминает все, что было добавлено, и не надо добавлять их снова.

В строке 109 создается экземпляр горизонтальной панели-пачки, который будет добавлен в основную панель-пачку в строке 167. Это было сделано программно, так что можно чередовать цвет фона, как показано в строках 155-116. Также добавляется TextBlock, который будет хранить выделенное жирным имя пользователя в строке 130, и TextBox, который будет хранить сообщение в строке 164, для указанной панели-пачки. Это было весьма просто.

Почему используется TextBox (строка 133) вместо TextBlock для сообщения рядом с именем пользователя? В Silverlight есть следующая ошибка: при использовании события KeyDown поля сообщения возвраты каретки кодируются в сообщениях, вводимых при нажатии кнопки «Ввод». Это значит, что сообщения разбиваются на две строки в разных местах. Допустим, введено "Привет, как твои дела?" в текстовое поле сообщения, и нажата кнопка ввода. Сообщение "Привет, как твои дела?" разобьется на две строки при присвоении TextBlock значения Text поля сообщения, следовательно, оно должно читаться так:

Hello how are
you doing?

или так:

Hello 
how are you doing?

Нет управляющего элемента, куда вставляется символ перевода строки; он и не нужен. Выяснилось, что присвоение значения Text поля сообщения другому TextBox устраняет эту проблему.

100     void proxy_GetMessagesCompleted(object sender, 
             Silverlight2Chat.LinqChatReference.GetMessagesCompletedEventArgs e)
101     {
102         if (e.Error == null)
103         {
104             ObservableCollection<LinqChatReference.MessageContract>
                                                             messages = e.Result;
105
106             foreach (var message in messages)
107             {
108                 // добавить горизонтальную панель-пачку
109                 StackPanel sp = new StackPanel();
110                 sp.Orientation = Orientation.Horizontal;
111                 sp.HorizontalAlignment = HorizontalAlignment.Left;
112                 sp.Width = SpnlMessages.ActualWidth;
113
114                 // поместить чередующийся фон
115                 if (!_isWithBackground)
116                     sp.Background = new SolidColorBrush(
                             System.Windows.Media.Color.FromArgb(100, 235, 235, 235));
117
118                 // добавить TextBlock для хранения имени пользователя для панели-пачки
119                 TextBlock name = new TextBlock();
120                 name.Text = message.UserName + ": ";
121                 name.FontSize = 12.0;
122                 name.FontWeight = FontWeights.Bold;
123                 name.Padding = new Thickness(4, 8, 0, 8);
124
125                 if (message.Color == "Gray")
126                     name.Foreground = new SolidColorBrush(Colors.Gray);
127                 else
128                     name.Foreground = new SolidColorBrush(Colors.Black);
129
130                 sp.Children.Add(name);
131
132                 // добавить TextBox для хранения сообщения пользователя для панели-пачки
133                 TextBox text = new TextBox();
134                 text.BorderBrush = new SolidColorBrush(Colors.Transparent);
135                 text.FontSize = 12.0;
136                 text.Text = message.Text.Trim();
137                 text.VerticalAlignment = VerticalAlignment.Top;
138                 text.Width = SpnlMessages.ActualWidth - name.ActualWidth;
139                 text.TextWrapping = TextWrapping.Wrap;
140                 text.Margin = new Thickness(0, 4, 4, 0);
141                 text.IsReadOnly = true;
142
143                 // изменить цвет текста исходя из выбранного пользователем цвета
144                 if(message.Color == "Red")
145                     text.Foreground = new SolidColorBrush(Colors.Red);
146                 else if (message.Color == "Blue")
147                     text.Foreground = new SolidColorBrush(Colors.Blue);
148                 else if (message.Color == "Gray")
149                     text.Foreground = new SolidColorBrush(Colors.Gray);
150                 else
151                     text.Foreground = new SolidColorBrush(Colors.Black);
152
153                 // поместить чередующийся фон
154                 if (!_isWithBackground)
155                 {
156                     text.Background = new SolidColorBrush(
                            System.Windows.Media.Color.FromArgb(100, 235, 235, 235));
157                     _isWithBackground = true;
158                 }
159                 else
160                 {
161                     _isWithBackground = false;
162                 }
163
164                 sp.Children.Add(text);
165
166                 // добавить горизонтальную панель-пачку к основной панели-пачке
167                 SpnlMessages.Children.Add(sp);
168
169                 // запомнить идентификатор последнего сообщения
170                 _lastMessageId = message.MessageID;
171             }
172
173             SetScrollBarToBottom();
174             TxtMessage.Text = String.Empty;
175             TxtMessage.Focus();
176         }
177     }

7.    Выход (разрегистрация): При нажатии кнопки выхода останавливается таймер (строка 227). Далее удаляется пользователь из таблицы LoggedInUser путем вызова службы WCF в строке 229-230. Обработчик события Completed не вызывается, так как не извлекаются никакие значения из базы данных. И наконец, пользователь перенаправляется на пользовательский управляющий элемент входа XAML.

Как сказано ранее, можно разрегистрировать пользователя, когда он нажимает кнопку закрытия браузера, путем перехвата события unload тега body в главной странице ASP.NET.

225     private void BtnLogOut_Click(object sender, RoutedEventArgs e)
226     {
227         timer.Stop();
228
229         LinqChatReference.LinqChatServiceClient proxy =
                      new LinqChatReference.LinqChatServiceClient();
230         proxy.LogOutUserAsync(_userID, _roomId, TxtbLoggedInUser.Text); 
231
232         // перенаправить на страницу входа
233         App app = (App)Application.Current;
234         app.RedirectTo(new Login());
235     }