Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Операционная система Microsoft Windows 3.1 для программиста. Дополнительные главы

© Александр Фролов, Григорий Фролов
Том 17, М.: Диалог-МИФИ, 1994, 287 стр.

[Назад] [Содеожание] [Дальше]

4.6. Встроенное окно

При создании справочной базы данных с использованием приложения winhelp.exe существует малоизвестная возможность создания встроенных окон, внутри которых разработчик справочной системы может делать практически все что угодно, за исключением ввода с клавиатуры (так как встроенное окно не может получить фокус ввода).

Процедура создания встроенных окон не описана в документации, поставляющейся вместе с SDK, однако необходимая информация есть в руководстве к утилите "Windows Help Authoring Guide for Word for Windows 2.0", которое поставляется на компакт-диске MSDN CD ("Microsoft Developer Network. Development Library").

Для чего можно использовать встроенное окно?

Можно придумать различные применения, например, следующие.

Отображение анимации и видеофильмов. Для увеличения привлекательности и наглядности справочной системы можно дополнить ее не только статическими графическими изображениями, но и видеофрагментами, например, в формате avi-файлов. Встроенное окно может содержать органы управления и окно просмотра видеофрагментов.

Изображение стандартных или нестандартных органов управления непосредственно в тексте раздела или в несворачиваемой области. Такие органы управления можно использовать как для организации гипертекстовых ссылок, так и для выполнения нестандартных функций, таких, как копирование файлов из справочной системы на диск и т. п.

Создание встроенного окна

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

{ewl dllname, wndclass, param}

Как и при вставке bmp-файлов, в зависимости от выбранной команды будет использован один из трех способов взаимного расположения текста и встроенного окна в окне раздела:

Команда Способ расположения
ewc Встроенное окно будет вставлено в строку как символ
ewl Встроенное окно выравнивается по левой границе, текст располагается справа
ewr Встроенное окно выравнивается по правой границе, текст располагается слева

Параметр dllname - это имя DLL-библиотеки, которую требуется разработать специально для обслуживания встроенного окна. При инициализации функция LibMain этой библиотеки должна зарегистрировать класс окна wndclass, указанный во втором операторе команды вставки встроенного окна.

Функция окна, соответствующая классу wndclass и определенная в библиотеке dllname будет получать и обрабатывать сообщения, предназначенные для встроенного окна.

Через параметр param можно передать любую текстовую строку, которая будет доступна на этапе создания встроенного окна при обработке сообщения WM_CREATE.

Сообщения для встроенного окна

Функция встроенного окна ведет себя как дочернее окно главного окна приложения winhelp.exe (или вторичного окна). В частности, при создании функция окна получает сообщение WM_CREATE, а при уничтожении - WM_DESTROY. Есть также ряд сообщений, специфических для встроенного окна.

Сообщение WM_CREATE

Сообщение WM_CREATE может попадать в функцию встроенного окна несколько раз за время одного сеанса работы со справочной системой. Если пользователь отображает раздел, содержащий встроенное окно, оно создается. Затем это окно может быть уничтожено при переходе к другому разделу и создано заново при повторном отображении того же раздела.

Когда функция встроенного окна получает сообщение WM_CREATE, параметр lParam содержит указатель на структуру CREATESTRUCT. Поле lpCreateParams этой структуры, в свою очередь, содержит указатель на структуру EWDATA следующего вида (не описана в файле windows.h):

typedef struct tagCreateInfo 
{
  short   idMajVersion; // ст. версия winhelp.exe
  short   idMinVersion; // мл. версия winhelp.exe
  LPSTR   szFileName;   // путь к hlp-файлу
  LPSTR   szAuthorData; // строка параметров команды ewl
  HANDLE  hfs;          // идентификатор файловой системы
  DWORD   coFore;  // основной цвет главного окна winhelp.exe
  DWORD   coBack;  // фоновый  цвет главного окна winhelp.exe
} EWDATA;

Microsoft рекомендует проверить поля idMajVersion и idMinVersion (старший и младший номер версии справочной системы winhelp.exe) на равенство нулю. Если содержимое этих полей не равно нулю, работоспособность вашей DLL-библиотеки с этой версией winhelp.exe не гарантируется.

Поле szFileName содержит указатель на текстовую строку, содержащую путь к hlp-файлу. Если эта строка вам нужна, ее надо скопировать в отдельный буфер на этапе обработки сообщения WM_CREATE.

Через поле szAuthorData передается указатель на текстовую строку, указанную при вставке встроенного окна в раздел, т. е. строка param в команде ewl, ewc или ewr. Если эта строка нужна, ее также следует сохранить на этапе обработки сообщения WM_CREATE.

Через параметр hfs передается идентификатор файловой системы hlp-файла, который может пригодиться при выполнении операций с данными, описанными в секции BAGGAGE файла проекта справочной системы. Подробное рассмотрение этих операций выходит за рамки нашей книге, однако несколько слов по этому поводу мы скажем в конце текущей главы.

И, наконец, через параметры coFore и coBack передается основной и фоновый цвет главного окна winhelp.exe, соответственно. Вы можете использовать эти значения для раскраски создаваемого встроенного окна.

Так как отображение встроенного окна выполняется приложением winhelp.exe, ваше приложение не должно вызывать для этого окна функцию ShowWindow или назначать для него стиль WS_VISIBLE.

Сообщение EWM_QUERYSIZE

Функция встроенного окна получает сообщение EWM_QUERYSIZE (с кодом 0x706b) от приложения winhelp.exe.

Параметр wParam содержит идентификатор контекста отображения для окна. Через параметр lParam передается указатель на структуру POINT.

Основная задача обработчика сообщения EWM_QUERYSIZE заключается в том, чтобы записать в поля x и y структуры POINT, соответственно, ширину и высоту встроенного окна. Таким образом, встроенное окно должно сообщить приложению winhelp.exe свои размеры.

Сообщение EWM_RENDER

Содержимое раздела справочной системы может быть скопировано в Clipboard или распечатано на принтере. В первом случае приложению winhelp.exe требуется получить текстовое представление содержимого встроенного окна, во втором - графическое в виде битового изображения.

Для получения текстового и графического представления приложение winhelp.exe посылает в функцию встроенного окна сообщение EWM_RENDER (с кодом 0x706a). Если параметр wParam этого сообщения равен константе CF_TEXT, обработчик должен вернуть текстовое представление, а если CF_BITMAP - графическое в виде битового изображения.

Процедура возврата текстового представления достаточно проста. Она заключается в том, что обработчик сообщения EWM_RENDER создает и фиксирует глобальный блок памяти. Затем он записывает в этот блок памяти текстовую строку представления содержимого встроенного окна, расфиксирует блок памяти и возвращает идентификатор блока памяти. Освобождение блока памяти выполняет приложение winhelp.exe, вам не нужно беспокоиться по этому поводу.

Если требуется вернуть графическое представление, параметр lParam сообщения EWM_RENDER содержит указатель на структуру следующего вида (не описана в файле windows.h):

typedef struct tagRenderInfo
{
  RECT rc;
  HDC  hdc;
} RENDERINFO;

Поле hdc этой структуры нужно использовать для создания битового изображения функцией CreateCompatibleBitmap.

Что же касается размеров битового изображения, их следует получить при помощи сообщения EWM_QUERYSIZE.

Сообщение EWM_ASKPALETTE

Приложение winhelp.exe посылает сообщение EWM_ASKPALETTE (с кодом 0x706c) функции встроенного окна для того чтобы получить информацию об используемой цветовой палитре.

Обработчик сообщения EWM_ASKPALETTE должен вернуть идентификатор палитры, используемой встроенным окном.

Сообщение EWM_FINDNEWPALETTE

Сообщение EWM_FINDNEWPALETTE (с кодом 0x706d) нигде не документировано, однако оно встречается в *.h файлах примеров, поставляемых в составе средств разработки справочных систем на базе Microsoft Windows Help.

Библиотека HELPMORE.DLL

В этом разделе мы приведем исходные тексты DLL-библиотеки, предназначенной для работы совместно со справочной системой helpmore.hlp, описанной нами ранее.

Основной файл исходного текста приведен в листинге 4.7.


Листинг 4.7. Файл hlpmore/hlpmore.cpp


#define  STRICT
#include <windows.h>
#include <mem.h>
#include <time.h>

#include "hlpmore.h"

BOOL RegisterEWclass(HMODULE hModule);
extern "C" LRESULT CALLBACK _export
EWWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
char const szClassName[]   = "EWHlpMoreClass";

// ========================================================
// Функция LibMain
// Получает управление только один раз при
// загрузке DLL-библиотеки в память
// ========================================================
#pragma argsused
int FAR PASCAL
LibMain(HINSTANCE hModule, WORD wDataSegment,
  WORD wHeapSize, LPSTR lpszCmdLine)
{
  // После инициализации локальной области данных
  // функция LibEntry фиксирует сегмент данных.
  // Его необходимо расфиксировать.
  if(wHeapSize != 0)
    // Расфиксируем сегмент данных
    UnlockData(0);

  // Регистрируем класс для встроенного окна
  if(!RegisterEWclass(hModule))
    return FALSE;
  else
    // Возвращаем TRUE. Это означает, что инициализация
    // DLL-библиотеки выполнена успешно
    return TRUE;
}

// ========================================================
// Функция WEP
// Получает управление только один раз при
// удалении DLL-библиотеки из памяти
// ========================================================
#pragma argsused
int FAR PASCAL WEP(int bSystemExit)
{
  return 1;
}

// ========================================================
// Функция MsgBox
// Выводит на экран диалоговую панель с сообщением
// ========================================================
extern "C" void FAR PASCAL _export
MsgBox(HWND hwnd, LPSTR szMsg)
{
  MessageBox(hwnd, szMsg, "Message from DLL", MB_OK);
}

// ========================================================
// Функция RegisterEWclass
// Регистрация класса для встроенного окна
// ========================================================
BOOL RegisterEWclass(HMODULE hModule)
{
  ATOM aWndClass;
  WNDCLASS wc;

  memset(&wc, 0, sizeof(wc));

  // Класс окна доступен для всех приложений
  wc.style         = CS_GLOBALCLASS;

  wc.lpszMenuName  = NULL;
  wc.lpfnWndProc   = (WNDPROC) EWWndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hModule;
  wc.hIcon         = NULL;
  wc.hCursor       = LoadCursor(hModule, "AppCursor");
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszClassName = (LPSTR)szClassName;

  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}

// ========================================================
// Функция EWWndProc
// Функция встроенного окна
// ========================================================
extern "C" LRESULT CALLBACK _export
EWWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
  RECT rc;
  TEXTMETRIC tm;
  int cxChar, cyChar;
  QCI  qci;
  QRI  qri;
  static BYTE szCustomData[80];
  static BYTE szBuf[80];

  #define CLOCK_TIMER 1

  switch (msg)
  {
    // Создание встроенного окна
    case WM_CREATE:
    {
      // Копируем строку параметров
      qci = (QCI)((CREATESTRUCT FAR *)lParam)->lpCreateParams;
      if(lstrlen(qci->szAuthorData) < 80)
        lstrcpy(szCustomData, qci->szAuthorData);

      // Добавляем к встроенному окну рамку 
      SetWindowLong(hwnd, GWL_STYLE,
        GetWindowLong(hwnd,GWL_STYLE) | WS_BORDER);

      // Создаем таймер для часов
      SetTimer(hwnd, CLOCK_TIMER, 1000, NULL);
      return 0;
    }

    // Сообщение от таймера
    case WM_TIMER:
    {
      // Перерисовываем встроенное окно
      InvalidateRect(hwnd, NULL, FALSE);
      return 0;
    }

    // Обработчик сообщения WM_PAINT
    case WM_PAINT:
    {
      int    nBufSize;
      time_t t;
      struct tm *ltime;
      RECT    rc;

      hdc = BeginPaint(hwnd, &ps);

      // Определяем время и его отдельные компоненты
      time(&t);
      ltime = localtime(&t);

      // Подготавливаем буфер, заполняя его
      // строкой с текущим временем
      nBufSize = wsprintf(szBuf, "%02d:%02d:%02d",
        ltime->tm_hour, ltime->tm_min, ltime->tm_sec);

      // Выбираем шрифт с фиксированной шириной букв
      SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

      // Получаем координаты и размер окна
      GetClientRect(hwnd, &rc);

      // Выбираем цвет текста для часов
      SetTextColor(ps.hdc, RGB(0,100,0));

      // Выводим время в центре окна
      DrawText(hdc, (LPSTR)szBuf, nBufSize, &rc,
        DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_SINGLELINE);

      EndPaint(hwnd, &ps);
      return 0;
    }

    // Обработчик этого сообщения должен определить
    // размеры встроенного окна
    case EWM_QUERYSIZE:
    {
      // Определяем метрики фиксированного шрифта
      hdc = GetDC(hwnd);
      SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
      GetTextMetrics(hdc, &tm);
      cxChar = tm.tmMaxCharWidth;
      cyChar = tm.tmHeight + tm.tmExternalLeading;
      ReleaseDC(hwnd, hdc);

      // Сохраняем размеры встроенного окна
      ((LPPOINT)lParam)->x = 10 * cxChar;
      ((LPPOINT)lParam)->y = 2  * cyChar;

      return 1;
    }

    // В ответ на это сообщение функция встроенного окна
    // должна вернуть представление содержимого окна
    // в виде текста или битового изображения
    case EWM_RENDER:
    {
      long lPtr = 0l;

      switch(wParam)
      {
        // Представление содержимого в виде текста
        case CF_TEXT:
        {
          // Заказываем глобальный блок памяти
          HGLOBAL hglb = GlobalAlloc(GMEM_MOVEABLE, 50);
          if(hglb)
          {
            // Фиксируем блок памяти
            LPSTR lpszTempBuf = (LPSTR)GlobalLock(hglb);

            // Копируем строку, содержащую текущее время
            lstrcpy(lpszTempBuf,"\r\n");
            lstrcat(lpszTempBuf,szBuf);
            lstrcat(lpszTempBuf,"\r\n");

            // Расфиксируем блок памяти
            GlobalUnlock(hglb);

            // Возвращаем идентификатор блока памяти,
            // содержащего текстовое представление
            lPtr = (long)hglb;
          }
          break;
        }

        // Представление содержимого
        // в виде битового изображения 
        case CF_BITMAP:
        {
          POINT pt;
          RECT rc;
          HBITMAP hbmp, hbmpOld;
          HBRUSH hbrush;

          // Сохраняем указатель на структуру RENDERINFO
          qri = (QRI)lParam;

          // Создаем контекст отображения, совместимый
          // с экраном монитора
          hdc = CreateCompatibleDC(NULL);

          // Определяем размеры встроенного окна
          SendMessage(hwnd, EWM_QUERYSIZE,
            (WPARAM)hdc, (long)(LPPOINT)&pt);

          // Создаем битовое изображение, имеющее размеры,
          // равные размерам встроенного окна 
          rc.left = rc.top = 0;
          rc.right = pt.x;
          rc.bottom = pt.y;
          hbmp = CreateCompatibleBitmap(qri->hdc, pt.x, pt.y);

          // Выбираем битовое изображение
          // в контекст отображения
          hbmpOld = (HBITMAP)SelectObject(hdc,hbmp);

          // Закрашиваем битовое изображение цветом фона
          hbrush = CreateSolidBrush(GetBkColor(hdc));
          FillRect(hdc, &rc, hbrush);
          DeleteObject(hbrush);

          // Рисуем рамку по периметру битового изображения
          Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);

          // Рисуем время в центре битового изображения
          DrawText(hdc, (LPSTR)szBuf, lstrlen(szBuf), &rc,
            DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_SINGLELINE);

          // Выбираем старое битовое изображение
          hbmp = (HBITMAP)SelectObject(hdc, hbmpOld);
          DeleteDC(hdc);

          // Возвращаем идентификатор созданного
          // битового изображения
          lPtr = (long)hbmp;

          break;
        }
      }

      return lPtr;
    }

    // Обрабатываем сообщение от левой клавиши мыши
    case WM_LBUTTONDOWN:
    {
      if(IDYES == MessageBox(hwnd, "Запустить clock.exe?",
       "Help Sample", MB_YESNO | MB_ICONQUESTION))
        WinExec("clock.exe", SW_SHOW);

      #define IDN_CLOCK 200

      // Во временном окне отображаем раздел справочной
      // системы с номером контекста, равным IDN_CLOCK
      WinHelp(hwnd, "hlpmore.hlp", HELP_CONTEXTPOPUP,
          (DWORD)IDN_CLOCK);

      return 0;
    }

    // При удалении встроенного окна уничтожаем таймер
    case WM_DESTROY:
    {
      KillTimer(hwnd, CLOCK_TIMER);
      return 0;
    }

    default:
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Функция WinMain получает управление при загрузке DLL-библиотеки в память. Ее основная задача - регистрация класса встроенного окна, для чего вызывается функция RegisterEWclass, определенная в этой же библиотеке.

