Friday, February 11, 2011

Nokia + Windows Phone. Это случилось.

Несомненно, новость дня и отличная новость для Windows Phone - сегодня Nokia и Microsoft объявили о стратегическом партнерстве.

Вот что произойдет в его рамках:
  • Nokia будет использовать Windows Phone в качестве смартфон-платформы;
  • Сервисы Bing и Ad Center будут использоваться в телефонах Nokia;
  • Nokia Maps будут основным решением для мобильных карт в Windows Phone, а также будут интегрированы в карты Bing;
  • Магазин контента и приложений Ovi Store переедет в Marketplace. 
Полный текст пресс-релиза

    Saturday, February 5, 2011

    Январское обновление Windows Phone Developer Tools

    Вчера в Windows Phone Developer Blog был объявлен выход январского обновления инструментов для разработки под Windows Phone 7. Давайте посмотрим, что нового нас ждёт.

    Обновление Windows Phone Developer Tools включает в себя:

    • Обновление эмулятора Windows Phone - Добавлена функциональность копирования/вставки в эмулятор Windows Phone. Стоит заметить, что в реальных девайсах этой функциональности пока нет (она будет включена в обновление операционной системы, которое должно быть буквально на днях).
    • Обновление инструментов разработки - исправлен баг в выделении текста в контролах Pivot и Panorama. Если в вашем существующем приложении есть текстбоксы в содержимом панорамы или пивота, при копировании текста пользователи могут случайно перейти на другую вкладку. Microsoft рекомендует перекомпилировать приложение, используя обновлённые инструменты и перезалить его в Marketplace.
    • Windows Phone Capability Detection Tool - инструмент для определения возможностей приложения (например, использует ли оно микрофон или службу определения местоположения). Больше информации: MSDN (на английском языке).
    • Windows Phone Connect Tool - позволяет проводить отладочные сессии без запущенного Zune. Зачем это нужно, можно почитать в MSDN или в одном из моих будущих постов.
    • Обновление Silverlight-контрола Bing Maps - улучшена производительность жестов при использовании карт.
    Загрузить обновление

    От себя хочу добавить, что обновление получилось довольно формальным. Смею предположить, что где-то в районе Mobile World Congress (середина февраля) нас ждёт гораздо большее обновление инструментов разработки вместе с обновлением системы для телефонов.

    Monday, January 31, 2011

    TS: Silverlight 4, Development

    Сегодня в почту наконец-то пришли результаты бета-экзамена по Silverlight 4, который я сдавал ещё в октябре. Экзамен я сдал, что добавляет ещё один сертификат в копилку.

    Интересно, что когда экзамен был в бета-стадии, указывалось, что сертификация по SL4 является предусловием для мистического MCPD: Windows Phone 7. Сейчас эта информация с сайта убрана, но будем надеяться, что такой экзамен всё-таки будет и эту, без сомнения, чудесную сертификацию можно будет добавить в резюме.

    Thursday, January 27, 2011

    Моя презентация с вчерашнего выступления на Lviv .net User Group

    Пожалуй, пора снова вернуться к блогу. Простите за столь долгое моё отсутствие!

    Вчера я выступал с докладом по Windows Phone 7 на львовской .net-юзергруппе, и хочу предложить вашему вниманию презентацию с доклада.

    Thursday, June 10, 2010

    Перехват сообщений Windows в WPF-приложении. Аналог WndProc.

    Проблема

    Для второй версии монитора буфера обмена (с которой мы будем разбираться в следующем посте) мы создадим WPF-приложение. Однако, у Windows Presentation Foundation есть одно существенное отличие от WinForms — в WPF-форме такой код из первой версии работать не будет:
    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_DRAWCLIPBOARD:
                // содержимое буфера обмена изменилось
                {
                    // реагируем на изменение буфера
                    ClipboardChanged();
                    // пересылаем сообщение следующему окну в цепочке
                    SendMessage(nextClipboardViewer, WM_DRAWCLIPBOARD, IntPtr.Zero, IntPtr.Zero);
                    break;
                }
            case WM_CHANGECBCHAIN:
                // цепочка приложений, подписанных на WM_DRAWCLIPBOARD изменилась
                {
                    if (m.WParam == nextClipboardViewer)
                    {
                        // окно, которому мы передавали WM_DRAWCLIPBOARD, удалено из
                        // цепочки, и нам нужно обновить переменную
                        nextClipboardViewer = m.LParam;
                    }
                    else
                    {
                        // просто передаем новости дальше
                        SendMessage(nextClipboardViewer, WM_CHANGECBCHAIN, m.WParam, m.LParam);
                    }
                    m.Result = IntPtr.Zero; // уведомляем систему об обработке сообщения
                    break;
                }
            default:
                // поведение для остальных сообщений - по умолчанию
                {
                    base.WndProc(ref m); 
                    break;
                }
        }
    }
    
    Аналогом класса Form в Windows Forms является класс Window, в котором нет метода WndProc, который мы могли бы переопределить. Возвращаемся в Windows Forms? Нет, решение всё-таки существует.

    Решение

    Для решения нашей задачи предназначен замечательный класс HwndSource, который находится в пространстве имен System.Windows.Interop. HwndSource — класс, который представляет Win32-окно, в котором находится контент WPF. Именно им нужно будет пользоваться, если нам нужно вызывать какие-либо функции WinAPI, которые требуют дескриптор окна в качестве одного из параметров. Также в экземпляре класса HwndSource есть два метода, которые позволяют получить доступ к сообщениям Windows:
    public void AddHook(HwndSourceHook hook);
    public void RemoveHook(HwndSourceHook hook);
    
    Эти методы позволяют соответственно установить и удалить функцию-перехватчик. Сигнатура HwndSourceHook выглядит так:
    public delegate IntPtr HwndSourceHook(
     IntPtr hwnd, // дескриптор окна
     int msg, // сообщение Windows
     IntPtr wParam, // параметры
     IntPtr lParam, // сообщения
     ref bool handled // обработано ли сообщение
    )
    
    Чтобы создать экземпляр класса HwndSource, мы можем воспользоваться его конструктором и создать новое окно, сообщения которому мы и будем перехватывать. Однако, можно сделать и по-другому. Поскольку HwndSource является наследником класса PresentationSource, достаточно будет воспользоваться статическим методом PresentationSource.FromVisual(Visual) с нашим главным окном в качестве параметра и привести результат к типу HwndSource.
    В этом моменте я допустил ошибку, вызвав FromVisual в конструкторе формы. В момент вызова окно ещё не было полностью инициализировано, и результатом стало исключение. После этой попытки я переопределил вызов OnSourceInitialized и завершил инициализвцию HwndSource в нём.

    Код

    Полный код перехвата сообщений Windows выглядит так:
    public partial class MainWindow : Window
    {
        private const int WM_DRAWCLIPBOARD = 0x0308;
        private HwndSource hwndSource;
    
        // функция-перехватчик
        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == WM_DRAWCLIPBOARD)
            {
                // обрабатываем сообщение
            }
            return IntPtr.Zero;
        }
    
        public MainWindow()
        {
            InitializeComponent();   
        }
    
        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);
            // создаем экземпляр HwndSource
            hwndSource = PresentationSource.FromVisual(this) as HwndSource; 
            // и устанавливаем перехватчик
            hwndSource.AddHook(WndProc);
        }
    }
    
    HwndSource Class (System.Windows.Interop)
    HwndSourceHook delegate (System.Windows.Interop)

    Monday, June 7, 2010

    Странные ошибки и ExternalException при работе с буфером обмена

    В комментарии к предыдущей записи Омен подбросил задачку:
    … предлагаю для решения простую задачку: посмотреть, как работает пример из этого поста при запущенной вот этой маленькой программке. Выяснить, что и почему происходит, и как с этим бороться.
    Чур без рефлектора:)
    Ну что же, давайте разбираться. Грузим архив, открываем приложение. Видим обычную форму без каких-либо компонентов. Что же с ней не так? В этот момент я отвлекся на сообщение в скайпе, а когда попытался скопировать оттуда ссылку, вот что получилось:
    Получается, наш TestClipboard.exe каким-то образом запорол доступ к буферу обмена (причём, не только для скайпа — чтобы сделать скриншот выше, тест-приложение пришлось закрыть). Как он это сделал? Можно, конечно, посмотреть рефлектором, но нас же просили этого не делать — поэтому пойдем слегка обходным путём:
    1. У меня уже была открыта Visual Studio с каким-то проектом, и я банально нажал F11, чтобы перейти в режим отладки. Открытый проект совершенно не важен, потому как нам будет нужен Immediate Window. Давайте попробуем из него получить текст из буфера обмена:
      Clipboard.GetText()
    2. Студия, как и скайп раньше, не может получить доступ к буферу обмена, только в этот раз у нас куда больше информации:
    3. Параметр ErrorCode — как раз то, что нам нужно. Поскольку под классом Clipboard находятся нативные функции Windows, мы будем смотреть, что не так у них. Конвертируем -2147221040 в hex, получаем 0x800401d0. Поискав число в MSDN или заглянув в winerror.h, узнаём, что числу соответствует константа CLIPBRD_E_CANT_OPEN.
    4. Давайте посмотрим теперь, какая функция WinAPI вернула нам такой результат. В описании константы в MSDN это указано:
      0x800401D0 CLIPBRD_E_CANT_OPEN
      OpenClipboard failed.
      Итак, «виновница» торжества — функция WinAPI OpenClipboard.
    В процессе расследования мы спустились с вершин дотнета в старое доброе WinAPI. И вот как в нём устроена работа с буфером обмена:
    1. Приложение вызывает функцию OpenClipboard для получения доступа к буферу обмена. С этого момента доступ к буферу закрыт для всех остальных приложений.
    2. Приложение вызывает либо функцию GetClipboardData для получения данных, либо EmptyClipboard для его очистки и SetClipboardData для записи данных в буфер обмена.
    3. Приложение закрывает буфер обмена при помощи функции CloseClipboard.
    Итак, после первого пункта больше никто, кроме приложения, не может получить доступ к буферу. Если вызвать OpenClipboard и не закрывать буфер (что и делает наше тестовое приложение), то можно отобрать доступ к буферу у всех остальных (не делайте так, пожалуйста). Также работа с буфером может вызвать исключение, если в момент вызова какое-то приложение находится на пункте 2 работы. Это довольно редкая ситуация, но её стоит учитывать — поэтому работа с буфером всегда должна обрабатывать ExternalException. Мы учтём это в следующей версии нашего монитора буфера обмена.

    OpenClipboard Function (Windows)
    Clipboard (Windows)

      Tuesday, June 1, 2010

      Уведомление о изменении буфера обмена (Clipboard) Windows в С#, часть 1

      Введение

      Пожалуй, буфер обмена — одно из найболее часто используемых функций в Windows, да и в других операционных системах. Сегодня мы взглянем на то, как при помощи.net Framework и нескольких API-функций Windows создать простое приложение, которое будет следить за буфером обмена и сообщать нам о изменении его содержимого.

      Класс Clipboard

      В .net-приложении буфер обмена Windows доступен в любой момент при помощи класса
      System. Windows. Forms. Clipboard. Для получения данных, которые находятся в буфере, предназначены четыре метода:
      public static string Clipboard.GetText();
      public static string Clipboard.GetText(TextDataFormat format);
      public static Object Clipboard.GetData(string format);
      public static IDataObject Clipboard.GetDataObject();
      
      Найболее простой из них — первый метод, который получает текстовые данные из буфера обмена. Если в буфере не текстовые данные (а, например, изображение), то результатом будет пустая строка.
      Метод GetText (TextDataFormat format) отличается от собственной перегрузки без параметров тем, что позволяет получить текст из буфера обмена в определенном формате (всего их пять — Text, UnicodeText, Rtf, Html и CommaSeparatedValue). Опять же, если в буфере обмена не текстовые данные, либо данные не запрошенного формата, результатом вызова метода будет пустая строка.
      Для проверки того, содержит ли буфер текст нужного формата, существуют следующие методы:
      public static bool ContainsText();
      public static bool ContainsText(TextDataFormat format);
      
      Синтаксис их вызова идентичен методам GetText, но результатом будет true, если буфер обмена содержит текст заданного формата и false в обратном случае.
      Последние два Get-метода предназначены для получения объекта определенного формата из буфера (о других форматах мы поговорим в следущей части статьи) и имеют соответствующие Contains-методы.

      Уведомления об изменении содержимого буфера обмена

      Что нужно сделать, чтобы узнать, когда содержимое буфера изменилось? Простейшее решение — воспользоваться таймером и периодически опрашивать буфер на предмет изменения. Это не лучшее решение — содержимое буфера может измениться несколько раз между итерациями таймера, а слишком частый опрос может быть губительным для производительности.
      В этой задаче нам на помощь приходят три WinAPI-функции:
      //Register a window handle as a clipboard viewer
      [DllImport("User32.dll", CharSet = CharSet.Auto)]
      public static extern IntPtr SetClipboardViewer(IntPtr hWnd);
      //Remove a window handle from the clipboard viewers chain
      [DllImport("User32.dll", CharSet = CharSet.Auto)]
      public static extern bool ChangeClipboardChain(
          IntPtr hWndRemove,  // handle to window to remove
          IntPtr hWndNewNext  // handle to next window
          );
      [DllImport("user32.dll", CharSet = CharSet.Auto)]
      public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
      
      Функция SetClipboardViewer регистрирует окно для получения сообщения WM_DRAWCLIPBOARD (о нём ниже), ChangeClipboardChain предназначена для удаления идентификатора окна из цепочки получения WM_DRAWCLIPBOARD, а SendMessage — функция WinAPI для отправки сообщений Windows другим окнам.
      Сообщение WM_DRAWCLIPBOARD рассылается системой при изменении содержимого буфера обмена. С помощью функции SetClipboardViewer мы подпишем наше приложение в цепочку получателей этого сообщения. Небольшой сложностью этого подхода будет то, что наше приложение должно будет передать сообщение дальше по цепочке — адрес окна, которому нужно будет передать сообщение, вернет функция SetClipboardViewer.
      Кроме обработки WM_DRAWCLIPBOARD, нашему приложению придётся обрабатывать также и сообщение WM_CHANGECBCHAIN. Система рассылает это сообщение, когда исключает одно из окон из цепочки рассылки.

      Время для кода

      Итак, откроем Visual Studio, создадим новый проект типа Windows Forms Application, и приступим к работе. Создадим простой интерфейс для приложения:
      Кнопки Start и Stop Monitoring будут соответственно регистрировать окно нашего приложения в цепочке уведомлений и удалять его оттуда, а в текстовое поле будет выводиться изменения в буфере и прочая диагностическая информация. Для этого будет предназначена вспомогательная функция Output:
      private void Output(string message)
      {
          tbOutput.Text += message + Environment.NewLine; // добавить наше сообщение
          tbOutput.SelectionStart = tbOutput.Text.Length; // установить курсор в конец текста
          tbOutput.ScrollToCaret(); // прокрутка до курсора для показа сообщения
      }
      
      Добавим переменную, в которой будем хранить дескриптор окна, которому будем передавать сообщения WM_DRAWCLIPBOARD и WM_CHANGECBCHAIN:
      private IntPtr nextClipboardViewer;
      
      Добавим обработчики событий для кнопок:
      private void btnStart_Click(object sender, EventArgs e)
      {
          nextClipboardViewer = SetClipboardViewer(this.Handle);
          Output("Registered clipboard viewer.");
      }
      
      private void btnStop_Click(object sender, EventArgs e)
      {
          ChangeClipboardChain(this.Handle, nextClipboardViewer);
          Output("Unregistered clipboard viewer.");
      }
      
      Переопределим функцию WndProc, чтобы обработать пришедшие нам сообщения:
      protected override void WndProc(ref Message m)
      {
          switch (m.Msg)
          {
              case WM_DRAWCLIPBOARD:
                  // содержимое буфера обмена изменилось
                  {
                      // реагируем на изменение буфера
                      ClipboardChanged();
                      // пересылаем сообщение следующему окну в цепочке
                      SendMessage(nextClipboardViewer, WM_DRAWCLIPBOARD, IntPtr.Zero, IntPtr.Zero);
                      break;
                  }
              case WM_CHANGECBCHAIN:
                  // цепочка приложений, подписанных на WM_DRAWCLIPBOARD изменилась
                  {
                      if (m.WParam == nextClipboardViewer)
                      {
                          // окно, которому мы передавали WM_DRAWCLIPBOARD, удалено из
                          // цепочки, и нам нужно обновить переменную
                          nextClipboardViewer = m.LParam;
                      }
                      else
                      {
                          // просто передаем новости дальше
                          SendMessage(nextClipboardViewer, WM_CHANGECBCHAIN, m.WParam, m.LParam);
                      }
                      m.Result = IntPtr.Zero; // уведомляем систему об обработке сообщения
                      break;
                  }
              default:
                  // поведение для остальных сообщений - по умолчанию
                  {
                      base.WndProc(ref m); 
                      break;
                  }
          }
      }
      
      И последнее, что осталось сделать — реализовать метод ClipboardChanged:
      private void ClipboardChanged()
      {
          Output("The clipboard content has been changed.");
          string s = "New clipboard content: ";
          if (Clipboard.ContainsText()) // будем выводить только текстовое содержимое
          {
              s += Clipboard.GetText();
          }
          else
          {
              s += "[non-text]";
          }
          Output(s);
      }
      
      Готово! Монитор буфера обмена готов к работе:
      Обратите внимание, что при регистрации монитора Windows тут же присылает нам сообщение о изменении буфера — что-то вроде «добро пожаловать» :)
      В следующей части мы рассмотрим работу с другими форматами данных буфера обмена, и пропагрейдим наш монитор до менеджера буфера обмена — с историей и прочими полезными функциями.
      Исходник проекта (Visual Studio 2008)
      SetClipboardViewer Function (Windows)
      Clipboard Class (System. Windows. Forms)