Пишем плагин для GStreamer на MS Visual Studio

Меня всегда интересовали прикладные задачи обработки видеоданных в реальном времени. На Хабре я прочитал серию статей о мультимедиа фреймвоке GStreamer:

Очень захотелось что-нибудь сделать с его использованием. Но, как обычно бывает, текущие задачи полностью исчерпывали ресурс свободного времени.

И вот однажды, в процессе работы над проектом, мне понадобилось организовать на объекте систему видеонаблюдения и интегрировать ее в систему учета. Эта задача была успешно решена специалистам нашей компанией и не достойна внимания широкой общественности. Система учета работает на Microsoft .NET, все камеры выдают H264 RTSP поток. Для захвата видео используется коммерческая библиотека MediaSuite от Streamcoders.

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

Итак, дано:

  • RTSP H264 источник
  • Система Windows 7 32-bit
  • MS Visual Studio 2010
  • Язык С++
  • GStreamer 1.0

Необходимо получить:

  • поток видео, содержащий результаты обработки

Способ решения:

  • Разработка плагина для GStreamer 1.0

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

Установка GStreamer

Загружаем и устанавливаем GStreamer gstreamer-1.0-ххх-1.2.4.msi. Его можно взять здесь.
Для разработки, нам также понадобится дистрибутив gstreamer-1.0-devel-xxx.msi например, gstreamer-1.0-devel-x86-1.2.4.msi, В процессе установки выбираем необходимые опции:

Забегая вперед, скажу, лучше будет установить Windows Device Driver Kit 7.1.0. По-умолчанию он ставится в C:\WinDDK\7600.16385.1 и именно там его будет искать Visual Studio при построении проекта. Если у вас уже установлен DDK по другому пути, это можно будет поправить потом, непосредственно в настройках проекта.

На сайте проекта GStreamer среди прочей документации есть руководство для разработчиков плагинов. Можно прочитав руководство написать весь код самому, но есть возможность скачать шаблон проекта плагина, из репозитория.

Шаблон содержит исходные файлы с++ и скрипт для генерации кода плагина. Как гласит инструкция, после развертывания шаблона из репозитория, необходимо перейти в директорий gst-template/gst-plugin/src и запустить утилиту ../tools/make_element. Утилита make_element имеет два параметра: имя плагина (dummy), имя исходного файла, который будет использован (gstplugin по-умолчанию).

В результате выполнения мы получим два файла: gstdummy.c и gstdummy.h. Внутри будет скелет плагина dummy, который еще глупый и нечего не делает, но уже может быть встроен в систему плагинов фреймвока.

Небольшая ремарка: все, что сказано выше, справедливо для Linux, Unix машин, а как быть скорбным обладателям Windows? Cmd.exe не станет выполнять make_element. Если заглянуть внутрь make_element станет ясно, что ничего сложного он не делает, а с помощью потокового редактора sed производит генерацию целевых исходников на основании данных ему параметров. Это можно сделать и самому. На всякий случай, я создал репозиторий, куда по ходу развития буду помещать свой тестовый проект: github.com/nostrum-service/gst.

После того, как мы проделали предварительную работу, настал черед формирования проекта непосредственно в MS Visual Studio 2010. К счастью, разработчики GStreamer позаботились о пользователях Visual Studio и поместили в дистрибутив все необходимое для создания проекта. Надо только правильно разместить файлы в каталогах Visual Studio.
Все необходимое лежит в директории gstreamer\1.0\x86\share\vs\2010.
Выполняем:

xcopy c:\gstreamer\1.0\x86\share\vs\2010\gst-template\*.* "C:\Program Files\Microsoft Visual Studio 10.0\VC\VCWizards\gst-template\*.*" /s /e /c xcopy C:\gstreamer\1.0\x86\share\vs\2010\wizard\*.* "C:\Program Files\Microsoft Visual Studio 10.0\VC\vcprojects\" 