Функция RegisterEWclass выполняет обычную процедуру регистрации класса, однако при регистрации используется стиль CS_GLOBALCLASS. Класс, зарегистрированный с этим стилем, доступен для использования всеми запущенными приложениями - это глобальный класс.

Далее в исходном тексте DLL-библиотеки определена функция MsgBox, которая не имеет никакого отношения к встроенным окнам и используется как дополнительная макрокоманда, вызываемая при помощи ссылки из раздела оглавления. Задача функции MsgBox заключается в отображении сообщения, полученного ей через второй параметр.

Функция EWWndProc выполняет роль функции встроенного окна. Она обрабатывает все сообщения, поступающие от приложения winhelp.exe при создании, в процессе работы и при удалении встроенного окна.

Обработчик сообщения WM_CREATE копирует строку параметров, добавляет к окну рамку и создает таймер с периодом работы 1 сек.

Каждую секунду от таймера в функцию встроенного окна приходит сообщение WM_TIMER. Обработка этого сообщения заключается в полной перерисовке содержимого встроенного окна.

Обработчик сообщения WM_PAINT вызывается при создании окна и затем периодически, раз в секунду. Он рисует в центре окна текстовую строку текущего времени.

Обработчик сообщения EWM_QUERYSIZE возвращает размер окна, определяя его на основании метрик фиксированного системного шрифта.

Как мы уже говорили, когда приложению winhelp.exe требуется получить текстовое или графическое представление содержимого встроенного окна, оно посылает функции окна сообщение EWM_RENDER.

При запросе текстового представления наше приложение предоставляет идентификатор блока, содержащего текстовую строку, в которой записано текущее время. При запросе графического представления приложение создает битовое изображение и рисует на нем тот же самый текст, обводя его рамкой по периметру окна.

Все выполняемые при этом действия были описаны нами ранее в предыдущих томах "Библиотеки системного программиста", поэтому они должны быть понятны без дополнительных объяснений.

Обратим ваше внимание на обработчик сообщения WM_LBUTTONDOWN.

Этот обработчик был определен для обеспечения возможности использования встроенного окна для организации чувствительной точки (в исходном тексте раздела встроенное окно нельзя использовать для организации ссылки или запуска макрокоманд).

Когда пользователь расположит курсор мыши над поверхностью встроенного окна и нажмет левую клавишу мыши, функция встроенного окна получит обычное сообщение WM_LBUTTONDOWN. Обработчик этого сообщения выводит диалоговую панель с предложением запустить приложение clock.exe, при необходимости запускает это приложение, а затем вызывает функцию WinHelp.

Функция WinHelp используется для организации гипертекстовой ссылки на раздел с номером контекста IDN_CLOCK. Раздел будет отображен во временном окне.

Идентификаторы констант, а также все необходимые структуры данных определены в файле hlpmore.h (листинг 4.8).


Листинг 4.8. Файл hlpmore/hlpmore.h


#define EWM_RENDER         0x706A
#define EWM_QUERYSIZE      0x706B
#define EWM_ASKPALETTE     0x706C
#define EWM_FINDNEWPALETTE 0x706D

typedef struct tagCreateInfo 
{
  short   idMajVersion;
  short   idMinVersion;
  LPSTR   szFileName;
  LPSTR   szAuthorData;
  HANDLE  hfs;
  DWORD   coFore;
  DWORD   coBack;
} EWDATA, FAR* QCI;

typedef struct tagRenderInfo
{
  RECT rc;
  HDC  hdc;
} RENDERINFO, FAR* QRI;

Файл описания ресурсов содержит ссылку на файл hlpmore.cur (листинг 4.9). Файл hlpmore.cur содержит курсор, который используется для встроенного окна.


Листинг 4.9. Файл hlpmore/hlpmore.rc


AppCursor CURSOR hlpmore.cur

Файл определения модуля DLL-библиотеки hlpmore.dll приведен в листинге 4.10.


Листинг 4.10. Файл hlpmore/hlpmore.def


LIBRARY        HLPMORE
DESCRIPTION    'DLL-библиотека HLPMORE, (C) 1995, Frolov A.V.'
EXETYPE        windows
CODE           preload moveable discardable
DATA           preload moveable single
HEAPSIZE       1024

[Назад] [Содеожание] [Дальше]