Источник: rsdn.ru/article/mfc/reusingdhtml.xml

R U S S I A N   S O F T W A R E   D E V E L O P E R   N E T W O R K
 

Использование web-интерфейса в MFC-приложениях

Введение

В данной статье описаны способы использования MFC-класса CHtmlView и технологии DHTML для создания web-интерфейса в MFC приложении. Под формулировкой «приложение с Web-интерфейсом» я подразумеваю то, что пользовательский интерфейс или его часть в приложении создан на основе HTML. При попытке применить такой интерфейс в MFC-приложении я столкнулся с следующими проблемами:

Для решения таких вопросов MSDN (см. Handling HTML Element Events) советует применять COM-интерфейсы DHTML. Мне показалось, что это плохая альтернатива классической модели взаимодействия с GUI-интерфейсом в MFC (карты событий, и прямое манипулирование через SendMessage или объекты элементов GUI).

В этой статье приводится описание некоторых методик, облегчающих создание Web-интерфейса и работу с ним из MFC-приложений, а именно:

В качестве примера рассмотрим задачу разработки 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-страничке:

ПРИМЕЧАНИЕ

Последние два пункта довольно спорны – так как неудобства пользователю они причинить могут, а пользу – вряд ли. В общем, на вкус и цвет... – прим.ред.

Далее приведен 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).

Итак, почти все готово, но остаются еще две проблемы:

Если первое еще можно перетерпеть, то вторая проблема очень серьезна. Возможна такая ситуация, что приложение будет выглядеть по-разному в зависимости от настроек 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).

На этом я поставлю точку. Спасибо за внимание.

Ссылки

  1. Thomas Aust. Статья «HTML—The MFC-Way.…»
  2. MSDN Library, October 2001.
  3. Ветка - Platform SDK Documentation \ Web Development \ Internet Development SDK \ HTML and Dynamicс HTML , разделы: HTML and Dynamicс HTML, DHTML Reference, Programming and Reusing the Browser
  4. Paul DiLascia. Microsoft Systems Journal, January 2000, Q&A C++, CHtmlCtrl by Paul DiLascia.
  5. Eugene Khodakovsky. Статья «JavaScript Calls from C++»
  6. Статья «Автоматизация и моторизация приложения. Акт Второй. Броузер в каждом окне», Николай Куртов
  7. Ehsan Akhgari. Статьи по DHTML в MFC и Advanced Hosting Interfaces, http://www.beginthread.com/Contributor/Ehsan/ViewAllArticles

Книги по JScript/VBScript и DHTML :

  1. Web программирование на Java и JavaScript , Гарнаев А, Гарнаев С, BHV 2002 год.
  2. HTML 4.0 , Матросов А, Сергеев А, Чаунин А, BHV 2001 год.


Эта статья опубликована в журнале RSDN Magazine #1-2004. Информацию о журнале можно найти здесь