Запускаем Visual Studio (или перезапускаем, чтобы она увидела новые настройки), создаем новый проект, и выбираем из установленных шаблонов Visual C++\gst-dk-template.

Если все прошло нормально, создастся пустой проект с необходимыми настройками. Поскольку, мы хотим создать плагин, идем в настройки проекта и меняем в Project Details Configuration Type с Application (.exe) на Dynamic Library (.dll).

В окне Property Manager наблюдаем следующую картину (включить Property Manager можно View->Other Windows->Property Manager):

В созданный пустой проект необходимо включить файлы, которые были созданы ранее с помощью утилиты make_element.

Компилируем, если все правильно, получаем готовый DLL, который надо скопировать в каталог плагинов GStreamer (у меня — C:\gstreamer\1.0\x86\lib\gstreamer-1.0\).
На всякий случай проверим: gst-inspect-1.0 dummy. Здесь мы увидим, что узнал GStreamer о нашем плагине.

Минимальный набор функций плагина

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

  • сообщить фреймвоку данные о себе (инициализировать класс)
  • произвести инициализацию экземпляра плагина
  • обработать процесс подключения потока
  • сделать что-то с входными данными и передать их на выход
  • по завершению очистить занятые ресурсы

Метаданные

За предоставление метаданных о плагине у нас отвечает функция
static void gst_dummy_class_init (GstdummyClass * klass)

В нашем примере элемент dummy имеет свойство Silent типа Boolean, отвечающее за вывод текста при обработке потока.

  gobject_class->set_property = gst_dummy_set_property;   gobject_class->get_property = gst_dummy_get_property;    g_object_class_install_property (gobject_class, PROP_SILENT,       g_param_spec_boolean ("silent", "Silent", "Produce verbose output ?",           FALSE, (GParamFlags)G_PARAM_READWRITE)); 

Этим кодом мы сообщаем среде GStreamer о том, что у плагина есть свойство Silent, оно имеет тип Boolean, за его установку отвечает делегат gst_dummy_set_property, чтение — gst_dummy_get_property, оно доступно по чтению и записи, значение по-умолчанию – FALSE. Далее мы регистрируем точки подключения к плагину – pads.

  gst_element_class_add_pad_template (gstelement_class,       gst_static_pad_template_get (&src_factory));   gst_element_class_add_pad_template (gstelement_class,       gst_static_pad_template_get (&sink_factory)); 

Определяем входной pad sink, который имеется всегда в наличие — GST_PAD_ALWAYS и принимает любой формат GST_STATIC_CAPS («ANY»).

static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",     GST_PAD_SINK,     GST_PAD_ALWAYS,     GST_STATIC_CAPS ("ANY")     ); 

Определяем выходной pad src, который имеется всегда в наличие — GST_PAD_ALWAYS и выдает любой формат GST_STATIC_CAPS («ANY»).

static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",     GST_PAD_SRC,     GST_PAD_ALWAYS,     GST_STATIC_CAPS ("ANY")     ); 

Инициализация экземпляра

В процессе построения pipeline, среда GStreamer вызывает функцию инициализации экземпляра плагина:

static void gst_dummy_init (Gstdummy * filter) 

на вход которой подается структура, которую необходимо заполнить:

struct _Gstdummy {   GstElement element;   GstPad *sinkpad, *srcpad;   gboolean silent; }; 

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

gst_pad_set_event_function (filter->sinkpad, GST_DEBUG_FUNCPTR(gst_dummy_sink_event)); 

Подключение к потоку

При возникновении событий на входе плагина, в нашем случае будет вызвана функция:

static gboolean gst_dummy_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) 

которая пока ничего не делает.

Пробный запуск

Теперь можно попробовать запустить — gst-launch-1.0 videotestsrc! dummy! autovideosink –v. Данная команда передает тестовый поток видео сгенерированный videotestsrc на наш плагин, который передает его дальше без изменений на видео проигрыватель autovideosink. Ключ v позволяет увидеть, как происходит обработка всей цепочки.

Если мы увидели тестовую картинку – значит наш плагин успешно передал данные со своего входа на выход.
Для нашего случая, вывод обработки будет содержать следующее:

/GstPipeline:pipeline0/GstVideoTestSrc:videotestsrc0.GstPad:src: caps = video/x-raw, format=(string)I420, width=(int)320, height=(int)240, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive 

Из этого следует, что экземпляр класса GstVideoTestSrc с именем videotestsrc0 предоставил pad src, выдающий поток video/x-raw в формате I420 размерами кадра 320 на 240 и т.д.
Поскольку наш плагин может принять любой формат, его вход связался с выходом videotestsrc0:

/GstPipeline:pipeline0/Gstdummy:dummy0.GstPad:sink: caps = video/x-raw, format=(string)I420, width=(int)320, height=(int)240, framerate=(fraction)30/1, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive 

Дальнейшее погружение

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

При добавлении нового плагина возникает проблема его регистрации, т.к. регистратор должен быть один на проект. В связи с этим, был введен файл GstTestLib.cpp, в котором размещен код регистрации обоих плагинов.

struct _elements_entry {   const gchar *name;     GType (*type) (void); };  static const struct _elements_entry _elements[] = {   {"dummy", gst_dummy_get_type},   {"painter", gst_painter_get_type},   {NULL, 0}, };  static gboolean plugin_init (GstPlugin * plugin) {   gint i = 0;    while (_elements[i].name) {     if (!gst_element_register (plugin, _elements[i].name,             GST_RANK_NONE, (_elements[i].type) ()))       return FALSE;     i++;   }    return TRUE; } 

Для painter я создал более жесткие ограничения на входной формат потока. Теперь это выглядит следующим образом:

static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", 	GST_PAD_SINK, 	GST_PAD_ALWAYS, 	GST_STATIC_CAPS ( GST_VIDEO_CAPS_MAKE ("{ BGRx }") ) 	); 

Это значит, что на вход может подаваться потоковое видео в формате BGRx с любым разрешением и частотой кадров. OpenCV по-умолчанию использует схему BGR. Про цветовые схемы и преобразования можно почитать здесь.
Поскольку, интересующая меня камера выдает RTSP H264 поток, нам необходимо его раскодировать и подать на вход преобразователя videoconvert. Наш тестовый пример будет выглядеть следующим образом:

gst-launch-1.0 -v rtspsrc location=rtsp://10.10.0.15 ! rtph264depay ! avdec_h264 ! videoconvert ! painter ! videoconvert ! autovideosink 

(10.10.0.15 – мой внутренний адрес, взят для примера).
Теперь на входе буфер у нас будет в BGRx формате. Если вы не знаете, как построить pipeline, можно воспользоваться универсальным контейнером decodebin. Он попытается сам подобрать и связать подходящие плагины:

gst-launch-1.0 -v rtspsrc location=rtsp://10.10.0.15 ! decodebin ! autovideosink 

ключ –v необходим для диагностического вывода, там и будет видно как сформирован pipeline.

Вернемся к нашему плагину. Вся наша обработка будет заключаться в формировании нового изображения на основе входного и отрисовке поверх него прямоугольника. Рабочая функция имеет вид:

