Использование web-интерфейса в MFC-приложениях
Введение
В данной статье описаны способы использования MFC-класса CHtmlView и технологии DHTML для создания web-интерфейса в MFC приложении. Под формулировкой «приложение с Web-интерфейсом» я подразумеваю то, что пользовательский интерфейс или его часть в приложении создан на основе HTML. При попытке применить такой интерфейс в MFC-приложении я столкнулся с следующими проблемами:
- сложность в обработке событий от web-интерфейса.
- сложность взаимодействия c элементами Web-интерфейса (например, как задать или считать текст определенному элементу UI).
Для решения таких вопросов MSDN (см. Handling HTML Element Events) советует применять COM-интерфейсы DHTML. Мне показалось, что это плохая альтернатива классической модели взаимодействия с GUI-интерфейсом в MFC (карты событий, и прямое манипулирование через SendMessage или объекты элементов GUI).
В этой статье приводится описание некоторых методик, облегчающих создание Web-интерфейса и работу с ним из MFC-приложений, а именно:
- Получение событий от Web-интерфейса через событие OnBeforeNavigate2().
- Взаимодействие с Web-интерфейсом через вызов скриптов в HTML-коде .
В качестве примера рассмотрим задачу разработки MFC-класса (CHtmlDialog) для создания диалогового окна с web-интерфейсом.
ПРИМЕЧАНИЕ Для ознакомления с понятием Web-интерфейса советую прочитать статью “Браузер в каждом окне”, в которой не только хорошо описано это явление, но и показаны приемы реализации Web-интерфейса в MFC-приложении. |
К статье прилагается архив с классом CHtmlDialog и примерами его использования.
Примеры применения web-интерфейса
Впервые Web–интерфейс для настольных приложений был применен в приложениях Microsoft, которые входят в состав ОС Windows.
Рисунок 1. Web-интерфейс в
справочном окне Windows XP.
Отображение HTML с помощью CHtmlView
Отобразить HTML в MFC-приложении довольно просто, для этого надо использовать класс ChtmlView. Создайте новый проект с помощью помощника “MFC AppWizard (exe)” и выберите Single Document. Включите поддержку архитектуры Document View. На последней странице помощника для класса ХххView установите CHtmlView как базовый класс (список Base Class), а затем добавьте в ресурсы приложения HTML-страничку.
Для того чтобы программа была больше похожа на приложение Windows, чем на окно Internet Explorer, необходимо кое-что подправить в HTML-страничке:
- В качестве фонового цвета нужно установить стандартный фоновый цвет для Windows-приложений.
- Запретить показывать контекстное меню от Internet Explorer (кроме контекстного меню над полем ввода):
- Запретить выделение мышью контента HTML. Разрешить выделять только текст в полях ввода.
- Запретить менять тип курсора над текстом. В IE курсор над текстом становится редактирующим (I, таким же, как в поле ввода), чтобы пользователь мог выделять и копировать текст. В приложениях такое поведение лучше исключить. Нужно разрешить менять курсор только над полем ввода и ссылками.
ПРИМЕЧАНИЕ Последние два пункта довольно спорны – так как неудобства пользователю они причинить могут, а пользу – вряд ли. В общем, на вкус и цвет... – прим.ред. |
Далее приведен DHTML-код, реализующий данные исправления. Такие действия возможны благодаря технологии DHTML.
... <SCRIPT LANGUAGE="JScript"> // Запретить пользователю выделение контента мышью // (разрешить выделять текст только в EditBox) function onSelect1() { if (window.event.srcElement.tagName !="INPUT") { window.event.returnValue = false; window.event.cancelBubble = true; } } // Запретить контекстное меню IE (разрешить только в EditBox) // (если все-таки надо показать настоящее меню, - // надо использовать Advanced Hosting Interfaces) function onContextMenu() { if (window.event.srcElement.tagName !="INPUT") { window.event.returnValue = false; window.event.cancelBubble = true; return false; } } // При загрузке HTML установить обработчики Контекстного Меню и Выделения. // function onLoad() { // для текста на HTML запретить менять курсор // (кроме Поля ввода "INPUT" и ссылки "A") var Objs = document.all; for (i = 0; i < Objs.length; i++) // Поля ввода "INPUT" и ссылки "A" if (Objs(i).tagName != "INPUT" && Objs(i).tagName != "A") Objs(i).style.cursor = "default"; // обработчик события – начало выделения HTML-области document.onselectstart = onSelect1; // обработчик события – контекстное меню document.oncontextmenu = onContextMenu; } </SCRIPT> </head> <BODY onload="onLoad();" leftmargin=0 topmargin=0 rightmargin=0 bottommargin=0 style = "background-color: buttonface;"> <!-- фон HTML области будет таким же, как у всех диалогов --> ... |
Итак, мы создали приложение, которое использует в качестве интерфейса HTML. Меню, Панель инструментов и строка состояния остались, но это нормально, т.е в приложении можно не отказываться от традиционных элементов управления, что дает определенную гибкость.
ПРИМЕЧАНИЕ Здесь мы столкнулись с технологией DHTML, благодаря которой вообще все это возможно. Сама технология DHTML возможна благодаря технологии DOM (Document Object Model). DOM представляет HTML-документ в виде объектов (см. MSDN Library). |
Обработка событий HTML-окна
Чего не хватает в ChtmlView?
Типичный сценарий работы с элементами интерфейса предполагает получение событий от них (например, от кнопок), и установку данных (например текст в поле ввода). Конкретно в нашем случае необходимо организовать взаимодействие HTML-окна с MFC-кодом. Это реализовать не сложно – идея состоит в том, чтобы передавать событие от HTML в MFC-код с помощью функции OnBeforeNavigate2 класса CHtmlView (см. HTML—The MFC-Way)
ПРИМЕЧАНИЕ В DHTML тоже есть такое понятие как событие. Возможности событийной модели в DHTML очень широки, например, событие можно остановить на определенном этапе обработки. |
При возникновении HTML-события его можно трансформировать в вызов window.navigate(%строка%). MFC-код получит такое событие в виде вызова OnBeforeNavigate2. В строке можно передать любые параметры, таким образом, MFC-код сможет обработать ситуацию.
Передача события о Нажатии на кнопку Ok, вместе с событием передается и текст из поля txtBox:
<SCRIPT LANGUAGE="JScript"> function onBtnOk() { var Txt = txtBox.value; // строка из TextBox // "app:1005@" - это префикс команды для MFC кода. // Txt - вместе с событием можно передать данные window.navigate("app:1005@" + Txt); } </SCRIPT> <BODY> <input type=text style="width:50" id=txtBox > <!-- у кнопки есть обработчик события – скрипт функция onBtnOk() --> <input type=BUTTON value="Ok" onClick="onBtnOk()" style="width:45%"> </BODY> </HTML> |
Код обработки события в классе CHtmlView:
void CHtmlCtrl::OnBeforeNavigate2( LPCTSTR lpszURL, DWORD nFlags, LPCTSTR lpszTargetFrameName, CByteArray& baPostedData, LPCTSTR lpszHeaders, BOOL* pbCancel) { const char APP_PROTOCOL[] = "app:"; int len = _tcslen(APP_PROTOCOL); if (_tcsnicmp(lpszURL, APP_PROTOCOL, len)==0) { OnAppCmd(lpszURL + len); // конкретная реакция приложения на событие *pbCancel = TRUE; // Отмена события BeforeNavigate2, иначе будет ошибка } CHtmlView::OnBeforeNavigate2(lpszURL, nFlags, lpszTargetFrameName, baPostedData, lpszHeaders, pbCancel); } |
Поскольку источником событий может быть не только HTML, но и MFC-код, надо, чтобы MFC-код мог обращаться к HTML (MFC -> HTML). Кроме событий, MFC-код должен иметь возможность передавать данные в HTML. Чтобы это стало возможным, можно вызывать скрипты (Jscipt\VBScript) в HTML и передавать им данные в качестве параметров. Идея и реализация этого метода принадлежат Eugene Khodakovsky – см. JavaScript Calls from C++ .
Получение объекта «Скрипт» из HTML:
void CHtmlCtrl::OnDocumentComplete(LPCTSTR lpszURL) { HRESULT hr; hr = GetHtmlDocument()->QueryInterface(IID_IHTMLDocument, (void**)&m_pDocument); if (FAILED(hr)) { m_pDocument= NULL; return; } IDispatch *disp; m_pDocument->get_Script(&disp); } |
MFC-код, который вызывает конкретную функцию скрипта с параметрами:
CStringArray strArray; strArray.Add("String 1"); strArray.Add("String 2"); strArray.Add("String 3"); // вызов функции SetParameters в скрипте и передача Массива строк в HTML код m_HtmlCtrl.CallJScript2("SetParameters", strArray); // внутри функции CallJScript2: // GetIDsOfNames() – получить номер скрипт-функции по имени // Invoke() – вызвать скрипт-функцию по номеру |
Скрипты в HTML являются очень мощным и легким в использовании средством. С помощью них можно написать целую программу, которая будет управлять содержимым HTML-страницы и реагировать на действия пользователей. Обьектная структура DHTML (DOM) делает это программирование достаточно легким.
СОВЕТ Таким образом, можно часть MFC-кода, которая отвечает за интерфейс и действия пользователя, перенести в HTML-скрипт. А если HTML-странички держать отдельно от приложения, то можно менять логику интерфейса, не перекомпилируя EXE-файл. |
CHtmlDialog - диалоговое окно на основе HTML
Что дает использование Advanced Hosting Interfaces?
В любом приложении кроме главного окна есть еще и диалоги. Эти диалоги могут обладать достаточно сложным пользовательским интерфейсом. Я имею в виду не только сам дизайн, но и реакцию на действия пользователя. Здесь тоже есть смысл использовать DHTML. Проблема, которая здесь возникает (как разместить в диалоговом окне класс, производный от CView) решена в статье Microsoft Systems Journal, January 2000, Q&A C++, CHtmlCtrl by Paul DiLascia. Paul DiLascia создал класс CHtmlCtrl (производный от CHtmlView), который можно разместить на диалоге.
Созданный мной класс СHtmlDialog решает две задачи – установку имени диалогового окна и его размеров. Эти параметры указаны в HTML-страничке. Использование класса СHtmlDialog:
1. Вставить в ресурсы диалоговое окно и разместить на нем STATIC-control (вместо этого control-а будет HTML-страничка). На основании этого диалога создать MFC-класс (унаследованный от CDialog).
3. В заголовочном файле (например, Dlg4.h) изменить базовый класс на CHtmlDialog.
class CDlg4 : public CHtmlDialog // Теперь наследуем класс от CHtmlDialog { ... |
4. В CPP файле (например Dlg4.cpp) в конструкторе класса Dlg4 добавить вызов конструктора CHtmlDialog :
CDlg4::CDlg4(CWnd* pParent /*=NULL*/) // передача ресурса с HTML страничкой : CHtmlDialog(CDlg4::IDD,pParent, IDR_HTML4, IDC_STATIC1) { //{{AFX_DATA_INIT(CDlg4) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT } |
Класс СHtmlDialog позволяет также изменять размеры окна из HTML-кода (см. _onHtmlCmd в СHtmlDialog).
Итак, почти все готово, но остаются еще две проблемы:
- Окно, которое отображает HTML в приложении (ActiveX-элемент), всегда будет обрамлено «вдавленной» рамкой, и это никак нельзя изменить. Явная установка атрибутов окна через SetWindowLong(GWL_STYLE) не помогает. Выглядит это не очень красиво.
- Если пользователь изменит свойства отображения HTML (свойства Internet Explorer -> вкладка General, кнопки Colors, Fonts, …), то это скажется на отображении HTML в приложении, т.е. шрифты и цвета изменятся. Это никого не устраивает. Пользователь приложения может просто его не узнать при очередном запуске.
Если первое еще можно перетерпеть, то вторая проблема очень серьезна. Возможна такая ситуация, что приложение будет выглядеть по-разному в зависимости от настроек IE у пользователя.
Чтобы преодолеть эти проблемы, необходимо реализовать поддержку Advanced Hosting Interfaces.
ПРИМЕЧАНИЕ Возможно, несколько проще это сделать с помощью CSS – прим. ред. |
Advanced Hosting Interfaces (AHI) – это COM-интерфейсы ActiveX-элемента WebBrowser (начиная с версии 4.0). Они помогают получить полный контроль над элементом WebBrowser. Реализация AHI в MFC-приложениях и преимущества этих интерфейсов хорошо продемонстрированы в примерах к http://www.beginthread.com/Contributor/Ehsan/ViewAllArticles.
Описанные проблемы решаются переопределением событий OnGetHostInfo и OnGetOptionKeyPath (см. Html_Host_Handlers.cpp).
На этом я поставлю точку. Спасибо за внимание.
Ссылки
- Thomas Aust. Статья «HTML—The MFC-Way.…»
- MSDN Library, October 2001.
- Ветка - Platform SDK Documentation \ Web Development \ Internet Development SDK \ HTML and Dynamicс HTML , разделы: HTML and Dynamicс HTML, DHTML Reference, Programming and Reusing the Browser
- Paul DiLascia. Microsoft Systems Journal, January 2000, Q&A C++, CHtmlCtrl by Paul DiLascia.
- Eugene Khodakovsky. Статья «JavaScript Calls from C++»
- Статья «Автоматизация и моторизация приложения. Акт Второй. Броузер в каждом окне», Николай Куртов
- Ehsan Akhgari. Статьи по DHTML в MFC и Advanced Hosting Interfaces, http://www.beginthread.com/Contributor/Ehsan/ViewAllArticles
Книги по JScript/VBScript и DHTML :
- Web программирование на Java и JavaScript , Гарнаев А, Гарнаев С, BHV 2002 год.
- HTML 4.0 , Матросов А, Сергеев А, Чаунин А, BHV 2001 год.