Windows 8. Контракт «поиск» в деталях

    Поиск внутри приложений одна из новых, наиболее важных и интересных функций Windows 8. Поиск предоставляет возможность искать не только файлы и документы на устройстве, но также позволяет искать внутри установленных приложений.

    В этой статье рассматривается:
    1. Интеграция поиска в метро приложение
    2. Контекстные подсказки (suggestions).
    3. Обработка запроса по мере ввода данных.
    4. Неосторожное использование контракта поиска (обычные ошибки интеграции).

    1. Интеграция поиска в метро приложение

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

    Самым простым способом добавления поддержки контракта поиска является использование готового шаблона в студии:



    В VS2012 есть встроенный шаблон SearchContract который добавляет соответствующую запись в манифест (если этого не было сделано), добавляет запись в app.xaml.cs(переопределение метода OnSearchActivated если этого еще не было сделано), и добавляет страницу принимающий поисковой запрос с минимальной логикой обработки.

    Мы не будем детально рассматривать использования этого шаблона и пошагово рассмотрим добавление поддержки контракта поиска:

    В первую очередь в манифесте приложения (файл Package.appxmanifest) нам необходимо указать, что наше приложение поддерживает контракт поиска. Для этого открыв этот манифест в студии, во вкладке “declarations” добавляем контракт поиска.


    Далее в классе приложения “app.xaml.cs” мы можем переопределить алгоритм активации приложения через контракт поиска и указать нужную нам логику. Для простоты сделаем, что бы у нас всегда открывалась отдельная страница (SearchPage.xaml) при активации по контракту поиска.

            protected override void OnSearchActivated(SearchActivatedEventArgs args)
            {
                var frame = Window.Current.Content as Frame;
                if(frame==null)
                {
                    frame=new Frame();
                    Window.Current.Content = frame;
                }
                
                frame.Navigate(typeof(SearchPage), args.QueryText);
                Window.Current.Activate();
            }
    


    В этом методе мы перенаправляем пользователя на страницу SearchPage где в качестве параметра указан поисковой запрос.

    Надо учитывать, что если приложение не было запущено, приложение запускается через метод OnSearchActivated. В связи с этим текущий фрейм может быть еще не создан. Проверка на null осуществляется для инициализации фрейма при первом запуске приложения.

    На странице SearchPage теперь мы можем отобразить результаты поиска, получив поисковой запрос из переданного параметра:
           protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                var query = (string)e.Parameter;
                SearchByQuery(query);
            }
    
            private void SearchByQuery(string queryText)
            {
                pageTitle.Text = "Search result for \"" + queryText + "\":";
                //Логика обработки поискового запроса
            }
    


    2. Контекстные подсказки (suggestions).

    API контракта поиска позволяет нам добавлять контекстные подсказки при наборе поисковой фразы.
     Контекстные подсказки приложения могут работать только тогда, когда пользователь выберет (активирует) приложение для поиска.
         

     2. 1. Текстовые подсказки

    Добавить поддержку довольно просто, в конструкторе страницы мы можем подписаться на событие запроса контекстных подсказок.
          
            public SearchPage()
            {
                this.InitializeComponent();
                SearchPane.GetForCurrentView().SuggestionsRequested += SearchPage_SuggestionsRequested;
            }
    


    Далее в методе SearchPage_SuggestionsRequested мы можем добавить необходимые контекстные подсказки:

            void SearchPage_SuggestionsRequested(SearchPane sender, SearchPaneSuggestionsRequestedEventArgs args)
            {
                var products=new[] { "apple", "cheese", "bread", "onion", "orange", "potato"}
                args.Request.SearchSuggestionCollection.AppendQuerySuggestions(products);
            }
    


    Теперь, если мы в конструкторе подпишемся на событие выбор запроса:

            SearchPane.GetForCurrentView().QueryChanged += SearchPage_QueryChanged;

    Мы сможем обрабатывать выбор пользователя из подсказок.
            void SearchPage_QueryChanged(SearchPane sender, SearchPaneQueryChangedEventArgs args)
            {
                SearchByQuery(args.QueryText);
            }


    2.2. Сложные подсказки

    API SearchContract позволяет нам реализовать более сложные виды контекстных подсказок:
          



    Предположим у нас есть некий слой логики (LogicLayer) который возвращает нам продукт с полями:
        public class Product
        {
            public string Name { get; set; }
            public string Image { get; set; }
            public string Description { get; set; }
            public string Id { get; set; }
        }
    


    Теперь в методе SearchPage_SuggestionsRequested мы должны использовать метод args.Request.SearchSuggestionCollection.AppendResultSuggestion для добавления более сложных подсказок.
            void SearchPage_SuggestionsRequested(SearchPane sender, SearchPaneSuggestionsRequestedEventArgs args)
            {
                foreach (var product in LogicLayer.SearchProduct(args.QueryText))
                {
                    var image = RandomAccessStreamReference.CreateFromUri(newUri(product.Image));
                    args.Request.SearchSuggestionCollection.AppendResultSuggestion(product.Name, product.Description, product.Id, image, String.Empty);
                }
            }
    


    Здесь мы в качестве тегов передаем Id продукта. При выборе пользователем сложных подсказок не срабатывает событие QueryChanged. Для обработки сложных подсказок есть специальное
    событие ResultSuggestionChosen.

    Мы можем добавить в конструктор обработку этого события.

            SearchPane.GetForCurrentView().ResultSuggestionChosen += SearchPage_ResultSuggestionChosen;

    Теперь мы можем получить ссылку на идентификатор продукта и обработать его:
            void SearchPage_ResultSuggestionChosen(SearchPane sender, SearchPaneResultSuggestionChosenEventArgs args)
            {
                var id = args.Tag;
                SearchById(query);
            }
    

    2.3. Контекстные подсказки на всех страницах.
     
    В небольших приложениях скорее всего будет проще сделать контекстные подсказки не отдельно для каждой страницы,  а одну подсказку для всего приложения.
     
    Мы можем подписаться на события поиска в экземпляре приложения. При этом, так как приложение может быть активировано несколько раз, мы должны быть уверены что подпишемся только один раз.
     
    Добавим метод SubscribeToSearchEvents в App.xaml.cs 
     
            private bool isSubscribed;
            public void SubscribeToSearchEvents()
            {
                if (!isSubscribed)
                {
                    isSubscribed = true;
                    SearchPane.GetForCurrentView().SuggestionsRequested += SearchPage_SuggestionsRequested;
                    SearchPane.GetForCurrentView().ResultSuggestionChosen += SearchPage_ResultSuggestionChosen;
                    SearchPane.GetForCurrentView().QueryChanged += SearchPage_QueryChanged; 
                }
            }
    


     
    Теперь достаточно вызвать его в методах OnLaunched и OnSearchActivated
     
            protected override void OnLaunched(LaunchActivatedEventArgs args)
            {
                SubscribeToSearchEvents();
                ...
             }


     
            protected override void OnSearchActivated(SearchActivatedEventArgs args)
            {
                SubscribeToSearchEvents();
            }


     
    3. Обработка запроса по мере ввода данных.

    Если нам требуется реализовать еще более сложные сценарии подсказок, или если мы можем быстро обработать запрос и сразу показать результат поискового запроса до завершения ввода, мы можем подписаться на события изменения поискового запроса (QueryChanged).

    Для этого можем добавить в конструктор следующий метод:

    SearchPane.GetForCurrentView().QueryChanged += SearchPage_QueryChanged;

    Аналогично остальным событиям мы можем обработать поисковый запрос:
          void SearchPage_QueryChanged(SearchPane sender, SearchPaneQueryChangedEventArgs args)
          {
                   SearchByQuery(args.QueryText);
          }



    4. Исправляем типичные ошибки интеграции поиска:

    Описанные здесь рекомендации относиться к версии Windows 8 RP и наверняка будут исправлены в релизе. Тем не менее сейчас есть несколько «симптом» присущих практически всем приложениям (включая встроенные системные приложения, такие как «Люди»).

    4.1. Потеря возможности попасть на главную страницу.

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

    Исправить эту проблему достаточно просто. В методе активации приложения по поиску (OnSearchActivated) при инициализации фрейма мы также добавим лишний переход на главную страницу.
            protected override void OnSearchActivated(SearchActivatedEventArgs args)
            {
                var frame = Window.Current.Content as Frame;
                if(frame==null)
                {
                    frame=new Frame();
                    Window.Current.Content = frame;

                    //Переход на главную страницу
                    frame.Navigate(typeof(MainPage));
                }           
                frame.Navigate(typeof(SearchPage), args.QueryText);
                Window.Current.Activate();
            }

     4.2. Многочисленные экземпляры страницы поиска

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

    Одно из решений довольно простое. При активации приложения по поиску проверять, является ли текущая страница страницей поиска и возвращаться назад в таком случае:
            protected override void OnSearchActivated(SearchActivatedEventArgs args)
            {
                RemoveSearchPage();
                var frame = Window.Current.Content as Frame;
                if(frame==null)
                {
                    frame=new Frame();
                    Window.Current.Content = frame;
                    frame.Navigate(typeof(MainPage));
                }
                
                frame.Navigate(typeof(SearchPage), args.QueryText);
                Window.Current.Activate();
            }


    Соответственно реализация метода RemoveSearchPage:
            private void RemoveSearchPage()
            {
                var frame = Window.Current.Content as Frame;
                if (frame== null)
                {
                    return;
                }
                            
                var page=frame.Content as Page;
                if (page == null)
                {
                    return;
                }
    
                if (page.GetType()==typeof(SearchPage))
                {
                    frame.GoBack();
                }
            }

     
    Исходный код (288,51 kb)

    P.S. Спасибо хабраюзеру Стасу Павлову за ценные замечания при подготовке статьи.
    Поделиться публикацией

    Комментарии 18

      +3
      Исправьте в заголовке «контракт»
        0
        Спасибо, что исправили
          +7
          Спасибо большое! Но все таки о таких мелких ошибках лучше пишите в личку :)
            +4
            Не знал, извините. Если так и продолжал минусовать, то наверное и комментариев больше писать не смогу)
              0
              Наоборт — от меня вам плюс. Надеюсь не потеряете возможность комментировать — добавил плюс в карму.
              +1
              Ох уж эти грабли…
            0
            А какие еще новые фичи появились? Где можно почитать про это? Поиск не чего не подсказал…
            Если сделаете еще пару подобных статей, будет просто замечательно !)
              +1
              Постараюсь! У меня уже готовы черновики статей и скринкасты по контрактам «настройки» и «шаринг данных». Есть еще наброски статьи по работе с сокетами (регистрация подключения для работы в свернутом состоянии). Надеюсь довести все до чистовых состояний и выложить статьи и видео в течении недели-двух.
                0
                Будем ждать. Было бы интересно.
                А то есть желания попробовать написать под 8, но пока не видел смысла и чего то интересного в этом. Теперь думаю стоит этим заняться.
                  0
                  На самом деле стоит попробовать писать под Windows 8. Достаточно много интересных особенностей. Особенно просто будет разработчикам с WP7 — концепции очень похожие. Также, если Вам интересно, один из моих черновиков видео контракта «поиск»
                0
                Если в формате видео, а не статей, то можно посмотреть обзорные доклады на Windows 8 Camp и более углублённые найти в докладах DevCon'12.

              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  Да, было.
                  Но, search provider был спроектирован для того, что бы интегрировать корпоративные базы данных на файловой основе а так же подключать не стандартные хранилища. Так же для реализации этого провайдера нужно было хорошенько поразмыслить.

                  А представленный поиск — это поиск по ФУНКЦИОНАЛЬНЫМ возможностям.
                  — Ау, программы, кто из вас рисовать умеет?
                  — Я и я, отозвались программы в поиске.

                  Или реализовать защищённое хранилище контактов, когда программа сама отвечает за их хранени и защиту, но при этом контакты могут попадать в Outlook и так далее.

                  В Google Chrome я видела что-то подобное в настройках.
                  Удобно, но симантика всё ещё на плечах разработчиков.
                  –1
                  многим нужны нормальные функции поиска. Например в файлах с расширением .js которые лежат в папке c:\myProjects\scripts\ найти строку getCurrency.

                  Начиная с Windows Vista встроенный поиск этого сделать больше не позволяет. Пришлось ставить Total Commander для поиска в файлах.

                  Пистец это называется. Улучшатели.
                    0
                    Windows Search Index Advanced Options

                    Добавте в индекс свою директорию, расширение файла и укажите что индексировать нужно ещё и контент.
                    А если уж совсем у вас будет не стандартный контент, то MSDN к вашим услугам — пишите провайдер для типа данных.
                      0
                      windows-search-index-advanced-options
                        –3
                        вы понимаете что пишете? Это элементарная функция. Она обязана работать из коробки (как работало раньше).

                        А если у меня произвольные расширения файлов?
                        А если я пишу на большом количестве языков?
                        Я должен тратить своё время на восстановление нормального функционала который зачем-то убрали?
                        0
                        Total Commander один из моих рабочих инструментов, который я запускаю пожалуй чаще чем Visual Studio.
                        Но все таки в статье речь идет о совсем другой концепции поиска. Вместо концепции поиска файлов и документов переход в сторону поиска информации. К примеру по запросу «рим» выбрав погодное приложение мы можем посмотреть прогноз погоды, выбрав приложение карт посмотреть карту рима и/или географическое расположение, выбрав приложение «путешествия» посмотреть достопромечательности Рима и т.д.

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

                      Самое читаемое