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

в 20:50, , рубрики: .net, contract "search", windows, Windows 8, WinRT, контракт "поиск", Программирование, метки: , , , ,

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

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

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

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

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

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

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

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

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

Далее в классе приложения “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 где в качестве параметра указан поисковой запрос.

Надо учитывать, что если приложение не было запущено, приложение запускается через метод OnSearhActivated. В связи с этим текущий фрейм может быть еще не создан. Проверка на 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 контракта поиска позволяет нам добавлять контекстные подсказки при наборе поисковой фразы.
 Контекстные подсказки приложения могут работать только тогда, когда пользователь выберет (активирует) приложение для поиска.
     Windows 8. Контратк «поиск» в деталях

 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 позволяет нам реализовать более сложные виды контекстных подсказок:
      Windows 8. Контратк «поиск» в деталях

Предположим у нас есть некий слой логики (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)

Автор: Atreides07

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js