static GstBuffer * 	gst_painter_process_data (Gstpainter * filter, GstBuffer * buf) { 	// объединяем все части буфера в непрерывную область для чтения 	GstMapInfo srcmapinfo; 	gst_buffer_map (buf, &srcmapinfo, GST_MAP_READ);  	// формируем новый буфер 	GstBuffer * outbuf = gst_buffer_new (); 	 	// создаем заголовок на исходное изображение 	IplImage * dst = cvCreateImageHeader (cvSize (filter->width, filter->height), IPL_DEPTH_8U, 4);  	// выделяем память для нового изображения 	GstMemory * memory = gst_allocator_alloc (NULL, dst->imageSize, NULL); 	GstMapInfo dstmapinfo; 	if (gst_memory_map(memory, &dstmapinfo, GST_MAP_WRITE)) {  		// копируем исходное изображение в новую область памяти 		memcpy (dstmapinfo.data, srcmapinfo.data, srcmapinfo.size); 		dst->imageData = (char*)dstmapinfo.data;  		// рисуем прямоугольник 		cvRectangle (dst, cvPoint(10,10), cvPoint(100, 100), CV_RGB(0, 255, 0), 1, 0);  		// добавляем память с модифицированным изображением в выходной буфер 		gst_buffer_insert_memory (outbuf, -1, memory);  		gst_memory_unmap(memory, &dstmapinfo); 	}  	cvReleaseImageHeader(&dst); 	 	gst_buffer_unmap(buf, &srcmapinfo);  	return outbuf; } 

В обработкчике событий от sink pad мы можем узнать информацию о входном потоке

static gboolean 	gst_painter_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) { 	gboolean ret; 	Gstpainter *filter;  	filter = GST_PAINTER (parent);  	switch (GST_EVENT_TYPE (event)) { 		case GST_EVENT_CAPS: 			{ 				GstCaps * caps;  				gst_event_parse_caps (event, &caps);  				//получаем структуру 				GstStructure *structure = gst_caps_get_structure (caps, 0);  				//читаем параметы 				gst_structure_get_int (structure, "width", &filter->width); 				gst_structure_get_int (structure, "height", &filter->height);  				filter->format = gst_structure_get_string (structure, "format");  				ret = gst_pad_event_default (pad, parent, event); 				break; 			} 	default: 		ret = gst_pad_event_default (pad, parent, event); 		break; 	} 	return ret; } 

Задача решена в лоб. Эксперимент удался. По мере осовения GStreamer, открываются новые возможности, о которых по мере моего продвижения и наличия интереса к теме я буду рассказывать дальше.

Источники

GStreamer
иерархия объектов GStreamer
руководство по разработке плагина GStreamer
OpenCV
репозиторий шаблона плагина
мои исходники

ссылка на оригинал статьи http://habrahabr.ru/post/221483/

Как я управляю разработкой CMS, используя 3SL Cradle. Общий взгляд

Это продолжение статьи «Как я управляю разработкой CMS, используя 3SL Cradle».
Начало (предыстория) здесь Как я управляю разработкой CMS (yii), используя 3SL Cradle. Предыстория

Итак, в предыдущий раз я рассказала контекст этого проекта. Сухой остаток таков:

  • этот проект целиком и полностью я выполняю одна, т.е. все, что будет описываться дальше может выполнять один человек,
  • этим проектом я не могу заниматься все время, реально это 2 месяца в году, поэтому мне очень важно иметь возможность быстро восстановить все проектные связи в голове, даже если я не занималась им полгода,
  • инструментально проект базируется на:
    — 3SL Cradle — разработка и управление требованиями, ошибками, запросами на изменение, задачами,
    — PHPStorm, MySQL Workbench — реализация,
    — git,github — управление версиями,
    — MS Project (редко) — прогнозирование сроков.

Продолжаем.

Общий взгляд на технологию

На следующей схеме я отразила основные задачи проектирования и соответствующие им типы проектных данных (или как сейчас говорят артефактов). В зависимости от конкретной задачи по CMS, я могу браться за карандаш (1), чтобы подумать над общей концепцией реализации задачи или сразу начать описывать процесс, который мне надо реализовать, с точки зрения пользователя (2). Может оказаться и так, что неожиданно придет мысль о некоторой полезной функции, которая дожна быть у системы, ее я тоже могу зафиксировать (4), даже если нет под нее процесса пользователя.

Т.е. эта схема отнюдь не фиксирует последовательность выполнения работ, не задает жесткий workflow (делай раз, два, три…). Я могу начать проектирование с любой удобной мне точки и разворачивать по мере вдохновения или доступности данных в любую сторону.

image

Как это выглядит?

А. Наброски на бумаге (1)

image

Б. Процессы и шаги пользователя (2)

image

При этом такие диаграмки система мне строит сама по этим деревьям (удобно, когда надоедают текстовые деревья)
image

В. Процессы и нефункциональные требования к ним (2)

image

Г. Шаги пользователя и ресурсы сайта (2) -> (3)

image

или так (тоже самое, но другой режим отображения)

image

Д. Сквозная трассировка от шагов пользователя до компонентов, которые их реализуют (2) -> (4) -> (6)

image

Это лишь некоторые рабочие срезы, которые я себе построила, есть еще масса других, которые используются в зависимости от ситуации.

Далее в зависимости от наличия интереса и вопросов могу описать те или иные детали, например, про управление статусами, базовые линии, связь требований с задачами и др.

ссылка на оригинал статьи http://habrahabr.ru/post/221465/

Как настроить доступ к Microsoft Azure через корпоративную учетную запись (Organizational Account) и включить мультифакторную аутентификацию

Для управления и доступа к ресурсам Azure изначально можно было использовать только Microsoft Account (LiveID), но какое-то время назад была добавлена поддержка учетной записи организации (Organizational Account). Organizational Account обслуживаются службой Azure Active Directory, а это обеспечивает расширенные функции управления данными учетными записями в рамках вашей организации (для которой создается выделенная Azure Active Directory). Например, Organizational Account, так же как и Microsoft Account (LiveID), поддерживают двухфакторную аутентификацию. Но для Organizational Account требование использовать мультифакторную аутентификацию может быть задано как обязательное, т.е. зайти на портал управления Azure пользователь сможет только выполнив все шаги двухфакторной верификации.

image
Мне достаточно часто задают вопрос относительно предоставления доступа к управлению системой, развернутой в Azure. Не всегда предоставление доступа на основе Microsoft Account (LiveID) для реальной системы подходит, т.к. администраторам сложнее контролировать как применяемые меры безопасности, так и права пользователей\сотрудников. Например, сотрудник может уволиться или его аккаунт будет взломан (далеко не все включают для своего аккаунта двухфакторную аутентификацию), в этом случае необходимо приостановить доступ по данной учтённой записи к облачной системе, чтобы никакие действия не могли быть выполнены ни через портал, ни через API.

Organizational Account как раз решает большинство вопросов (централизованное управление доступом, повышенные настройки безопасности и т.п.). Более подробная информация об управление аккаунтами и подписками в Azure представлена в MSDN статье Manage Accounts, Subscriptions, and Administrative Roles.

А далее будет пошаговая инструкция заведения Organizational Account и привязки его к Azure и включения для аккаунта двухфакторной аутентификации.

Organizational Account и привязки его в Azure

1. Зайдите на страницу Sign up for Azure as an organization и выберите Sign up now.

2. Далее будет предложено выполнить вход с использованием Microsoft Account (LiveID) или Organizational Account. Выбирайте Organizational Account.

3. Откроется страница, где будет предложено создать Organizational Account и определить Azure Active Directory. Поле DOMAIN NAME как раз определяет название Azure Active Directory (полное имя будет <указанное значение>.onmicrosoft.com), в которую будет добавлен создаваемый пользователь и которой вы сможете управлять в дальнейшем. В данном примере DOMAIN NAME определено как dxrussia (а полное имя как dxrussia.onmicrosoft.com, которое и является префиксом для полного имени пользователя).

Вам необходимо будет указать номер мобильного телефона для верификации, на указанный номер телефона придет СМС с кодом.
image

4. Далее будет предложено создать аккаунт в Azure на созданного на предыдущем шаге пользователя (natale@dxrussia.onmicrosoft.com).

Необходимо будет указать для верификации данные банковской карты и поставить галочку напротив I agree to the Windows Azure Agreement, Offer Details and Privacy Policy, если согласны с условиями использования Azure.
image

5. Собственно, вот и все — теперь созданы:

  • Учетная запись организации (Organizational Account)
  • Ваша Azure Active Directory
  • Аккаунт в Azure
  • Подписка Azure, привязанная к аккаунту.

image

Настройка двухфакторной аутентификации для Organizational Account

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

Примечание: включение мультифакторной аутентификации для учетных записей является платной услугой (см. пункт 10 в конце статьи).

1. Для этого необходимо зайти на портал управления Azure — https://manage.windowsazure.com.

Кстати, когда выполните вход, обратите внимание, что адрес будет иметь вид manage.windowsazure.com/<Название вашей Azure Active Directpory>.onmicrosoft.com. В данном примере это manage.windowsazure.com/dxrussia.onmicrosoft.com#Workspaces/All/dashboard.

2. Перейдите во вкладку Azure Active Directory, в секции Configure You Directory выберите пункт Enable Multi-Factor Authentication.
image

3. Теперь перейдите во вкладку Usersи нажмите кнопку Manage Multi-Factor Auth внизу страницы. Откроется страница с настройками двухфакторной аутентификации для пользователей вашей Azure Active Directory.

Включите двухфакторную аутентификацию (Enable) для пользователей.
image

4. Теперь снова зайдите на портал управления Azure, т.к. настройки безопасности были изменены, то система попросит определить удобный способ для дополнительной верификации учетной записи.
image

5. Собственно, предлагаю настроить =) По умолчанию выставлено “Позвоните мне”, я предпочла все-таки получать код через СМС.
image

6. Следующий шаг – это проверка работоспособности выбранного способа подтверждения. Мне на телефон пришла СМС с кодом.
image

7. Теперь еще раз заходим на портал. Вводим логин (natale@dxrussia.onmicrosoft.com) и пароль.
image

8. И теперь код, пришедший в СМС.
image

9. На портале теперь можно увидеть, что появился новый провайдер – Multi-Factor Auth Provider.
image

10. Еще раз обращу внимание, что включение мультифакторной аутентификации для учетных записей является платной услугой. Расценки приведены здесь. Сумма начисляется\списывается с общего счета за Azure ресурсы.

Есть два способа оплаты:

  • Per user (неограниченное количество аутентификаций для пользователя) – это $2\месяц на пользователя.
  • Per Authentication — $2\10 аутентификаций

Кроме того, двухфакторная аутентификация может использоваться при доступе пользователя и к другим ресурсам и приложениям, а не только для управления ресурсами Azure.
image

ссылка на оригинал статьи http://habrahabr.ru/company/microsoft/blog/221473/

Использование Yandex MapKit совместно с элементами управления Pivot и Panorama

Основная страница нашего приложения построена с использованием элемента управления Pivot, на одной из закладок которого необходимо было разместить карту с информацией о местоположении автомобиля. Пользователям нашего приложения было решено предоставить выбор между сервисами карт от компании Микрософт и компании Яндекс.

Интеграция встроенного сервиса карт не составило никакого труда, так как элемент управления для отображения карт является частью операционной системы, и его интеграция со всеми стандартными элементами управления отработана до мелочей.

Однако с интеграцией элемента управления для отображения карт входящего в Yandex.Map MapKit от компании Яндекс возникли неожиданные сложности. Попытки манипуляции картой в горизонтальной плоскости приводили к переключению текущей закладки элемента управления Pivot.

Изучение проблемы показало, что в Windows Phone 8 были произведены оптимизации обработки жестов. В результате чего, события о манипуляциях пользователя могут перехватываться встроенными элементами управления, до передачи их в дочерние элементы управления. Перехват событий осуществляют следующие элементы управления: DrawingSurface, DrawingSurfaceBackgroundGrid, ListBox, LongListSelector, Map, Panorama, Pivot, ScrollViewer, ViewportControl, WebBrowser.

К сожалению инструментарий Yandex.Maps MapKit больше не поддерживается компанией Яндекс, а последняя доступная версия разработана для Windows Phone 7.5 в которой указанные оптимизации отсутствуют.

Оптимизации операционной системы можно отключить установив значение свойства UseOptimizedManipulationRouting элемента управления в false. Но после установки значения свойства поведение приложения не изменилось. Все дело в том, что включив обработку жестов для элемента управления, так же необходимо обработать события: ManipulationStarted, ManipulationDelta и ManipulationCompleted. Нам нет необходимости обрабатывать жесты пользователя в этих обработчиках, так как этим занимается элемент управления Map, достаточно установить флаг Handled в true уведомив остальные элементы управления, что событие уже обработано.

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

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

using System.Windows; using Yandex.Maps;  namespace YandexMapKit { 	public static class YandexMapHelper 	{ 		public static readonly DependencyProperty FixManipulationProperty = DependencyProperty.RegisterAttached( 			"FixManipulation", typeof(bool), typeof(YandexMapHelper), new PropertyMetadata(OnFixManipulationChanged));  		public static void SetFixManipulation(DependencyObject element, bool value) 		{ 			element.SetValue(FixManipulationProperty, value); 		}  		public static bool GetFixManipulation(DependencyObject element) 		{ 			return (bool) element.GetValue(FixManipulationProperty); 		}  		private static void OnFixManipulationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 		{ 			var map = d as Map; 			if (map == null) 			{ 				return; 			}  			var fixManipulation = (bool?) e.NewValue;  			if (fixManipulation == true) 			{ 				map.UseOptimizedManipulationRouting = false; 				 				map.ManipulationStarted += MapManipulationStarted; 				map.ManipulationCompleted += MapManipulationCompleted; 				map.ManipulationDelta += MapManipulationDelta;  				return; 			}  			fixManipulation = (bool?)e.OldValue;  			if (fixManipulation == true) 			{ 				map.UseOptimizedManipulationRouting = true;  				map.ManipulationStarted -= MapManipulationStarted; 				map.ManipulationCompleted -= MapManipulationCompleted; 				map.ManipulationDelta -= MapManipulationDelta; 			} 		}  		private static void MapManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e) 		{ 			e.Handled = true; 		}  		private static void MapManipulationCompleted(object sender, System.Windows.Input.ManipulationCompletedEventArgs e) 		{ 			e.Handled = true; 		}  		private static void MapManipulationStarted(object sender, System.Windows.Input.ManipulationStartedEventArgs e) 		{ 			e.Handled = true; 		} 	} } 

Теперь можно в разметке страницы установить это свойство в true для элемента управления Map избежав написания лишнего кода.

<phone:Pivot Title="YANDEX MAPKIT TEST APP">             <phone:PivotItem Header="описание">                 <StackPanel>                     <RichTextBox VerticalAlignment="Top" VerticalContentAlignment="Top">                         <Paragraph>                             <Run Text="Приложение демонстрирует работу элемента управления Map из набора разработчика Yandex.Maps MapKit при его размещении на элемнете управления Pivot"/>                         </Paragraph>                     </RichTextBox>                     <CheckBox x:Name="viewFixManipulation" Content="Включить исправления" VerticalAlignment="Top" />                 </StackPanel>             </phone:PivotItem>                          <phone:PivotItem Header="карта">                 <Grid>                     <yandexMaps:Map yandexMapKit:YandexMapHelper.FixManipulation="{Binding IsChecked, ElementName=viewFixManipulation}" />                 </Grid>             </phone:PivotItem>         </phone:Pivot> 

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

Тестовый проект доступен на GitHub

ссылка на оригинал статьи http://habrahabr.ru/post/221477/