Как сделать историю в браузере wpf
задан 08 марта '10, 04:03
Как я уже сказал . Я кодер-любитель . пожалуйста, помогите мне понять класс MRUManager и то, что он делает. Спасибо. - Gagan
Вы проверили статью кода проекта, на которую я ссылался? - Shoban
Вы видели слово WPF в OP? - Robin Davies
6 ответы
JumpList в WPF4 это превосходно. Это все, что мне нужно было сделать:
ответ дан 12 мая '10, 06:05
Что такое список переходов для WinForm? - ПозвониМеЛаНН
@kmc Поскольку JumpList (показанный код) отображает список последних использованных документов, вы webby;) - Сергей Алдухов
Возможно, мне что-то не хватает, но это просто создает список переходов и говорит, что нужно включить категорию «Недавние» (это список переходов по умолчанию, который вы все равно получаете бесплатно). Это ничего не делает для отображения недавних элементов в приложении? - Ник
Это инициализирует список переходов, но ничего с ним не делает. По крайней мере, нужно также вызывать, например, AddToRecentCategory() и Apply() всякий раз, когда файл должен быть записан как недавний. Кроме того, эта информация доступна только на панели задач Windows, но не в самом приложении. - Мкьельдсен
Это довольно приятная прогулка и образец
Хорошо, что в нем есть и xml-файл, и хранилище реестра.
ответ дан 14 мар '11, в 03:03
Моя идея решить эту проблему (как новичок) заключалась в том, чтобы сохранить все пути к файлам в Очереди заданной максимальной емкости и добавить их во время выполнения в menuItem .
ответ дан 02 апр.
Вы можете просто вести список документов, которые открывает пользователь. Сохраняйте список при выходе из программы и загружайте его при запуске. Вы могли бы, вероятно, сохранить список вещей в настройках программы или записать его в файл (обычный текст или xml подойдут).
Вам нужно будет создать подменю для «недавних документов» динамически, сохранив ссылку на «недавние документы». MenuItem , затем добавление и удаление MenuItem с его Items коллекция. Здесь обсуждается это: Добавить новый элемент меню в меню во время выполнения.
Библиотека, на которую ранее ссылался Shoban, выглядит как набор классов, которые делают это за вас. Но это для winforms. Если вы используете wpf, вам, возможно, придется написать свой собственный (хотя, вероятно, где-то есть готовые варианты), но winforms даст вам хорошую отправную точку.
ответ дан 23 мая '17, 11:05
-> чтобы включить функциональность jumplist и меню запуска недавних файлов, я использовал процедуру оболочки Windows API следующим образом:
[DllImport ("shell32.dll")] // процедура оболочки для включения списка переходов и повторных файлов public static extern void SHAddToRecentDocs (UInt32 uFlags, [MarshalAs (UnmanagedType.LPWStr)] String pv);
и назовите его так: SHAddToRecentDocs (0x00000003, mFilePath);
-> Затем, чтобы отобразить меню последних файлов, я использовал XML-файл, сохранил в нем последние файлы, проанализировал и отобразил последний файл в меню.
Создан 19 июля '10, 09:07
Вы могли бы быть заинтересованы в писатель образец приложения Платформа приложений WPF (WAF). Он показывает, как использовать и реализовать список последних файлов, который отображается в меню файлов и на начальной странице.
ответ дан 22 мая '11, 09:05
Приложения браузера XAML (XBAP) объединяют функции веб-приложений и полнофункциональных клиентских приложений. Как веб-приложения, XBAP можно развертывать на веб-сервере и запускать из Internet Explorer или Firefox. Как многофункциональные клиентские приложения, XBAP могут использовать все возможности WPF. Кроме того, XBAP разрабатываются аналогично многофункциональным клиентским приложениям. Этот раздел содержит простое, общее введение в разработку XBAP и показывает, чем она отличается от разработки стандартных многофункциональных клиентов.
Этот раздел состоит из следующих подразделов.
Создание приложения обозревателя XAML (XBAP)
Проще всего создать проект XBAP с помощью Visual Studio. При создании нового проекта выберите из списка шаблонов приложение браузера WPF. Дополнительные сведения см. в разделе Практическое руководство. Создание нового проекта приложения обозревателя WPF.
При запуске проект XBAP откроется в окне браузера, а не в отдельном окне. При отладке XBAP в Visual Studio приложение выполняется с разрешениями зоны Интернета и в случае, если эти разрешения будут превышены, выдает исключения безопасности. Дополнительные сведения см. в разделах Безопасность и Безопасность частичного доверия в WPF.
Если вы не используете Visual Studio при разработке или хотите изучить файлы проекта более подробно, см. раздел Построение приложения WPF.
Развертывание XBAP
При построении XBAP создаются следующие три файла:
Файл | Описание |
---|---|
Исполняемый файл (.EXE) | Содержит скомпилированный код и имеет расширение EXE. |
Манифест приложения (.MANIFEST) | Содержит метаданные, связанные с приложением, и имеет расширение MANIFEST. |
Манифест развертывания (.XBAP) | Этот файл содержит сведения, которые ClickOnce использует для развертывания приложения, и имеет расширение .xbap. |
Чтобы подготовить XBAP для развертывания, скопируйте файл EXE и связанные с ним манифесты на веб-сервер. Создайте HTML-страницу, содержащую гиперссылку, чтобы открыть манифест развертывания, который является файлом с расширением XBAP. Когда пользователь щелкает ссылку на XBAP-файл, ClickOnce автоматически обрабатывает механизм скачивания и запуска приложения. В следующем примере кода показана HTML-страница, которая содержит гиперссылку, указывающую на XBAP.
Кроме того, XBAP можно разместить во фрейме веб-страницы. Создайте веб-страницу с одним или несколькими фреймами. Назначьте исходное свойство фрейма файлу манифеста развертывания. Чтобы использовать встроенный механизм взаимодействия между веб-страницей размещения и XBAP, необходимо разместить приложение во фрейме. В следующем примере кода показана HTML-страница с двумя фреймами; в качестве источника для второго фрейма выбран XBAP.
Очистка кэшированных XBAP
В некоторых случаях после повторной сборки и запуска XBAP может оказаться, что открывается более ранняя версия XBAP. Это может произойти, например, если номер версии сборки XBAP статичен и XBAP запускается из командной строки. В этом случае в связи с тем, что номер кэшированной версии (версии, запущенной ранее) и новой версии остаются прежними, новая версия XBAP не загружается. Вместе нее загружается кэшированная версия.
В подобном случае можно удалить кэшированную версию с помощью команды Mage (устанавливается вместе с Visual Studio или Windows SDK) в командной строке. Следующая команда очищает кэш приложения.
Она обеспечивает запуск последней версии XBAP. При отладке приложения в Visual Studio должна запускаться последняя версия XBAP. Как правило, номер версии развертывания необходимо обновлять при каждой сборке. Дополнительные сведения о команде Mage см. в разделе Mage.exe (средство создания и редактирования манифеста).
Связь с веб-страницей размещения
Если приложение находится во фрейме HTML, вы можете взаимодействовать с веб-страницей, которая содержит XBAP. Для этого извлеките свойство HostScript элемента BrowserInteropHelper. Оно возвращает объект скрипта, представляющий окно HTML. Доступ к свойствам, методам и событиям можно получить в объекте окна, используя обычный синтаксис с точками. Также можно получить доступ к методам скрипта и глобальным переменным. В следующем примере показано, как извлечь объект скрипта и закрыть браузер.
Отладка XBAP, в котором используется HostScript
Если XBAP использует объект HostScript для взаимодействия с окном HTML, для запуска и отладки приложения в Visual Studio необходимо задать два параметра. Приложение должно иметь доступ к своему исходному сайту, а запустить его необходимо с HTML-страницы, которая содержит XBAP. Ниже описаны процедуры проверки двух этих параметров:
В Visual Studio откройте свойства проекта.
На вкладке Безопасность нажмите кнопку Дополнительно.
Отобразится диалоговое окно с расширенными параметрами безопасности.
Убедитесь, что флажок Предоставить приложению доступ к своему исходному сайту установлен, и нажмите кнопку ОК.
На вкладке Отладка выберите параметр Запустить браузер, используя URL-адрес и укажите URL-адрес HTML-страницы, содержащей XBAP.
В Internet Explorer нажмите кнопку Сервис и выберите Свойства обозревателя.
Откроется диалоговое окно «Свойства веб-обозревателя».
Перейдите на вкладку Дополнительно.
В списке Параметры в разделе Безопасность установите флажок Разрешать запуск активного содержимого файлов на моем компьютере.
Чтобы изменения вступили в силу, Internet Explorer необходимо перезапустить.
Включение активного содержимого в Internet Explorer может подвергнуть компьютер риску. Если вы не хотите изменять настройки безопасности Internet Explorer, запустите HTML-страницу с сервера и присоедините к процессу отладчик Visual Studio.
Вопросы безопасности XBAP
Обычно XBAP выполняются в изолированной среде безопасности частичного доверия, ограниченной набором разрешений зоны Интернета. Следовательно, ваша реализация должна поддерживать подмножество элементов WPF, которые поддерживаются в зоне Интернета; в противном случае разрешения приложения придется повысить. Дополнительные сведения см. в статье Безопасность.
Если в приложении используется элемент управления WebBrowser, WPF создает в нем экземпляр машинного элемента управления WebBrowser ActiveX. Если ваше приложение — это XBAP частичного доверия, запущенный в браузере Internet Explorer, элемент управления ActiveX выполняется в выделенном потоке процесса Internet Explorer. В связи с этим применяются указанные ниже ограничения.
Элемент управления WebBrowser должен обеспечивать поведение, аналогичное браузеру узла, включая ограничения безопасности. Некоторыми из этих ограничений безопасности можно управлять с помощью параметров безопасности Internet Explorer. Дополнительные сведения см. в статье Безопасность.
Если XBAP загружается на HTML-странице в междоменном режиме, возникнет исключение.
Время или порядок навигации могут отличаться, если элемент управления ActiveX выполняется в другом потоке. Например, переход на какую-либо страницу не всегда отменяется при запуске другого запроса навигации.
Настраиваемый элемент управления ActiveX может испытывать проблемы со связью, поскольку приложение WPF выполняется в отдельном потоке.
MessageHook не вызывается, потому что HwndHost не может назначить в качестве подкласса окно, выполняемое в другом потоке или процессе.
Создание XBAP с полным доверием
Если XBAP требует полного доверия, проект можно изменить, предоставив ему соответствующее разрешение. Чтобы предоставить полное доверие, необходимо выполнить указанные ниже действия.
В Visual Studio откройте свойства проекта.
На вкладке Безопасность выберите параметр Это приложение с полным доверием.
При этом происходят следующие изменения:
В файле проекта значение элемента изменяется на Custom .
В манифесте приложения (app.manifest) к элементу PermissionSet добавляется атрибут Unrestricted="true" .
Развертывание XBAP с полным доверием
При развертывании XBAP с полным доверием, не основанного на модели доверенного развертывания ClickOnce, действия, выполняемые после того, как пользователь запустит приложение, зависят от зоны безопасности. В некоторых случаях пользователь получит предупреждение при попытке установить приложение. Пользователь может выбрать продолжение или отмену установки. В следующей таблице описаны поведение приложения для каждой зоны безопасности и действия, необходимые для получения приложением полного доверия.
Поведение, описанное в предыдущей таблице, относится к приложениям XBAP с полным доверием, не следующим модели доверенного развертывания ClickOnce.
Для развертывания XBAP с полным доверием рекомендуется использовать модель доверенного развертывания ClickOnce. Она позволяет XBAP получать полное доверие автоматически, независимо от зоны безопасности и не запрашивая подтверждение пользователя. При использовании этой модели приложение должно быть подписано сертификатом надежного издателя. Дополнительные сведения см. в разделах Общие сведения о развертывании доверенных приложений и Знакомство с подписыванием кода.
Влияние времени запуска XBAP на производительность
Важную роль в производительности XBAP играет время его запуска. Если XBAP является первым загружаемым приложением WPF, время холодного запуска может составить от десяти секунд. Это связано с тем, что страницу хода выполнения обрабатывает WPF, а для того, чтобы отображать приложение, требуется холодный запуск CLR и WPF.
Кроме того, улучшенный параллелизм последовательности загрузки ClickOnce сокращает время запуска почти на десять процентов. После того, как ClickOnce загрузит и проверит манифесты, запускается загрузка приложения, а индикатор выполнения начинает обновляться.
WPF стирает границы между традиционными настольными и веб-приложениями. За счет использования страниц можно создавать WPF-приложения с возможностями навигации в веб-стиле. ХВАР позволяют выполнять WPF-приложения внутри окна браузера. С применением элемента управления Frame можно проделывать обратное, размещая HTML-страницу в окне WPF.
Однако при использовании элемента Frame для отображения HTML-содержимого все возможности по управлению содержимым утрачиваются. Не остается никакого способа для его инспектирования или для следования ему, когда пользователь переходит на новую страницу по щелчку на ссылке. И, конечно же, отсутствует возможность вызова методов JavaScript внутри HTML-страницы либо их обращения к коду WPF. Вот здесь как раз и приходит на помощь элемент управления WebBrowser.
Элемент управления Frame является прекрасным вариантом, когда необходим контейнер, способный гладко переключаться между содержимым WPF и HTML. Элемент управления WebBrowser более удобен, когда нужно изучать объектную модель страницы, ограничивать или следить за страничной навигацией или создавать путь, через который код JavaScript и код WPF могут взаимодействовать.
И WebBrowser, и Frame (с HTML-содержимым) отображают стандартное окно Internet Explorer. Это окно обладает всеми возможностями и средствами браузера Internet Explorer, включая поддержку JavaScript, Dynamic HTML, элементов управления ActiveX и подключаемых модулей. Однако дополнительных деталей вроде панели инструментов, адресной строки или строки информации о состоянии это окно не содержит (хотя все эти ингредиенты легко добавить к форме с использованием соответствующих элементов управления).
Элемент управления WebBrowser не был написан с нуля в виде управляемого кода. Подобно Frame (когда он отображает HTML-содержимое), он является оболочкой для СОМ-компонента shdocvw.dll, который представляет собой часть Internet Explorer и включен в Windows. Как следствие, WebBrowser и Frame имеют несколько графических ограничений, которые отсутствуют у остальных элементов управления WPF. Например, не допускается размещать другие элементы поверх HTML-содержимого, отображаемого в этих элементах управления, равно как применять трансформации для скашивания или поворота.
В качестве средства, способность WPF отображать HTML-содержимое (с помощью Frame или WebBrowser) даже близко не настолько же полезно, как страничная модель или ХВАР. Тем не менее, в особых ситуациях при наличии готового HTML-содержимого, которое не хотелось бы заменять, это средство может пригодиться. Например, элемент WebBrowser может использоваться для отображения внутри приложения HTML-документации или для предоставления возможности переходить к функциональности, предлагаемой на веб-сайте сторонних разработчиков.
Навигация к странице
Кроме свойства Source доступны также и методы навигации, которые перечислены ниже:
Navigate()
NavigateToString()
Загружает содержимое из переданной ему строки с полным HTML-содержимым веб-страницы. Это открывает интересные возможности, такие как извлечение HTML-текста из ресурса внутри приложения и его отображение
NavigateToStream()
Загружает содержимое из потока, который содержит HTML-документ. Это позволяет открывать файл и передавать его прямо в элемент управления WebBrowser для визуализации без удержания всего HTML-содержимого в памяти
GoBack() и GoForward()
Переходят к предыдущему или следующему документу в хронологии навигации. Во избежание ошибок перед использованием этих методов необходимо проверять свойства CanGoBack и CanGoForward, т.к. попытка перейти к документу, которого не существует (например, перейти назад, находясь на первом документе в хронологии), вызывает генерацию исключения
Refresh()
Перезагружает текущий документ
Вся навигация WebBrowser является асинхронной. Это значит, что код приложения продолжает выполнение во время загрузки страницы. Элемент управления WebBrowser поддерживает небольшой набор событий, которые описаны ниже:
Событие Navigating срабатывает при установке нового URL-адреса или при выполнении пользователем щелчка на ссылке. URL-адрес можно инспектировать и отменять навигацию, устанавливая е.Cancel в true.
Событие Navigated срабатывает после Navigating непосредственно перед тем, как в WebBrowser начинается загрузка страницы.
Событие LoadCompleted срабатывает после завершения процесса загрузки страницы и представляет собой удобную возможность для обработки полученной страницы.
Построение дерева DOM
Прежде чем можно будет использовать модель DOM с WebBrowser, необходимо добавить ссылку на библиотеку Microsoft HTML Object Library (mshtml.tlb). Поскольку она является библиотекой СОМ-объектов, в Visual Studio понадобится сгенерировать для нее соответствующую управляемую оболочку. Для этого нужно выбрать в меню Project (Проект) пункт Add Reference (Добавить ссылку), перейти на вкладку СОМ, выбрать опцию Microsoft HTML Object Library (Библиотека объектов HTML от Microsoft) и щелкнуть на кнопке ОК.
Стартовой точкой для исследования содержимого на веб-странице является свойство WebBrowser.Document. Оно дает объект HTMLDocument, который представляет одиночную веб-страницу в виде иерархической коллекции объектов IHTMLElement.
Для каждого имеющегося на веб-странице дескриптора предусмотрен отдельный объект IHTMLElement, включая параграфы (), гиперссылки (<а), изображения () и прочие знакомые ингредиенты HTML-разметки.
Свойство WebBrowser.Document доступно только для чтения. Это означает, что связанный с ним объект HtmlDocument модифицировать можно, но создавать для него новый объект HtmlDocument "на лету" нельзя. Вместо этого понадобится либо установить свойство Source, либо вызывать метод Navigate() для загрузки новой страницы. После срабатывания события WebBrowser.LoadCompleted можно получать доступ к свойству Document.
Построение объекта HTMLDocument занимает небольшое, но все же заметное время (в зависимости от размеров и сложности веб-страницы). Элемент управления WebBrowser на самом деле не создает объект HTMLDocument для страницы до тех пор, пока не будет предпринята первая попытка доступа к свойству Document. Каждый объект IHTMLElement имеет описанные ниже ключевые свойства:
Свойство id содержит значение атрибута id, если он был указан. Элементы идентифицируются с помощью уникальных атрибутов id, когда необходимости иметь возможность манипулировать ими в средстве автоматизации или серверном коде.
Свойство children содержит коллекцию объектов IHTMLElement, по одному объекту для каждого имеющегося дескриптора.
Свойство innerHTML хранит полное содержимое дескриптора, включая любые вложенные в него дескрипторы вместе с их содержимым.
Свойство innerText хранит полное содержимое самого дескриптора и содержимое любых вложенных в него дескрипторов. Однако все HTML-дескрипторы отбрасываются.
Свойства outerHTML и outerText исполняют ту же роль, что и innerHTML и innerText, но включают текущий дескриптор (а не только его содержимое).
Чтобы лучше понять свойства innerText, innertHTML и outerHTML, предположим, что есть следующий дескриптор:
Значение свойства innerText для этого дескриптора будет выглядеть так:
Значение свойства innerHTML — так:
И, наконец, значение свойства outerHTML включает в себя полностью весь дескриптор:
Вдобавок с помощью метода IHTMLElement.getAttribute() можно извлечь значение атрибута для элемента по имени.
Для навигации по модели документа HTML-страницы нужно просто перемещаться по коллекции дочерних элементов каждого объекта IHTMLElement. В следующем коде эта задача выполняется в ответ на щелчок на кнопке. При этом создается дерево, отражающее структуру элементов и содержимого на странице:
Главной из этих возможностей является метод pushState(), который позволяет изменить URL в адресной строке браузера, не обновляя при этом содержимого страницы. Эта возможность приходится кстати в специфической ситуации, а именно при создании динамических страниц, которые незаметно загружают новое содержимое и плавно обновляют себя. В такой ситуации URL страницы и ее содержимое могут рассогласоваться.
Например, если страница загрузит в динамическом режиме содержимое с другой страницы, первоначальный URL страницы не изменится, что может вызвать разнообразные проблемы с созданием закладки для этой страницы. Эту проблему можно решить с помощью отслеживания истории сеансов.
Проблемы с URL
Но страницы с динамическим содержимым, использующие данный тип конструкции, имеют общеизвестный недостаток. В частности, хотя по загрузке нового содержимого страница изменяется, URL в строке адреса браузера остается прежним:
Теперь представьте себе, что пользователю особенно понравился один из просматриваемых слайдов, например дерево для загадывания желаний, и он сохраняет URL изображения в закладках, отправляет его по электронной почте своей приятельнице, а также делает запись об изображении с указанием URL в Твиттере.
Эта проблема усугубляется в случае, если слайдов огромное количество, например, поток Flickr может содержать десятки и даже сотни изображений.
Традиционное решение: hashbang URL
Теперь браузер отправит этот запрос веб-серверу и попытается загрузить новую страницу. Но это явно не то, что нам требуется.
Как же реализовываться метод hashbang? Сначала нам нужно изменить URL, который отображается в браузере, когда страница загружает новое изображение. Это можно сделать, присвоив значение свойству location.href с помощью кода JavaScript. Далее, по загрузке страницы нам нужно исследовать URL, извлечь из него фрагментную часть и получить с веб-сервера соответствующее динамическое содержимое.
Хотя метод hashbang получил широкое распространение, он также порождает много споров о его соответствии требованиям. Веб-разработчики начали отказываться от его использования по нескольким причинам:
Отсутствие гибкости. Метод hashbang упаковывает в URL большой объем информации. Если изменить работу страницы, использующей этот метод, или ее способ сохранения информации, старые URL могут оказаться недееспособными, что вызовет крупный сбой в просмотре сайта.
Оптимизация поисковых систем. Поисковые системы могут рассматривать разные URL типа hashbang, практически как один и тот же URL. В случае страницы exotic-china.html это означает, что отдельные туристические достопримечательности, представляемые конкретными слайдами, не будут проиндексированы, более того, поисковые системы могут вообще игнорировать эту информацию.
Хотя веб-мастера по-разному относятся к этому методу, большинство из них разделяет мнение, что такие адреса представляют короткий этап в развитии интернета, и со временем им на смену придет возможность отслеживания истории сеансов.
HTML5-решение: история сеансов
HTML5 предоставляет другое решение проблемы с URL в отслеживании истории сеансов. Можно изменять URL любым образом, не требуя при этом добавления в него странных символов, как в случае с методом hashbang. Например, когда страница exotic_china.html загрузит пятый слайд, ее URL можно изменить так:
В этом случае браузер не будет пытаться запрашивать страницу exotic_china5.html, а оставит прежнюю страницу, но загрузит для нее указанный слайд, а это нам и нужно. То же самое происходит, когда посетитель перемещается в обратном порядке в истории просмотра. Например, если посетитель перейдет к следующему слайду (и URL изменится на exotic_china5.html), а потом возвратится назад к четвертому (возвращая URL к exotic_china4.html), браузер сохраняет текущую страницу, и активирует событие, с помощью которого можно загрузить соответствующий слайд и восстановить правильную версию страницы.
Для крупных веб-сайтов (например, Facebook или Flickr) это не представляет большой проблемы, т.к. они могут использовать серверный код и предоставить содержимое одного и того же слайда в другой упаковке. Но самостоятельному веб-разработчику для этого может потребоваться приложить несколько больше усилий.
Теперь, когда мы понимаем, каким образом история сеансов связана со страницами, собственно использование ее не представляет никаких трудностей. История сеансов состоит всего лишь из двух методов и одного события, добавленных к объекту history.
Наиболее важным является метод pushState(), который позволяет изменить часть URL, обозначающую страницу, в любое требуемое время. По причинам безопасности остальная часть URL изменениям не поддается. (Возможность изменять основную часть URL предоставила бы мощное средство для хакеров для подделывания веб-сайтов.)
Метод pushState() принимает три аргумента, обязательным из которых является только третий — URL, выводящийся в строке адреса браузера. Первым параметром могут быть любые данные, сохраняемые для представления текущего состояния данной страницы. Как мы увидим далее, эти данные можно использовать, чтобы восстановить состояние страницы, если пользователь возвратится к данному URL посредством списка истории посещенных страниц браузера.
Второй параметр — это заголовок страницы, отображаемый в браузере. В настоящее время все браузеры дружно игнорируют эту подробность. Если нет надобности в установлении этих параметров, им можно просто присвоить значение null.
Далее приведен код, который нужно добавить в страницу exotic_china.html, чтобы изменять ее URL в соответствии с текущим отображаемым слайдом:
Обратите внимание, что в качестве параметра состояния страницы используется номер текущего слайда. Важность этого обстоятельства станет понятной чуть позже, при рассмотрении события onPopState.
Результаты исполнения кода показаны на рисунке:
Используя метод pushState(), также следует иметь в виду событие onPopState, которое является его естественным дополнением. В то время как метод pushState() вставляет новый элемент в список История (History) браузера, событие onPopState предоставляет средство для обработки этого элемента, когда посетитель возвратится к нему.
Чтобы понять работу метода, рассмотрим, что происходит, когда посетитель просматривает все слайды. В процессе просмотра URL в адресной строке браузера меняется с exotic_china.html на exotic_china1.html, потом на exotic_china2.html, на exotic_china3.html и т.д. Хотя страница в действительности не изменяется, все эти URL добавляются в историю просмотра браузера.
Если пользователь щелкнет по ссылке для перехода на предыдущий слайд (например, с exotic_china3.html на exotic_china2.html), активируется событие onPopState. Это событие предоставляет коду информацию состояния, сохраненную ранее посредством метода pushState(). Задача программиста заключается в использовании этой информации, чтобы восстановить требуемую версию страницы. В настоящем примере это означает загрузку соответствующего слайда:
Обратите внимание, что в этом примере выполняется проверка на наличие объекта состояния, прежде чем приступать к работе. Это делается из-за того, что некоторые браузеры (включая Chrome) активируют событие onPopState при начальной загрузке страницы, даже если метод pushState() еще не вызывался.
Существует еще один метод объекта history — replaceState(), но он используется не так часто. Метод replaceState() можно применять для того, чтобы заменить информацию о состоянии, которая связана с текущей страницей, не добавляя при этом ничего в список История (History).
Поддержка браузерами истории сеансов
Функциональность отслеживания истории сеансов является сравнительно новой возможностью, хотя все последние версии основных браузеров поддерживают ее, за исключением Internet Explorer:
Браузер | IE | Firefox | Chrome | Safari | Opera | Safari iOS | Android |
Минимальная версия | - | 4 | 8 | 5 | 11.5 | 4.2 | - |
Проблему, возникающую вследствие отсутствия поддержки браузером истории сеансов, можно решить несколькими способами. Если ничего не делать, просто не будут выводиться составные URL. Как раз это и происходит, если загрузить рассмотренный пример в Internet Explorer — какой бы слайд мы не загрузили, URL остается неизменно exotic_china.html.
Другой подход — активировать обновление всей страницы при загрузке нового содержимого. Этот подход имеет смысл в том случае, если предоставление качественного, значащего URL более важно, чем приятная демонстрация динамически загружаемого содержимого. Например, этот метод применяется в онлайновом хранилище кода на сайте GitHub. Но если просматривать этот сайт через браузер, поддерживающий историю сеансов, то содержимое не только загружается динамически, это делается с применением эффекта скольжения изображений.
Самый сложный вариант — использовать историю сеансов там, где это возможно, а где невозможно, применять резервное решение в виде URL типа hashbang. (Этот метод используется на Facebook.) Недостатком данного метода является необходимость применять два разных подхода в одной и той же странице. Можно также использовать заплатку JavaScript.
Теперь, когда уже известно о страницах и различных способах их размещения, можно переходить к более подробному рассмотрению используемой WPF модели навигации. В этой статье речь пойдет о том, как именно работают гиперссылки WPF и каким образом восстанавливаются страницы, когда пользователь снова к ним возвращается.
Вам наверняка интересно узнать, как в действительности работают свойства вроде Application.StartupUri, Frame.Source и Hyperlink.NavigateUri. В приложении, которое состоит из несвязанных XAML-файлов и выполняется в браузере, этот процесс выглядит довольно просто: при щелчке на гиперссылке браузер интерпретирует ссылку на страницу как относительный URI-адрес и ищет указанную XAML-страницу в текущей папке. Но в скомпилированном приложении страницы перестают быть доступными в виде отдельных ресурсов: они компилируются в BAML (Binary Application Markup Language — двоичный язык разметки приложений) и вставляются в сборку. Так как же на них ссылаться с помощью URI?
Эта система работает благодаря способу, которым WPF обращается к ресурсам приложения. При выполнении щелчка на гиперссылке в скомпилированном XAML-приложении URI все равно интерпретируется как относительный путь. Однако он является относительным по отношению к базовому URI приложения. Поэтому гиперссылка, указывающая на Page1.xaml, фактически преобразуется в следующий упакованный URI:
Может возникнуть вопрос: почему так важно знать, как работают URI-адреса гиперссылок, если весь процесс проходит столь гладко? Главная причина состоит в том, что может потребоваться создать приложение, позволяющее переходить на XAML-страницы, которые хранятся в другой сборке. На самом деле для принятия такого проектного решения имеются веские основания. Поскольку страницы могут применяться в разных контейнерах, может возникнуть желание повторно использовать один и тот же набор страниц как в приложении ХВАР, так и в обычном приложении Windows.
В таком случае можно развернуть просто две версии приложения — браузерную и настольную. Чтобы избежать дублирования кода, все страницы, которые планируется использовать повторно, следует поместить в отдельную сборку библиотеки классов (DLL), на которую затем можно сослаться в обоих проектах приложений.
Это потребует внесения изменения в URI-адреса. При наличии страницы в одной сборке, указывающей на страницу в другой сборке, нужно будет использовать следующий синтаксис:
Здесь компонент имеет имя PageLibrary, а путь . PageLibrary;component/Page1.xaml указывает на скомпилированную и вставленную внутри него страницу Page1.xaml.
Конечно, абсолютный путь вряд ли будет использоваться. Вместо него гораздо целесообразнее применять в URI-адресах следующий относительный путь:
При создании сборки SharedLibrary для получения правильных ссылок на сборки, импортированных пространств имен и настроек приложения лучше использовать шаблон проекта Custom Control Library (WPF) (Библиотека специальных элементов управления (WPF)).
Хронология навигации
Хронология страниц в WPF работает точно так же, как и в браузере. При каждом переходе на новую страницу текущая страница добавляется в список предыдущих страниц. При щелчке на кнопке возврата страница добавляется в список следующих страниц. В случае возврата на одну страницу и перехода с нее на новую страницу список следующих страниц очищается.
Поведение списков предыдущих и следующих страниц выглядит довольно просто, но внутренние механизмы, обеспечивающие работу этих списков, являются гораздо более сложными. Например, предположим, что вы посещаете страницу с двумя текстовыми полями, вводите в них что-нибудь и двигаетесь дальше. Если вы после этого вернетесь обратно на эту страницу, то увидите, что WPF восстанавливает состояние текстовых полей, т.е. в них отображается все, что было ранее введено.
Между возвращением на страницу через хронологию навигации и выполнением щелчка на ссылке, которая направляет на эту же самую страницу, существует большая разница. Например, при переходе со страницы Page1 на страницу Раде2 и затем снова на страницу Page1 с помощью соответствующих ссылок WPF создаст три отдельных объекта страницы. При втором отображении страница Page1 создастся как отдельный экземпляр с собственным состоянием. Однако в случае возврата к первому экземпляру Page1 за счет двукратного щелчка на кнопке возврата она будет видна в исходном состоянии.
Может показаться, что WPF поддерживает состояние ранее посещенных страниц за счет удержания объекта страницы в памяти. Проблема такого подхода состоит в том, что связанные с памятью накладные расходы в сложном приложении с множеством страниц в таком случае могут быть слишком большими. По этой причине удержание объекта страницы не считается безопасной стратегией и вместо него при покидании страницы сохраняется информация о состоянии всех элементов управления, а объект страницы уничтожается. При возврате на эту страницу WPF создает ее заново (из исходного XAML-файла) и восстанавливает состояние ее элементов управления.
Такая стратегия сопровождается меньшим количеством накладных расходов, поскольку для сохранения деталей о состоянии элементов управления требуется гораздо меньше памяти, чем для сохранения всей страницы вместе с визуальным деревом объектов.
Глядя на эту систему, возникает интересный вопрос: как WPF решает, какие детали нужно сохранять? WPF анализирует все дерево элементов страницы и просматривает имеющиеся у этих элементов свойства зависимости. Свойства, которые должны быть сохранены, имеют небольшой фрагмент дополнительных метаданных — журнальный флаг, который указывает, что они должны помещаться в журнал навигации. Этот флаг устанавливается с помощью объекта FrameworkPropertyMetadata при регистрации свойства зависимости.
Присмотревшись к системе навигации поближе, можно будет заметить, что у многих свойств нет журнального флага. Например, если установить свойство Content элемента управления содержимым или свойство Text элемента TextBlock с помощью кода, ни одна из этих деталей не будет восстановлена при возврате на страницу. То же самое будет и при динамической установке свойства Foreground или Background. Однако если установить свойство Text элемента TextBox, свойство IsSelected элемента CheckBox или свойство SelectedIndex элемента ListBox, то все эти детали сохранятся.
Так что же можно предпринять, если такое поведение не подходит? Как быть в случае установки множества свойств динамическим образом и желании, чтобы вся эта информация сохранялась на страницах? Существует несколько возможных вариантов.
Самый мощный предполагает применение свойства Page.KeepAlive, которое по умолчанию имеет значение false. Когда это свойство устанавливается в true, WPF не применяет описанный выше механизм сериализации. Вместо этого WPF оставляет объекты всех страниц в действующем состоянии. Благодаря этому, при возврате обратно страница оказывается в том же виде, в котором была ранее. Разумеется, у этого варианта есть один недостаток, заключающийся в увеличении накладных расходов, связанных с памятью, поэтому его следует применять только для нескольких страниц, которые действительно в нем нуждаются.
В случае использования свойства KeepAlive для сохранения страницы в действующем состоянии, при следующем возврате к ней событие Initialized генерироваться не будет. (Для страниц, которые не сохраняются в действующем состоянии, а "возвращаются к жизни" с помощью системы журнализации WPF, такое событие будет инициироваться при каждом их посещении пользователем.) Если такое поведение не подходит, тогда следует обработать события Unloaded и Loaded, которые генерируются всегда.
Второй вариант — построить другое проектное решение, способное передавать информацию, где это необходимо. Например, можно создать страничные функции, предназначенные для возврата информации. Используя страничные функции вместе с дополнительной логикой инициализации, можно разработать собственную систему для извлечения из страницы важной информации и ее сохранения в подходящем месте.
С хронологией навигации WPF связан еще один недостаток. Можно написать код, который будет динамически создавать объект страницы и затем переходить на нее. В такой ситуации обычный механизм сохранения состояния страницы работать не будет. У WPF нет ссылки на XAML-документ страницы, поэтому ей не известно, как реконструировать страницу. (А если страница создается динамически, у нее может вообще не быть соответствующего XAML-документа.) В такой ситуации WPF всегда будет сохранять объект страницы в памяти, каким бы ни было значение свойства KeepAlive.
Добавление специальных свойств
Обычно все поля в классе страницы утрачивают свои значения при уничтожении данной страницы. Чтобы добавить в класс страницы какие-то специальные свойства и сделать так, чтобы они сохраняли свои значения, можно соответствующим образом устанавливать журнальный флаг. Однако подобное действие в отношении обычного свойства или поля невозможно. Поэтому в таком случае необходимо создать в классе страницы свойство зависимости.
Для создания свойства зависимости потребуется выполнить два шага. Во-первых, необходимо создать определение свойства зависимости, а во-вторых — добавить процедуру обычного свойства, устанавливающую или извлекающую значение этого свойства зависимости.
Чтобы определить свойство зависимости, необходимо создать статическое поле, такое как показанное ниже:
По соглашению поле, определяющее свойство зависимости, должно обязательно иметь то же имя, что и обычное свойство, но со словом Property в конце.
В данном примере используется приватное свойство зависимости. Причина в том, что единственный код, которому требуется получать доступ к этому свойству, находится в классе страницы, где это свойство определено.
В завершение необходим статический конструктор, регистрирующий определение свойства зависимости. Именно здесь указываются службы, которые должны применяться со свойством зависимости (вроде поддержки привязки данных, анимации и журнализации):
Затем можно создавать обычное свойство, упаковывающее данное свойство зависимости. Однако при написании процедур извлечения и установки следует использовать методы GetValue() и SetValue(), определенные в базовом классе DependencyObject:
Осталось только добавить все эти детали в одну страницу (в данном примере это PageWithPersistentData). После этого значение свойства MyPageData будет автоматически сериализироваться, когда пользователь покидает страницу, и восстанавливаться при его возвращении.
Читайте также: