Блог Сергея Байдачного

Мой блог о технологиях

Archive for Апрель 2011

Введение в Silverlight 5 (часть 1)

with one comment

Закончилась очередная конференция MIX (http://live.visitmix.com), на которой, традиционно, была представлена новая версия технологии Silverlight – 5-я версия. Несмотря на то, что в Silverlight 5 beta доступны еще не все ожидаемые возможности, я, также традиционно, начну обзор существующих.

Начну с того, что Silverlight 5 beta можно скачать по ссылке http://www.silverlight.net/getstarted/silverlight-5-beta/. Тут доступны: видео презентации с обзором новых возможностей, статьи и инсталляция Silverlight 5 beta утилиты для Visual Studio 2010 SP1.

Обработка множественного нажатия кнопки мыши

Первая возможность, с которой мы начнем обзор, это поддержка обработки множественного нажатия кнопки мыши. Обычно требуется реализовать обработчик события при двойном щелчке, но Silverlight позволяет обрабатывать и большее количество нажатий. Речь идет как о левой, так и о правой кнопках мыши. Тут разработчики немного схитрили. Чтобы не менять модель событий и не вводить новые события на двойной щелчок мыши, к параметру EventArgs уже существующих событий, возникающих при однократном нажатии кнопки мыши, было добавлено свойство ClickCount. Это свойство и содержит количество нажатий.

Интересно то, что события MouseLeftButtonDown и MouseRightButtonDown срабатывают для каждого щелчка, даже если вы хотите обрабатывать только двойной щелчок. При этом, если между щелчками проходит больше секунды, или другое событие (например, пользователь еще и потянул мышь), то счетчик обнуляется. Поэтому, если Вы хотите для одного и того же объекта обрабатывать одинарный и двойной (а может и тройной) щелчок, то необходимо, чтобы они не были взаимоисключающими. Иными словами, при одинарном щелчке Вы можете выбрать папку, а при двойном – открыть папку. Наоборот работать не будет, так как уже при первом щелчке папка будет открыта.

Ниже показан простой код, который позволяет отобразить количество щелчков:

   1:  <Grid x:Name="LayoutRoot" Background="White" 
   2:     MouseLeftButtonDown="LayoutRoot_MouseLeftButtonDown">
   3:     <sdk:Label Height="28" HorizontalAlignment="Left" 
   4:        Name="label1" VerticalAlignment="Top" Width="120" />
   5:  </Grid>

 

Поддержка выбора элементов в ItemsControl с клавиатуры

Данный функционал достаточно простой, но приятный. Вспомните бесконечные элементы ComboBox со списком стран, или элементы, позволяющие установить фильтр по чьей-то фамилии, выбрав ее из сотни других и т. д. Для всех этих случаев, имея элемент-наследник от ItemsControl, такой как ListBox, ComboBox и другие, Вы всегда должны были пользоваться скроллингом, чтобы найти нужный элемент. Естественно, я имею в виду Silverlight. В WPF или даже в простом HTML, подобные элементы давно поддерживают возможность выбора с клавиатуры. Так, переходя на тот же выпадающий список стран, мне достаточно легко набрать на клавиатуре букву «У», чтобы увидеть активным элементом слово «Украина». Это позволяет быстро заполнять сложные формы с большим количеством опций. Теперь такая возможность появилась и в Silverlight. Рассмотрим простой пример:

   1:      <Grid x:Name="LayoutRoot" Background="White">
   2:          <ComboBox Height="23" HorizontalAlignment="Left" 
   3:              Name="comboBox1" VerticalAlignment="Top" Width="120">
   4:              <ComboBoxItem Content="Последний" />
   5:              <ComboBoxItem Content="Третий" />
   6:              <ComboBoxItem Content="Второй" />
   7:              <ComboBoxItem Content="Первый" />
   8:          </ComboBox>
   9:      </Grid>

 

На экране отобразится обычный ComboBox, который позволяет выбрать один из четырех элементов. Переключитесь на русскую клавиатуру и попробуйте понажимать символ «П». ComboBox будет последовательно выбирать один из двух элементов (Первый и Последний). Если Вы последовательно наберете два символа, например «Пе», то будет выбран «Первый» без альтернатив.

Понятно, что ComboBox в Silverlight, как и другие ItemsControl, позволяет отображать что угодно и не обязательно текст, особенно если речь идет о связывании со сложными объектами. Именно поэтому, для всех элементов ItemsControl ввели дополнительное свойство TextPath, которое задает имя свойства объекта, с которым мы реализуем связывание, используемое при выборе элемента с клавиатуры.

   1:  <ComboBox Height="23" TextSearch.TextPath="ImageTitle" 
   2:     HorizontalAlignment="Left" Name="comboBox1" 
   3:     VerticalAlignment="Top" Width="120">

Written by Sergiy Baydachnyy

14.04.2011 at 18:15

Опубликовано в SilverLight

Tagged with

Windows Phone 7: Разрабатываем приложение с использованием Push Notification (часть 1, веб-роль)

4 комментария

Этим постом я начинаю серию из 4-х статей, посвященных разработке решения для Windows Phone 7 с использованием Push Notification. Описанное тут решение мы разрабатывали для Украинского Медиа Холдинга. Между тем оно является типовым, хотя информацию по некоторым шагам найти достаточно сложно.

Начнем с описания задачи: партнер реализовал специальную службу, которая позволяет пользователям мобильных телефонов получать нотификацию о ходе прохождения футбольных игр, а также получать расписание ближайших игр, интересных для украинского зрителя. Доступ к службе осуществляется на основе платной подписки. Оплата взымается, в основном, за использование гейтов мобильных операторов для рассылки СМС сообщений.

Поскольку Windows Phone 7 обладает такой замечательной вещью, как Push Notification, то было принято решение, реализовать аналогичный функционал для устройств WP 7 на бесплатной основе. Ведь для использования Push Notification мобильный оператор не используется, а уведомления доставляются с помощью специальной Push Notification службы, которой управляет Microsoft.

Идея очень простая: клиент, используя приложение партнера, подписывается на службу доставки мгновенных сообщений. При этом клиент передает партнеру уникальный URL, позволяющий партнеру связаться с Notification службой и передать сообщение на конкретный телефон. Партнер, генерируя новое сообщение, опрашивает свою базу адресов, и по каждому из адресов отправляет сообщение в Push Notification службу, которая на основании уникального адреса нотифицирует конкретный телефон, передавая ему сообщение.

Таким образом, при разработке этого решения, нам необходимо следующие три компонента:

· Служба, которая позволяет получить новое сообщение от оператора и поставить его в очередь;

· Модуль (служба), который обрабатывает сообщения в очереди;

· Клиент для Windows Phone 7, позволяющий пользователю подписаться на службу и настроить параметры уведомлений;

Проблема состоит в том, что если Вы планируете отправлять множество сообщений и ожидаете большое количество подписчиков, то второй компонент будет достаточно сильно нагружен и должен предусматривать возможность масштабирования. Также механизм очереди должен поддерживать работу нескольких экземпляров службы по обработки элементов. Ведь при масштабировании проблема доступа нескольких служб к одному элементу обязательно возникнет.

Учитывая проблемы выше и список основных компонентов, было принято решение использовать технологию Windows Azure для создания подобного решения. В случае использования Windows Azure, первый компонент можно было бы реализовать в виде обычных WCF служб, для реализации очереди использовать соответствующий тип хранилища в Storage службе, а для компонента обработки элементов очереди можно использовать Рабочую роль. Последняя предназначена для работы в фоне и может регулярно опрашивать очередь на наличие новых элементов. Кроме того, Рабочая роль очень хорошо масштабируется.

Таким образом, 3 из 4 статей будут иметь достаточно опосредованное отношение к Windows Phone, а скорее относятся к Windows AzureJ

Начнем работу. На первом шаге создадим проект в Visual Studio на базе шаблона Windows Azure

image

В создаваемое решение добавим две роли, это WCF Service Web Role и Worker Role. Тут Вашим ролям можно дать более осмысленные названия (я не буду).

image

Про Рабочую роль сейчас забудем и сосредоточимся на реализации WCF службы.

Опишем интерфейс нашей службы следующим образом:

   1:      [ServiceContract]
   2:      public interface INotification
   3:      {
   4:          [OperationContract]
   5:          void SubscribePhoneToService(Guid guid, string pushServiceUri);
   6:   
   7:          [OperationContract]
   8:          SMSMessage[] RefreshData();
   9:   
  10:          [OperationContract]
  11:          void NewSMSMessage(string sms, string login, string password);
  12:      }
  13:   
  14:   
  15:      [DataContract]
  16:      public class SMSMessage
  17:      {
  18:          string date;
  19:          string message;
  20:   
  21:          [DataMember]
  22:          public string DateTimeValue
  23:          {
  24:              get { return date; }
  25:              set { date = value; }
  26:          }
  27:   
  28:          [DataMember]
  29:          public string MessageValue
  30:          {
  31:              get { return message; }
  32:              set { message = value; }
  33:          }
  34:      }

 

Тут описан класс SMSMessage, с помощью которого мы будем передавать данные между службой и телефонами, а также интерфейс INotification, описывающий три метода интерфейса службы. Тут определены следующие методы:

· SubscribePhoneToService – этот метод позволяет подписать телефон на сообщения, принимая уникальный ID телефона и адрес службы уведомлений для отправки сообщений данному устройству. Обратите внимание, что сообщения высылаются каждому подписанному телефону отдельно. Это дает возможность кастомизировать текст сообщения и фильтры, в зависимости от настроек. В данном случае какой-то специальной настройки для конкретного телефона мы не поддерживаем, поэтому метод принимает всего два параметра;

· RefreshData – этот метод необходим, если пользователь запустил приложение и хочет посмотреть последние сообщения;

· NewSMSMessage – этот метод инициируется оператором и служит для передачи сообщения в очередь. Поскольку мы не хотим, чтобы все желающие отправляли сообщение в очередь, метод кроме сообщения принимает логин и пароль оператора, используя примитивный способ аутентификации.

Данный интерфейс можно модифицировать, позволив пользователю кешировать часть сообщений на телефоне и передавать дату последнего доступного у него сообщения, чтобы обновлять интерфейс только новыми сообщениями. Сюда можно добавить языковую поддержку и другие параметры. Но сейчас это не столь важно.

Следующим шагом мы должны перейти к реализации интерфейса. Но это делать еще рано, так как нам необходимо еще хранилище для наших подписок и сообщений, а также где-то определить параметры приложения, такие как строка соединения с базой, логин и пароль оператора и т. д.

Первым делом создадим хранилище сообщений и подписок. Я решил использовать SQL Azure, так как меня больше устраивало реляционное хранилище. Данных тут не много, зато можно делать сложные выборки.

Для этого воспользуемся панелью управления Windows Azure и создадим новую базу

image

Тут же, в панели, можно увидеть строку подключения к базе, которую мы будем использовать из приложения. Вот пример такой строки:

Server=tcp:imc39784zf.database.windows.net,1433;Database=UkraineTest;User ID=sbaidachni@imc39784zf;Password=myPassword;Trusted_Connection=False;Encrypt=True;

Далее нажмите кнопку Manage и создайте две таблицы, позволяющие хранить подписки и сообщения.

image

image

Помните, чтобы тестировать подключения к базам в SQL Azure, вы должны через панель управления выполнить настройки firewall, которые позволят подключаться с Вашей девелоперской машины к базе.

Поскольку нам нужен также и Storage Service, то создадим и его. Для этого также воспользуемся панелью управления.

image

Подготовительная работа завершена.

Теперь вернемся в Visual Studio и приступим к написанию кода. Первым делом внесем изменения в конфигурацию решения.

Обратите внимание, что параметры приложения (строка соединения, логин, доступ к Storage аккаунту) нужно прописывать в csdef и cscfg файлах. Если Вы внесете настройки в Web.Config (как Вы привыкли делать в обычных веб-приложениях), то менять их, в процессе работы, у Вас не выйдет.

Поэтому перейдите к настройкам нашей веб-роли и введите необходимые параметры именно там.

image

Тут мы ввели Логин, Пароль, строку соединения к базе, а также строку соединения к Storage Account. Последняя может быть описана с помощью специальной панели:

image

Тут Вы указываете имя созданного Storage аккаунта и ключ, который можно взять в панели управления Windows Azure. Не забудьте поменять HTTPS на HTTP. Наши сообщения не представляют особого интереса, поэтому лишняя безопасность тут не требуется.

Последний параметры NRecords определяет, сколько сообщений мы хотим возвращать на телефон. Мы могли бы отдать эту настройку и пользователю, но я все равно хочу ограничить их количество сверху.

А теперь приступаем к реализации кода.

Первый метод, позволяющий подписаться на сообщения, будет выглядеть следующим образом:

   1:  public void SubscribePhoneToService(Guid guid, string pushServiceUri)
   2:  {
   3:      SqlConnection conn = new SqlConnection(RoleEnvironment.GetConfigurationSettingValue("DatabaseConnection"));
   4:      SqlCommand comm = new SqlCommand("select count(*) from PhoneTable where PhoneID=@par1", conn);
   5:   
   6:      comm.Parameters.Add(new SqlParameter("par1", guid.ToString()));
   7:   
   8:      conn.Open();
   9:   
  10:      int res = (int)comm.ExecuteScalar();
  11:   
  12:      if (res > 0)
  13:      {
  14:          SqlCommand comm1 = new SqlCommand("update phonetable set PhoneUrl=@par2 where PhoneID=@par1", conn);
  15:          comm1.Parameters.Add(new SqlParameter("par1", guid.ToString()));
  16:          comm1.Parameters.Add(new SqlParameter("par2", pushServiceUri));
  17:          comm1.ExecuteNonQuery();
  18:      }
  19:      else
  20:      {
  21:          SqlCommand comm1 = new SqlCommand("insert phonetable (PhoneID, PhoneUrl) values (@par1, @par2)", conn);
  22:          comm1.Parameters.Add(new SqlParameter("par1", guid.ToString()));
  23:          comm1.Parameters.Add(new SqlParameter("par2", pushServiceUri));
  24:          comm1.ExecuteNonQuery();
  25:      }
  26:   
  27:      conn.Close();
  28:      conn.Dispose();
  29:  }

 

Фактически, мы связываемся с базой, чтобы проверить наличие устройства. Если устройства нет, то мы вносим его URL в базу, если оно есть, то обновляем URL (он может измениться). Интерес представляет класс RoleEnvironment, позволяющий получить доступ к настройкам роли.

Второй метод, RefreshData, возвращает последние N сообщений из базы. Тут лучше использовать LINQ, чтобы уменьшить количество строк кода, но я сейчас не буду усложнять код технологиями.

   1:  SqlConnection conn = new SqlConnection(RoleEnvironment.GetConfigurationSettingValue("DatabaseConnection"));
   2:  string n = RoleEnvironment.GetConfigurationSettingValue("NRecords");
   3:   
   4:  SqlCommand comm = new SqlCommand("select TOP (" + n + ") * from messagetable order by messagedate desc", conn);
   5:   
   6:  conn.Open();
   7:   
   8:  SqlDataReader res = comm.ExecuteReader();
   9:   
  10:  List<SMSMessage> SMSMessages = new List<SMSMessage>();
  11:   
  12:  while (res.Read())
  13:  {
  14:      SMSMessages.Add(new SMSMessage() { MessageValue = res["MessageText"].ToString(), DateTimeValue = res["MessageDate"].ToString() });
  15:  }
  16:   
  17:  conn.Close();
  18:  conn.Dispose();
  19:   
  20:  return SMSMessages.ToArray();

 

А вот последний метод представляет наибольший интерес, так как именно тут идет работа с очередью.

   1:  if ((login.Equals(RoleEnvironment.GetConfigurationSettingValue("Login"))) && (password.Equals(RoleEnvironment.GetConfigurationSettingValue("Password"))))
   2:  {
   3:   
   4:      SqlConnection conn = new SqlConnection(RoleEnvironment.GetConfigurationSettingValue("DatabaseConnection"));
   5:      SqlCommand comm = new SqlCommand("insert messagetable (MessageText, MessageDate) values (@par1, @par2)", conn);
   6:      comm.Parameters.Add(new SqlParameter("par2", DateTime.Now.ToUniversalTime().ToString()));
   7:      comm.Parameters.Add(new SqlParameter("par1", sms));
   8:   
   9:      conn.Open();
  10:   
  11:      comm.ExecuteNonQuery();
  12:   
  13:      conn.Close();
  14:      conn.Dispose();
  15:   
  16:      CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
  17:      {
  18:          configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
  19:   
  20:          RoleEnvironment.Changed += (sender, arg) =>
  21:          {
  22:              if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>()
  23:                  .Any((change) => (change.ConfigurationSettingName == configName)))
  24:              {
  25:                  if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
  26:                  {
  27:                      RoleEnvironment.RequestRecycle();
  28:                  }
  29:              }
  30:          };
  31:      });
  32:   
  33:      CloudStorageAccount acc = CloudStorageAccount.FromConfigurationSetting("StorageConnection");
  34:      CloudQueueClient queueClient = acc.CreateCloudQueueClient();
  35:   
  36:      CloudQueue queue = queueClient.GetQueueReference("smsqueue");
  37:      queue.CreateIfNotExist();
  38:   
  39:      queue.AddMessage(new CloudQueueMessage(sms));
  40:  }

 

Тут мы вставляем новое сообщение в нашу таблицу, а также размещаем его в очереди. Для работы очереди мы использовали управляемую библиотеку Microsoft.WindowsAzure.StorageClient.dll, которая облегчает нам работу с REST API. Итак, тут мы создаем объект типа CloudStorageAccount, который будет ассоциироваться с нашим Storage аккаунтом в Azure. После чего создаем объект типа CloudQueue, который будет ассоциироваться с нашей очередью. И с помощью метода CreateIfNotExist создает новую очередь или получаем ссылку на существующую. После этого можно добавлять элементы.

Теперь необходимо реализовать рабочую роль, добавить поддержку https и написать клиента. Но об этом в следующей статье.

Written by Sergiy Baydachnyy

13.04.2011 at 12:12

Опубликовано в Windows Azure, Windows Phone

Tagged with , , ,