Создание приложения веб-чата на 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 }