Отделенный код 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 }