Qt как открыть html в браузере
Здравствуй, дорогой хабражитель! Я хочу рассказать, как интегрировать программу на новомодном языке QML с веб-ресурсами.
Сам по себе, QML — это декларативный JavaScript-подобный язык программирования, который входит в фреймворк Qt. Разработчики Qt настроены серьезно и продвигают его как основной инструмент создания интерфейсов. Более того, достаточно много вещей можно сделать не прибегая вообще к C++, в том числе и возможность работы с веб-серверами.
Веб-технологии все сильнее проникают в нашу жизнь, мы часто пользуемся различными веб-ресурсами. Не всегда удобно для этого запускать браузер, иногда отдельное приложение-клиент гораздо удобнее, о чем красноречиво говорит, например, количество клиентов для различных социальных сетей, особенно на мобильных платформах.
Учитывая, что в Qt 5.1, альфа версия которой вышла на прошлой неделе, включена начальная поддержка Android и iOS, эта тема может быть особенно интересна тем, кто присматривается к Qt или активно ее осваивает. В этой статье я расскажу, как можно организовать работу с веб-ресурсами из приложения на QML на примере API ВКонтакте.
На всякий случай отмечу, что я рассматриваю последнюю стабильную версию Qt 5.0.2. В более ранних версиях каких-то возможностей может не быть.
Отличительной особенностью реализации XHR в QML является то, что запросы можно отправлять на любой хост, здесь нет таких ограничений, как в браузере.
Процесс работы с XHR выглядит следующим образом.
1. Создаем объект XHR:
Первым параметром передаем тип запроса, вторым — URL. Для GET-запроса параметры нужно передать здесь же, отделив их от адреса символом '?'. Параметры разделяются символом '&'.
Для POST-запроса нужно указать тип содержимого. Если мы передаем данные параметрами запроса, то делается это следующим образом:
4. Отправляем запрос.
В случае POST, здесь же нужно передать параметры запроса:
В параметрах запроса можно передавать далеко не все символы. Поэтому и параметр и значение стоит кодировать и, если надо, соответственно декодировать специальными функциями — encodeURIComponent() и decodeURIComponent(). Пример использования:
Рекомендуется закодированную строку еще дополнительно обработать и заменить последовательность "%20" (т.е. закодированный пробел) на символ '+'. Перед декодированием, соответственно, сделать наоборот.
Обычно параметрами запроса передаются значения простых типов. Можно передать и массив, но синтаксис несколько мутный. Например, отправка массива params из двух значений будет выглядеть так:
Если изловчиться, то можно в качестве значений передавать даже объекты (!), но это может быть не совсем надежно, в том плане, что на принимающей стороне он может превратится в массив :)
Здесь JSON — это глобальный объект доступный в QML, предоставляющий средства по работе с данным форматом [3].
Фактически, формат, в котором мы можем передавать данные, определяется сервером. Если он принимает JSON — отлично, шлем JSON. Ожидает, что данные придут параметрами запроса — значит так и надо отправлять.
Теперь, когда мы изучили необходимые теоретические сведения, приступим к практике и поработаем с ВКонтакте.
Получение и отображение списка друзей
Для начала рассмотрим простой пример с методами, не требующими авторизации и других лишних телодвижений. Получение списка друзей попадает в эту категорию. Напишем простую программу, при старте отправляющую XHR на получение списка друзей и после его получения отображающую имена пользователей и их аватарки.
Большую часть кода составляет интерфейс отображения и его особо описывать нет смысла. Отмечу только, что если в качестве модели используется JavaScript объект или массив, то для получения к данным модели используется modelData вместо model.
Ниже собственно исходный текст программы. В качестве userId в main задается ID пользователя.
Я сделал вывод в консоль того, что придет в ответе, это удобно, если возникнет желание поиграться с этим примером.
Запустив программу, если был указан действительный ID, мы получим примерно такую картину:
Самая большая сложность здесь именно в работе с XHR. Попробуем разобраться с этим и немного упростить код.
В работе с XHR есть две сложности.
1. При передаче данных параметрами запроса, этот запрос нужно составлять. В случае, если эти параметры могут меняться, то скорее всего в коде будет много операций, склеивающие параметры запроса из кусочков. К тому же, нужно не забывать про то, что неплохо бы еще и при составлении ключи и значения кодировать при помощи encodeURIComponent, как я уже писал выше. Итого код, формирующий эти параметры может получиться громоздким и не очень понятным. Гораздо удобнее было бы в качестве параметров использовать объект, в котором установлены соответствующие поля.
Используя две данные библиотеки я упростил предыдущий пример. Весь код здесь приводить не буду, рассмотрим только то, что изменилось.
В начале файла мы подключаем библиотеки (в данном примере, файлы библиотек лежат в том же каталоге, что и qml-файл):
Мы импортируем библиотеки и задаем для них пространства имен (namespace), через которые мы будем обращаться к функциям из библиотек.
Функция, отправляющая XHR выглядит теперь так:
Для начала мы определяем объект с параметрами запроса. Затем функцию-callback, которая вызовется при завершении запроса. Функция получает параметром сам запрос. И затем отправляем сам запрос, преобразовав объект с параметрами при помощи функции serializeParams.
В итоге, размер кода, можно сказать, не изменился, но зато он стал гораздо более структурированным и понятным.
Я буду эти функции применять в дальнейшем, чтобы код был проще. Если кому-то они пригодятся, можно брать и пользоваться, лицензия MIT.
Авторизация ВКонтакте из QML
1. Способы авторизации
Поскольку мы делаем standalone-приложение, то есть два способа авторизации, у обоих есть свои проблемы, так что нужно выбрать наименьшее зло :)
- Простота.
- Нужно передавать секретный код приложения (возможно даже придется зашить в программу), соответственно есть риск его утечки.
- Такой способ будет работать только для доверенных приложений. При создании нового приложения он будет недоступен и чтобы его включили нужно писать в поддержку.
2. Авторизация OAuth. Реализуется следующим образом. В программу нужно встроить браузер, в котором пользователю показать специальную страницу логина. После авторизации произойдет перенаправление на другую страницу и в текущем URL будет находиться токен либо описание ошибки. ВКонтакте этот способ позиционирует как основной.
- Главное и очень существенное преимущество в том, что он работает для всех приложений и для приложений, которым не разрешили прямую авторизацию, это вообще единственный способ.
- Не надо передавать секретный ключ.
- OAuth является стандартом и точно также можно авторизоваться в Facebook, например.
Недостатки, впрочем, тоже существенные.
- Нужно открывать страницу ВКонтакте, а значит либо пытаться встроить ее в окно программы либо открывать в отдельном окне.
- Поскольку мы открываем страницу, то нам нужен и браузер. Соответственно, придется тащить QtWebkit и все, что он за собой потянет, отчего программа прибавит в весе.
- Нужно будет перехватывать события смены URL встроенного браузера, парсить этот URL и выбирать из него параметры, что несколько сложнее, чем XHR.
2. Прямая авторизация
Я, конечно, запросил чтобы мне включили возможность прямой авторизации, но поддержка ВКонтакте сначала неторопливо расспрашивала меня, что да зачем мне надо, а потом полный доступ все-таки зажала :( Так что рассмотрим чисто теоретически. Выглядеть это будет примерно так:
В начале формируем параметры, в них я для примера указал, что требуется доступ к аудио записям пользователя (параметр scope). Затем функция-callback, которая в случае ошибки пишет в консоль, а в случае успеха сохраняет токен и дальше уже могут идти запросы к API.
3. Авторизация через OAuth.
Для этого типа авторизации нам нужно показать пользователю веб-страницу логина. В QtQuick есть компонент WebView, позволяющий встроить в приложение на QML браузер на движке WebKit. После того, как пользователь авторизуется, URL в браузере сменится и, в случае удачной авторизации будет содержать токен в параметрах запроса или описание ошибки в якоре [5].
Чтобы не морочиться с разбиранием этого URL, используем функцию parseParams из URLQuery. Ей можно передать сразу весь URL, на выходе мы получим объект с параметрами.
Ниже описан компонент, реализующий этот функционал.
Мы отображаем этот компонент в отдельном окне. После вызова метода login(), будет загружена страница логина.
Для того, чтобы понять, когда мы перешли на нужный адрес, мы устанавливаем обработчик onLoadingChanged. Он принимает объект loadRequest, из которого мы получаем всю нужную нам информацию. Он вызывается несколько раз и нас интересует ситуация либо когда произошла ошибка, в случае чего мы посылаем соответствующий сигнал, либо когда нужная страница загрузилась. В этом случае мы проверяем, пришел ли нам токен и, если да, посылаем сигнал об успешной авторизации, иначе сигнал об ошибке.
Ну а теперь рассмотрим саму программу, которая этот виджет использует. Программа в случае успешной авторизации устанавливает статус пользователя в «test». ID пользователя задается свойством userId в main.
После загрузки нам покажется окно логина. После логина оно скрывается и отправляется запрос на сервер для смены статуса пользователя. После этого, программа пишет в консоль результат и завершается.
После того, как мы авторизовались, нам не надо больше запрашивать токен, если нам не понадобились какие-то дополнительные права доступа или его время жизни не истекло (нам его возвращают вместе с токеном, в случае успешной авторизации).
Расскажу небольшую историю из своего опыта, не связанную с ВКонтакте, но зато связанную с XHR.
Как-то у моего коллеги возникла задача получать и обрабатывать в QML данные в формате XML.
В QtQuick есть специальный тип XmlListModel, способный вытащить из сети, распарсить и представить в виде модели XML-файл. Ему нужно задать запрос типа XPath, в соответствии с которым будет наполняться модель. Проблема была в том, что XML-файл содержал не только элементы, которые нужно было выбрать и поместить в модель но и некоторую дополнительную информацию, которую тоже нужно было получить.
Методов решения несколько. Можно использовать два объекта XmlListModel, но это однозначный костыль, к тому же не хотелось, чтобы XML-файл перекачивался два раза (а он будет, проверено). Можно реализовать этот функционал используя Qt, который содержит аж целых несколько вариантов парсеров, но было желание решить задачу на чистом QML.
Небольшое резюме
О кроссплатформенной библиотеке Qt слышали, наверное, многие. О движке отображения веб-страниц WebKit тем более. Не так давно первое стало содержать обертку над вторым, примеры создания браузеров в 50 строчек найти не сложно. Тем не менее о том, как получать доступ к отдельным элементам веб-страницы из Qt-кода написано не много.
В данном описании я предполагаю, что люди обладают начальными познаниями в PyQt (я учил по Саммерфилду), и смутным представлением о JavaScript. Свой уровень я характеризую, именно таким, так что заранее извиняюсь за ошибки, особенно в описании ява-скрипта. Несмотря на то что в качестве языка использован Python у программистов C++/Qt вопросов тоже быть не должно.
Тестовые примеры запускались на PyQt-4.7.3, версия Python-2.6.6-r1 под ОС GNU/Linux. Из программ понадобится браузер с отладкой JS (Chrome, например) и PyQt IDE на ваше усмотрение, я использую Eric4.
Пример 1. Браузер, над которым мы будем издеваться
def adjustLocation(self):
self.locationEdit.setText(self.webView.url().toString())
def adjustTitle(self):
if self.__progress = 100:
self.setWindowTitle(self.webView.title())
else :
self.setWindowTitle(QString( "%1 (%2%)" ).arg(self.webView.title()).arg(self.__progress))
def setProgress(self, p):
self.__progress = p
self.adjustTitle()
def finishLoading(self):
self.__progress = 100
self.adjustTitle()
if __name__ == "__main__" :
import sys
app = QApplication(sys.argv)
prog = BaseBrowser()
prog.show()
sys.exit(app.exec_())
* This source code was highlighted with Source Code Highlighter .
Браузер представляет некую вариацию на тему браузеров из обучающих примеров по C++/Qt и PyQt, в последующих двух примеров мы будем его наследовать. Я понимаю, что так программы, даже маленькие, не пишут, и программа не должна быть одним классом, но баланс между кол-вом кода, его наглядностью и правильностью архитектуру я соблюдаю как могу.
Итак, браузер наш умеет не многое, но может загружать и отображать введенную страницу, для этого используется виджет QWebView, стандартные сигналы создаваемые этим виджетом мы привязали к слотам нашего браузера, что позволяет программе знать программе о смене заголовку текущей веб-страницы SIGNAL(«titleChanged(QString)»), прогрессе загрузки SIGNAL(«loadProgress(int)») и окончании загрузки — SIGNAL(«loadFinished(bool)»). Кроме этого создается поле QlineEdit для ввода адресса страницы и кнопка для перехода к этой веб-странице, либо по нажатию «Enter» либо по щелчку на кнопке.
Запускаем браузер, пробуем в работе, офигиваем от скорости работы «голого» WebKit. Пока ничего особенного мы не написали. Наш браузер даже по ссылкам не по всем переходит.
Пример 2. DOM-деревья и доступ к их элементам из Qt
Вообще, о структуре HTML страниц лучше бы, почитать отдельно, в двух предложениях это описать проблематично. В общем-то, если вы будете делать из офлайновую оболочку к какому либо веб-интерфейсу, ява-скрипт нужно будет все-таки выучить, по-крайней мере ту его часть, которая относится к доступу к данным. Итак, любой современный браузер позволяет получить доступ к содержимому веб-страницы представляя его в в виде дерева узлов, каждый узел которого представляет собой элемент, атрибут, текстовый, графический или любой другой объект. Узлы связаны между собой отношениями родительский-дочерний (да, эта строка из википедии). При помощи интерпретатора JavaScript к узлам этого дерева можно получить доступ. Откроем наш браузер и зайдем на все тот же yandex.ru (надеюсь их не накроет хабраэффектом). Сколько вы видите ссылок над поисковой строкой?
Щелкните по списку ссылок и откройте их в меню разработчика (в Chrome это — «проверить элемент» в контекстном списке). Так мы увидим положение текущего элемента в дереве. Список имеет незамысловатый и является таблицей. Переключитесь в JavaScript консоль и попробуйте выбрать эту таблицу:
document.getElementById("tabs").
Посмотрите сколько в ней строк:
document.getElementById("tabs").rows.length
И сколько столбцов:
document.getElementById("tabs").rows(0).cells.length.
Теперь получим такой же результат в нашем браузере.
class SimpleJavaScript(BaseBrowser):
def __init__(self, parent = None):
super(SimpleJavaScript, self).__init__(parent)
self.jsButton = QPushButton( "ExecuteJS" )
self.connect(self.jsButton, SIGNAL( "clicked()" ), self.jsScript)
self.jsStringEdit = QLineEdit()
self.jsStringEdit.setSizePolicy(QSizePolicy.Expanding, self.jsStringEdit.sizePolicy().verticalPolicy())
self.jsStringEdit.setText( "document.getElementById(\"tabs\").rows(0).cells.length" )
self.connect(self.jsStringEdit, SIGNAL( "returnPressed()" ), self.jsScript)
self.jsReturnText = QTextEdit()
self.layout.addWidget(self.jsStringEdit, 2, 0, 1, 1)
self.layout.addWidget(self.jsButton, 2, 1, 1, 1)
self.layout.addWidget(self.jsReturnText, 3, 0, 1, 2)
def jsScript(self):
jsString = self.jsStringEdit.text()
jsReturn = self.webView.page().currentFrame().evaluateJavaScript(jsString)
self.jsReturnText.setPlainText(jsReturn.toString())
if __name__ == "__main__" :
import sys
app = QApplication(sys.argv)
ui = SimpleJavaScript()
ui.show()
sys.exit(app.exec_())
* This source code was highlighted with Source Code Highlighter .
Пример 3. Создание офлайн контролов
Адрес домашней страницы на этот раз выбран таким поскольку у меня карточка ATI и сижу я под Линуксом, кто знает, тот поймет, что это не от большой любви. На самом деле на странице множество контролов типа Select, для одного из которых мы создадим эквивалент.
class JSSelectList(QAbstractListModel):
def __init__ (self, _id, _jsFunc, parent = None):
super(JSSelectList, self).__init__(parent)
self.id = _id
self.jsFunc = _jsFunc
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return QVariant()
if role == Qt.DisplayRole:
jsstring = QString( "document.getElementById('%1').options[%2].textContent" ).arg(self.id).arg(index.row())
jsreturn = self.jsFunc(jsstring)
return jsreturn.toString().trimmed()
def rowCount(self, index=QModelIndex()):
jsstring = QString( "document.getElementById('%1').length" ).arg(self.id)
jsreturn = self.jsFunc(jsstring)
ok = False
count, ok = jsreturn.toInt()
return count if ok else 0
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role != Qt.DisplayRole:
return QVariant()
else :
return self.id
class JSComboBoxDemo(BaseBrowser):
def __init__(self, parent = None):
super(JSComboBoxDemo, self).__init__(parent)
self.vendorComboBox = QComboBox()
id = QString( "productLine" )
self.vendorListModel = JSSelectList(id, self.webView.page().currentFrame().evaluateJavaScript)
self.vendorComboBox.setModel(self.vendorListModel)
self.connect(self.vendorComboBox, SIGNAL( "currentIndexChanged(int)" ), self.setSelectOnWebPage);
self.connect(self.webView, SIGNAL( "loadFinished(bool)" ), self.initComboBox)
self.layout.addWidget(self.vendorComboBox, 2, 0, 1, 1)
self.webView.load(QUrl( "http://www.amd.com" ))
def setSelectOnWebPage(self, new_id):
jsstring = QString( "document.getElementById('productLine').selectedIndex=%1" ).arg(new_id)
self.webView.page().currentFrame().evaluateJavaScript(jsstring)
def initComboBox(self):
self.vendorComboBox.setCurrentIndex(0)
if __name__ == "__main__" :
import sys
app = QApplication(sys.argv)
ui = JSComboBoxDemo()
ui.show()
sys.exit(app.exec_())
* This source code was highlighted with Source Code Highlighter .
При создании таких элементов GUI как таблица, список, dropdown (не знаю как правильно перевести) Qt позволяет использовать удобный MVC подход. Вам нужно лишь описать доступ к вашей модели данных — вам нужно лишь наследовать ваше представление данных от встроенного абстрактного класса и прицепить его к стандартному контролу (у Саммерфилда, это вроде бы 14 глава). В данном случае используется QAbstractListModel, из параметров ей передается только функция исполнения JS и название select`а на странице. Все переопределения стандарты.
В самом примере тоже все достаточно понятно, кроме двух соединений типа сигнал-слот, на которые хотелось бы обратить ваше внимание.
Во-первых, бесполезно пытаться выполнить JavaScript до загрузки страницы, поэтому воспользуемся тем, что при окончании загрузки виджет QWebView формирует сигнал SIGNAL(«loadFinished(bool)»), о котором я уже говорил в первом примере.
self.connect(self.webView, SIGNAL("loadFinished(bool)"), self.initComboBox)
В противном случае, если запихнуть строку
в __init__ ни какой инициализации первым значением не произойдет — evaluateJavaScript ничего не вернет, так как страница еще не успеет загрузиться.
Во-вторых, нам нужна синхронизация в обе стороны:
self.connect(self.vendorComboBox, SIGNAL("currentIndexChanged(int)"), self.setSelectOnWebPage)
Аналогичным образом можно синхронизировать практически всю информацию на странице, нажимать кнопки, загружать информацию.
Буду рад, если информация окажется для кого-то полезной. Всех с Рождеством и прошедшим Новым Годом.
Ж. Бланшет, М. Саммерфилд. Qt 4: Программирование GUI на C++.
Mark Summerfield. Rapid GUI Programming with Python and Qt.
Другие источники:
Различные интернет сайты по JavaScript и PyQt, исходный код интернет-браузера Arora.
В процессе разработки приложения на Qt, может понадобиться добавить в данное приложение веб-интерфейс, что особенно может быть актуально при разработке встраиваемых систем с использованием Qt. Для решения данной задачи можно либо написать собственное решение, либо воспользоваться готовыми решениями. Например, библиотекой QtWebApp, которая предоставляет необходимый функционал для создания web-интерфейса.
- формирование страниц с динамическим содержанием по шаблонам;
- формирование полностью динамических страниц;
- работу с Cookie, что позволит добавить авторизацию на приложении;
- работу со статическими файлами, например, style.css или изображения;
- реализацию загрузки файлов.
На момент написания статьи изначально использовалась библиотека QtWebApp 1.6.3 и Qt 5.6. Проект успешно был запущен с комплектами сборки MSVC2013 и MinGW. В процессе отладки был замечен баг в классе Template библиотеки QtWebApp. После исправления бага и связи с разработчиком версия библиотеки была повышена до 1.6.4. Исходя из этого, можно отметить также плюс библиотеки, что разработчик ответил в течение суток на информацию о баге, и в тот же день версия библиотеки была повышена. Окончательный вариант примера приложения был подготовлен на версии 1.6.4.
В данном проекте предлагается создать приложение, имеющее три страницы, меню для выбора этих страниц, и три статических файл. Один из файлов – это style.css, а два других – это изображения.
Структура проекта
Проект будет сформирован в виде Subdirs проекта, который будет состоять из основного проекта и проекта библиотеки QtWebApp.
Структура проекта:
QtWebAppExample.pro – основной профайл проекта
common – пользовательский проект web-сервера
QtWebAppExample.pro
Общий профайл проекта — шаблон subdirs с подключённым основным проектом и библиотекой QtWebApp. Важна последовательность подключения проектов в файле. Библиотека QtWebApp должна быть прописана первой, иначе при сборке проекта возникнут ошибки:
если на момент сборки основного проекта, который зависит от QtWebApp, собранных файлов библиотеки (.dll или .so) не будет в наличии, проект не соберется.
common.pro
Помимо этого, необходимо правильно прописать линковку к заголовочным файлам и файлам исходных кодов, а также выходные папки для сборки библиотеки, чтобы собираемый проект смог обратиться по правильному пути к файлам библиотеки.
QtWebApp.pro
Профайл проекта библиотеки по умолчанию показан ниже. Единственным изменением в проекте стало наличие дополнительной настройки сборки в качестве статической библиотеки.
main.cpp
А теперь по порядку пройдёмся по всем файлам проекта common, чтобы разобраться, как можно запустить Qt-приложение с web-интерфейсом. Начнём со стартового файла приложения и с функции main, с которой осуществляется запуск приложения.
Здесь имеется получение пути к файлу настроек, в котором хранятся параметры настройки web-сервера, порт TCP/IP и т.д.
Также создаётся объект класса WebConfigurator, который отвечает за обработку запросов и выдачу по запросам соответствующих страниц web-сервера.
Содержит фактически вспомогательный класс, не несущий в себе сверх необходимой информации, но в него для удобства вынесена инициализация файла настроек и в случае более глобального проекта данный класс будет удобно расширить. В данном варианте, естественно, больше похоже на дело вкуса.
Все параметры относятся к настройке порта подключения, количеству одновременных сессий, длительности ожидания запроса.
Также в настройках приложения будут содержаться и параметры контроллера статических файлов, в частности путь к папке, в которой будет производиться поиск статических файлов веб-сервера. В данном приложении это папка html-static, которая будет располагаться в той же папке, что и исполняемый файл приложения.
WebConfigurator.h
А теперь внимательно посмотрим на содержимое класса WebConfigurator, который отвечает непосредственно за определение страниц, которые подлежат к отправке на запрос извне.
Определение страниц осуществляется с помощью объекта класса QHash, который содержит указатели на все объекты web-страниц и соответствующие им ключевые значения, которые соответствуют URL адресам запросов. Но QHash используется лишь для динамических страниц, а для статических страниц используется объект класса StaticFileController.
Webconfigurator.cpp
WebConfiguratorPage.h
Данный заголовочный файл содержит объявление основного класса, отвечающего за формирование страниц и наследованные от него три класса страниц для проекта: index.html, first.html, second.html.
WebConfiguratorPage.cpp
Common.htm
Под занавес рассмотрим содержимое шаблонов.
index.htm
Результат
В итоге получим рабочее приложение с веб-сервером, который отлично подойдет для встраиваемых систем.
А данное приложение сформирует следующую веб-страницу.
Примечание
Проект приложения можно скачать по ссылке: скачать.
При сборке проекта обязательно поставьте этап install, чтобы необходимые статические файлы были установлены в соответствующую папку к исполняемому файлу.
Немного о баге
Добавим пару слов о баге, который сам по себе был больше похож на результат неудачного рефакторинга кода или, скорее, разработчик просто был в определенный момент уставший. Дело в том, что в более ранних версиях QtWebApp, а именно в версии 1.5.10, код был корректным и выглядел следующим образом.
Тогда как в версии 1.6.3 была пропущена одна единственная строчка.
В результате данные не добавлялись в шаблон страницы, и пользователь получал пустую страничку. Как сообщил Стефан Фрингс, разработчик QtWebApp, он обычно использует иной, нежели мы, подход к формированию веб-интерфейса, поэтому просто не замечал подобной проблемы.
Simple Browser demonstrates how to use the Qt WebEngine C++ classes to develop a small Web browser application that contains the following elements:
- Menu bar for opening stored pages and managing windows and tabs.
- Navigation bar for entering a URL and for moving backward and forward in the web page browsing history.
- Multi-tab area for displaying web content within tabs.
- Status bar for displaying hovered links.
- A simple download manager.
Running the Example
To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.
Class Hierarchy
We start with sketching a diagram of the main classes that we are going to implement:
- Browser is a class managing the application windows.
- BrowserWindow is a QMainWindow showing the menu, a navigation bar, TabWidget , and a status bar.
- TabWidget is a QTabWidget and contains one or multiple browser tabs.
- WebView is a QWebEngineView, provides a view for WebPage , and is added as a tab in TabWidget .
- WebPage is a QWebEnginePage that represents website content.
Additionally, we will implement some auxiliary classes:
- WebPopupWindow is a QWidget for showing popup windows.
- DownloadManagerWidget is a QWidget implementing the downloads list.
Creating the Browser Main Window
This example supports multiple main windows that are owned by a Browser object. This class also owns the DownloadManagerWidget and could be used for further functionality, such as bookmarks and history managers.
In main.cpp , we create the first BrowserWindow instance and add it to the Browser object. If no arguments are passed on the command line, we open the Qt Homepage:
Creating Tabs
The BrowserWindow constructor initializes all the necessary user interface related objects. The centralWidget of BrowserWindow contains an instance of TabWidget . The TabWidget contains one or several WebView instances as tabs, and delegates it's signals and slots to the currently selected one:
Each tab contains an instance of WebView :
In TabWidget::setupView() , we make sure that the TabWidget always forwards the signals of the currently selected WebView :
Implementing WebView Functionality
The WebView is derived from QWebEngineView to support the following functionality:
- Displaying error messages in case renderProcess dies
- Handling createWindow requests
- Adding custom menu items to context menus
First, we create the WebView with the necessary methods and signals:
Displaying Error Messages
If the render process is terminated, we display a QMessageBox with an error code, and then we reload the page:
Managing WebWindows
The loaded page might want to create windows of the type QWebEnginePage::WebWindowType, for example, when a JavaScript program requests to open a document in a new window or dialog. This is handled by overriding QWebView::createWindow() :
In case of QWebEnginePage::WebDialog , we create an instance of a custom WebPopupWindow class:
Adding Context Menu Items
We add a menu item to the context menu, so that users can right-click to have an inspector opened in a new window. We override QWebEngineView::contextMenuEvent and use QWebEnginePage::createStandardContextMenu to create a default QMenu with a default list of QWebEnginePage::WebAction actions.
The default name for QWebEnginePage::InspectElement action is Inspect. For clarity, we rename it to Open Inspector In New Window when there is no Inspector present yet, and Inspect Element when it's already created.
We also check if the QWebEnginePage::ViewSource action is in the menu, because if it's not we have to add a separator as well.
Implementing WebPage Functionality
In all the cases above, we display the appropriate dialog to the user. In case of authentication, we need to set the correct credential values on the QAuthenticator object:
In case of SSL errors, we just need to return a boolean value indicating whether the certificate should be ignored.
Opening a Web Page
This section describes the workflow for opening a new page. When the user enters a URL in the navigation bar and presses Enter, the QLineEdit::returnPressed signal is emitted and the new URL is then handed over to TabWidget::setUrl :
The call is forwarded to the currently selected tab:
The setUrl() method of WebView just forwards the url to the associated WebPage , which in turn starts the downloading of the page's content in the background.
Implementing Private Browsing
Implementing private browsing is quite easy using Qt WebEngine. All one has to do is to create a new QWebEngineProfile and use it in the QWebEnginePage instead of the default profile. In the example this new profile is owned by the Browser object:
Required profile for private browsing is created together with its first window. The default constructor for QWebEngineProfile already puts it in off-the-record mode.
All that is left to do is to pass the appropriate profile down to the appropriate QWebEnginePage objects. The Browser object will hand to each new BrowserWindow either the global default profile (see QWebEngineProfile::defaultProfile) or one shared off-the-record profile instance:
The BrowserWindow and TabWidget objects will then ensure that all QWebEnginePage objects contained in a window will use this profile.
Managing Downloads
Downloads are associated with a QWebEngineProfile. Whenever a download is triggered on a web page the QWebEngineProfile::downloadRequested signal is emitted with a QWebEngineDownloadItem, which in this example is forwarded to DownloadManagerWidget::downloadRequested :
This method prompts the user for a file name (with a pre-filled suggestion) and starts the download (unless the user cancels the Save As dialog):
The QWebEngineDownloadItem object will periodically emit the downloadProgress signal to notify potential observers of the download progress and the stateChanged signal when the download is finished or when an error occurs. See downloadmanagerwidget.cpp for an example of how these signals can be handled.
Files and Attributions
The example uses icons from the Tango Icon Library:
© 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.
The Qt WebBrowser (codename Roadtrip ) is a browser for embedded devices developed using the capabilities of Qt and Qt WebEngine. Using recent Chromium, it features up-to-date HTML technologies behind a minimal but slick touch-friendly user interface written in Qt Quick.
User Interface
The user interface of the browser is designed for embedded devices using a touch screen. It uses flat icons and minimalistic transitions to provide a contemporary look and feel.
All central actions are conveniently accessible in the menu bar at the top. When the user scrolls down a page, the menu bar disappears to leave more screen space for content. It appears again if the user scrolls upwards.
The Back button (1) and Forward button (2) navigate through the history of pages already visited. The input bar (3) allows the user to type and edit URL's, or search for text. It also allows to reload a page, or stop a page currently loading. Pages can be bookmarked with the Bookmark button (6), the bookmarked pages are accessible through the Home button (4). The Pages button (5) allows the user to manage the pages currently opened. The Settings button (7) brings up a view of the current settings.
Opening Pages
The input bar supports typing in either a full URL or a query that is automatically passed to Google. Url suggestions of visited pages matching the text are suggested during typing. The current text can be cleared by pressing the Cancel button on the right.
When the user finishes editing, a blue line serves as a progress indicator for the page currently loading. The loading can be stopped by pressing the Cancel button. After the page finished loading this button is replaced by a Reload button, that forces a reload of the page when pressed.
Page Scrolling
The view of the page can be moved around by pressing and moving a finger.
Page Zooming
Zoom in or out a particular section of the page by pinching fingers.
Bookmark Management
Individual pages can be bookmarked by pressing the Bookmark button. By pressing the same button again the page is removed from the bookmarks.
The Home button lets the user browse the bookmarked pages in a multi-page grid. Each page is represented by its name and icon, if available.
Page Management
The Pages button enables the user to open new pages and to switch between them using the Carousel UI pattern:
The number of pages that can be opened simultaneously is intentionally limited to 10.
User Settings
Virtual Keyboard
The integrated Qt Virtual Keyboard slides in whenever text input is required. It enables typing text in a great variety of different languages.
Features
Qt WebEngine leverages Chromium to provide a state-of-the art, high performance HTML5 web engine.
Video and Audio
Chromium and therefore Qt WebEngine directly integrate with OS services to access video and audio devices. After the user acknowledges access to them, solutions using WebRTC, such as video and audio conferencing, work out-of-the-box, provided that the required codecs are available.
Location Information
Qt WebEngine uses Qt Location to provide pages with location information. Again, the users have to explicitly give their consent to each page attempting to access this information.
Fullscreen Mode
Videos can be played in fullscreen mode.
Developer Features
Developer Tools
Qt WebEngine supports remotely accessing the built-in Chromium Developer Tools. This allows debugging and optimizing individual pages on the device.
For more information, see the Qt WebEngine documentation on Debugging and Profiling.
Simulation of Touch Input
While the Qt WebBrowser is optimized for touch devices, it can also be tested on all the desktop operating systems. Keyboard and mouse input works out of the box. Touch input can be simulated by using several mouse buttons while pressing the Ctrl key.
Platform Requirements
Qt WebBrowser requires the Qt WebEngine, Qt Quick and Qt Virtual Keyboard modules in version 5.7 or newer.
At minimum 1 GB of RAM is advised to provide a seamless experience for different pages. Depending on the exact configuration and the pages visited this can be further optimized.
Qt Quick and Qt WebEngine use OpenGL for rendering. Best performance therefore requires dedicated graphics hardware with drivers supporting OpenGL.
© 2016 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.
Читайте также: