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

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

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

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

2.3. Просмотр содержимого Clipboard

В этом разделе мы расскажем о том, как создать приложение, аналогичное стандартному приложению Clipboard операционной системы Windows 3.1. На примере исходных текстов приложения CLIPSHOW вы научитесь создавать окна, предназначенные для динамического просмотра содержимого Clipboard, научитесь извлекать и рисовать данные в формате битовых изображений с цветовыми палитрами, а также в формате метафайла.

Окно просмотра Clipboard

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

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

Если вы создаете приложение, главное окно которого должно выполнять функции просмотра Clipboard, при создании этого окна (во время обработки сообщения WM_CREATE) следует вызывать функцию SetClipboardViewer, передав ей идентификатор окна:

hwndNextViewer = SetClipboardViewer(hwnd);

Функция SetClipboardViewer включит окно, идентификатор которого передается ей через единственный параметр, в список окон просмотра Clipboard, вернув идентификатор предыдущего окна просмотра Clipboard hwndNextViewer. Вы должны сохранить возвращенное значение, так как функция вашего окна просмотра Clipboard будет передавать по цепочке сообщения для других окон просмотра Clipboard, пользуясь значением hwndNextViewer.

Если окно вашего приложения является единственным окном просмотра Clipboard, функция SetClipboardViewer вернет значение NULL.

Перед завершением работы приложение должно восстановить список окон просмотра Clipboard, удалив из него свое окно. Эту процедуру следует выполнить при обработке сообщения WM_DESTROY при помощи функции ChangeClipboardChain:

ChangeClipboardChain(hwnd, hwndNextViewer);

В качестве первого параметра функции передается идентификатор окна, удаляемого из списка окон просмотра Clipboard, в качестве второго - идентификатор окна, полученный ранее от функции SetClipboardViewer.

Самое последнее окно, для которого была вызвана функция SetClipboardViewer, становится текущим окном просмотра Clipboard. Текущее окно просмотра получает сообщения, связанные с изменением состояния Clipboard, и передает их по цепочке.

Когда содержимое Clipboard изменяется, текущее окно просмотра получает сообщение WM_DRAWCLIPBOARD. Функция окна просмотра предает это сообщение по цепочке с помощью функции SendMessage и перерисовывает внутреннюю область окна, вызывая функцию InvalidateRect:

case WM_DRAWCLIPBOARD:
{
  if(hwndNextViewer)
    SendMessage(hwndNextViewer, msg, wParam, lParam);
  InvalidateRect(hwnd, NULL, TRUE);
  return 0;
}

Чтение и рисование нового содержимого Clipboard выполняется при обработке сообщения WM_PAINT, которое записывается в очередь приложения в результате вызова функции InvalidateRect.

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

При любых изменениях в списке окон просмотра функция окна просмотра получит от Windows сообщение WM_CHANGECBCHAIN. Параметр wParam этого сообщения будет содержать идентификатор окна, удаляемого из списка окон просмотра. Младшее слово параметра lParam будет содержать идентификатор следующего окна просмотра в цепочке.

Обработка сообщения WM_CHANGECBCHAIN может быть выполнена следующим образом:

case WM_CHANGECBCHAIN:
{
  if(wParam == (WPARAM)hwndNextViewer)
    hwndNextViewer = (HWND)LOWORD(lParam);
  else if(hwndNextViewer)
    SendMessage(hwndNextViewer, msg, wParam, lParam);
  return 0;
}

Если удаляется следующее (по отношению к вашему) окно просмотра, значение параметра wParam равно значению, которое хранится в переменной hwndNextViewer. В этом случае мы записываем в переменную hwndNextViewer новое значение для идентификатора следующего в цепочке окна просмотра.

Если же удаляется другое окно, сообщение WM_CHANGECBCHAIN передается без изменений следующему окну. В том случае, когда следующего окна нет (наше окно является последним в цепочке окон просмотра Clipboard), содержимое переменной hwndNextViewer равно NULL, и нам не надо передавать это сообщение дальше.

Приложение CLIPSHOW

Приложение CLIPSHOW является упрощенным аналогом стандартного приложения Clipboard, предназначенного для динамического просмотра содержимого Clipboard (рис. 2.5).

Рис. 2.5. Главное окно приложения CLIPSHOW

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


Листинг 2.7. Файл clipshow/clipshow.cpp


// ----------------------------------------
// Просмотр Clipboard
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <mem.h>
#include "clipshow.hpp"

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawBitmap(HDC, int, int, HBITMAP);
void PrepareMetaFile(HDC, LPMETAFILEPICT, int, int);
void SizeToHiMetric(int*, int*);
void HiMetricToSize(int*, int*);
void HiMetricToSizeScaled(int*, int*, int, int);

char const szClassName[]   = "ClipShowClass";
char const szWindowTitle[] = "Clipboard Show";
// =====================================
// Функция WinMain
// =====================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpszCmdLine, int nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

// Не разрешаем запуск нескольких копий приложения
  if(hPrevInstance)
    return FALSE;

  if(!InitApp(hInstance))
    return FALSE;

  hwnd = CreateWindow(
    szClassName, szWindowTitle,
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT,
    CW_USEDEFAULT, CW_USEDEFAULT,
    0, 0, hInstance, NULL);
  if(!hwnd)
    return FALSE;

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  while(GetMessage(&msg, 0, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}
// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================
BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна
  memset(&wc, 0, sizeof(wc));
  wc.lpszMenuName  = "APP_MENU";
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = (WNDPROC) WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(hInstance, "APP_ICON");
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszClassName = (LPSTR)szClassName;

  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}
// =====================================
// Функция WndProc
// =====================================
LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
  RECT rc;
  static HWND hwndNextViewer;
  HGLOBAL hglbClipData;
  LPSTR   lpszClipBuf;
  HBITMAP hBitmap;
  static HPALETTE hPal, hOldPal;
  static int cxClient, cyClient;
  HMETAFILE hmf;
  LPMETAFILEPICT lpmfp;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Наше окно становится окном просмотра Clipboard
      hwndNextViewer = SetClipboardViewer(hwnd);
      return 0;
    }
    case WM_SIZE:
    {
      cxClient = LOWORD(lParam);
      cyClient = HIWORD(lParam);
      return 0;
    }
    case WM_PAINT:
    {
      hdc = BeginPaint(hwnd, &ps);
      GetClientRect(hwnd, &rc);

      // Открываем Clipboard
      OpenClipboard(hwnd);

      // Читаем данные в формате метафайла
      hmf = (HMETAFILE)GetClipboardData(CF_METAFILEPICT);
      if(hmf != NULL)
      {
        // Фиксируем память заголовка метафайла
        lpmfp = (LPMETAFILEPICT)GlobalLock(hmf);
        if(lpmfp != NULL)
        {
          // Сохраняем контекст отображения
          SaveDC(hdc);

          // Устанавливаем параметры контекста отображения
          // в соответствии с содержимым заголовка метафайла
          PrepareMetaFile(hdc, lpmfp, cxClient, cyClient);

          // Проигрываем метафайл 
          PlayMetaFile(hdc, lpmfp->hMF);

          // Восстанавливаем контекст отображения
          RestoreDC(hdc, -1);

          // Освобождаем память заголовка метафайла
          GlobalUnlock(hmf);
        }
      }
      else
      {
        // Читаем данные в формате CF_BITMAP
        hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP);
        if(hBitmap != NULL)
        {
          // Читаем палитру
          hPal = (HPALETTE)GetClipboardData(CF_PALETTE);
          if(hPal)
          {
            // Если Clipboard содержит палитру,
            // выбираем и реализуем ее
            hOldPal = SelectPalette(hdc, hPal, FALSE);
            RealizePalette(hdc);
          }
          // Рисуем изображение в окне
          DrawBitmap(hdc, 0, 0, hBitmap);
        }
        // Читаем текстовые данные
        else
        {
          hglbClipData = GetClipboardData(CF_TEXT);
          if(hglbClipData)
          {
            // Фиксируем блок памяти и рисуем текст
            lpszClipBuf = (LPSTR)GlobalLock(hglbClipData);
            DrawText(hdc, lpszClipBuf, -1, &rc,
              DT_EXPANDTABS);

            // Расфиксируем блок памяти
            GlobalUnlock(hglbClipData);
          }
        }
      }
      // Закрываем Clipboard
      CloseClipboard();
      EndPaint(hwnd, &ps);
    }
    // Произошли изменения в цепочке окон просмотра
    // Clipboard
    case WM_CHANGECBCHAIN:
    {
      // Если идентификатор удаляемого окна просмотра
      // равен идентификатору следующего окна в цепочке,
      // запоминаем идентификатор нового следующего
      // окна просмотра
      if(wParam == (WPARAM)hwndNextViewer)
        hwndNextViewer = (HWND)LOWORD(lParam);

      // Передаем сообщение следующему окну просмотра
      // по цепочке
      else if(hwndNextViewer)
        SendMessage(hwndNextViewer, msg, wParam, lParam);
      return 0;
        
    }
    // Содержимое Clipboard изменилось, наше приложение
    // должно перерисовать свое окно
    case WM_DRAWCLIPBOARD:
    {
      // Если наше окно не последнее в цепочке,
      // передаем сообщение дальше по цепочке
      if(hwndNextViewer)
        SendMessage(hwndNextViewer, msg, wParam, lParam);
      InvalidateRect(hwnd, NULL, TRUE);
      return 0;
    }
    case WM_COMMAND:
    {
      switch (wParam)
      {
        case CM_HELPABOUT:
        {
           MessageBox(hwnd,
             "Приложение CLIPSHOW\n(C) Фролов А.В., 1995",
             (LPSTR)szWindowTitle, 
             MB_OK | MB_ICONINFORMATION);
          return 0;
        }
        case CM_FILEEXIT:
        {
          DestroyWindow(hwnd);
          return 0;
        }
        default:
          return 0;
      }
    }
    // Отслеживаем изменения палитры
    case WM_PALETTECHANGED:
    {
      if(hwnd == (HWND) wParam)
        break;
    }
    case WM_QUERYNEWPALETTE:
    {
      int nChanged;

      hdc = GetDC(hwnd);
      hOldPal = SelectPalette(hdc, hPal,
        (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);
      nChanged = RealizePalette(hdc);
      SelectPalette(hdc, hOldPal, TRUE);
      ReleaseDC(hwnd, hdc);
      if(nChanged)
        InvalidateRect(hwnd, NULL, TRUE);
      return nChanged;
    }
    // При завершении работы приложения изменяем
    // цепочку окон просмотра Clipboard,
    // удаляя из нее окно нашего приложения
    case WM_DESTROY:
    {
      ChangeClipboardChain(hwnd, hwndNextViewer);
      PostQuitMessage(0);
      return 0;
    }
    default:
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}
// =====================================
// Функция DrawBitmap
// =====================================
void
DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap)
{
  HBITMAP hbm, hOldbm;
  HDC hMemDC;
  BITMAP bm;
  POINT  ptSize, ptOrg;

  hMemDC = CreateCompatibleDC(hDC);
  hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);
  if (hOldbm)
  {
    SetMapMode(hMemDC, GetMapMode(hDC));
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);

    ptSize.x = bm.bmWidth;   // ширина
    ptSize.y = bm.bmHeight;  // высота
    DPtoLP(hDC, &ptSize, 1);

    ptOrg.x = 0;
    ptOrg.y = 0;
    DPtoLP(hMemDC, &ptOrg, 1);

    BitBlt(hDC, x, y, ptSize.x, ptSize.y,
           hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);
    SelectObject(hMemDC, hOldbm);
  }
  DeleteDC(hMemDC);
}
// =====================================
// Функция PrepareMetaFile
// =====================================
void
PrepareMetaFile(HDC hdc, LPMETAFILEPICT lpmfp,
  int cxClient, int cyClient)
{
  int x, y;

  // Устанавливаем такой режим отображения,
  // какой указан в заголовке метафайла
  SetMapMode(hdc, lpmfp->mm);

  // Для изотропного и анизотропного режима
  // анализируем поля xExt и yExt заголовка
  if(lpmfp->mm == MM_ISOTROPIC ||
     lpmfp->mm == MM_ANISOTROPIC)
  {
    // Если xExt равен нулю, устанавливаем
    // размеры изображения равными размерам
    // внутренней области окна
    if(lpmfp->xExt == 0)
      SetViewportExtEx(hdc, cxClient, cyClient, NULL);

    // Если xExt больше нуля, устанавливаем
    // размеры изображения в соответствии со значениями,
    // указанными в заголовке метафайла
    else if(lpmfp->xExt > 0)
    {
      x = lpmfp->xExt;
      y = lpmfp->yExt;

      // Выполняем преобразование из
      // сотых долей мм в пикселы
      HiMetricToSize(&x, &y);
      SetViewportExtEx(hdc, x, y, NULL);
    }

    // Если xExt меньше нуля, сохраняем масштаб
    // изображения, разрешая изменения размеров
    else if(lpmfp->xExt < 0)
    {
      x = -lpmfp->xExt;
      y = -lpmfp->yExt;

      // Выполняем преобразование из
      // сотых долей мм в пикселы с учетом
      // размеров окна
      HiMetricToSizeScaled(&x, &y, cxClient, cyClient);
      SetViewportExtEx(hdc, x, y, NULL);
    }
  }
  // Для остальных режимов отображения устанавливаем
  // размеры изображения равными размерам,
  // указанным в заголовке метафайла
  else
    SetViewportExtEx(hdc, lpmfp->xExt, lpmfp->yExt, NULL);
}
// =====================================
// Функция SizeToHiMetric
// =====================================
void SizeToHiMetric(int *width, int *height)
{
  HDC hDC = GetDC(0);

  // Определяем количество пикселов в логическом
  // дюйме по горизонтали и по вертикали
  int dpiX = GetDeviceCaps(hDC, LOGPIXELSX);
  int dpiY = GetDeviceCaps(hDC, LOGPIXELSY);

  // Константа для преобразования
  const long HiMetricPerInch = 2540;

  // Выполняем преобразование
  if(width)  *width  = int(*width * HiMetricPerInch / dpiX);
  if(height) *height = int(*height * HiMetricPerInch / dpiY);

  ReleaseDC(0, hDC);
}
// =====================================
// Функция HiMetricToSize
// =====================================
void HiMetricToSize(int *width, int *height)
{
  HDC hDC = GetDC(0);
  long int dpiX = GetDeviceCaps(hDC, LOGPIXELSX);
  long int dpiY = GetDeviceCaps(hDC, LOGPIXELSY);
  const long HiMetricPerInch = 2540;

  if(width  != NULL) *width  =
    int(*width * dpiX / HiMetricPerInch);
  if(height != NULL) *height =
    int(*height * dpiY / HiMetricPerInch);

  ReleaseDC (0, hDC);
}
// =====================================
// Функция HiMetricToSizeScaled
// =====================================
void HiMetricToSizeScaled (int *width, int *height,
  int cxClient, int cyClient)
{
  HDC hDC = GetDC(0);
  long int dpiX = GetDeviceCaps(hDC, LOGPIXELSX); 
  long int dpiY = GetDeviceCaps(hDC, LOGPIXELSY);
  const long HiMetricPerInch = 2540;
  int xPic, yPic;

  // Определяем размеры в пикселах
  if (width !=  NULL) xPic =
    int(*width * dpiX / HiMetricPerInch);
  if (height != NULL) yPic =
    int(*height * dpiY / HiMetricPerInch);

  // Сравниваем коэффициенты масштабирования
  // по горизонтали и вертикали
  if((cxClient*100L) / xPic > (cyClient*100L) / yPic)
  {
    // Если коэффициент масштабирования по
    // горизонтали больше, масштабируем
    // по горизонтали
    xPic = ((long)xPic * cyClient) / (long)yPic;
    yPic = cyClient;
  }
  else
  {
    // Иначе масштабируем по вертикали
    yPic = ((long)cxClient * yPic) / (long)xPic;
    xPic = cxClient;
  }
  ReleaseDC (0, hDC);

  // Новые размеры
  *width = xPic; *height = yPic;
}

Функция WinMain допускает запуск только одной копии приложения. Вы можете убрать это ограничение, однако в этом случае вам нужно будет предусмотреть способ раздельного хранения (для каждой копии приложения) идентификаторов палитр hPal и hOldPal. Например, можно зарезервировать двойное слово в классе окна и использовать его для этой цели.

При создании главного окна приложения обработчик сообщения WM_CREATE добавляет это окно в список окон просмотра Clipboard, вызывая функцию SetClipboardViewer. Идентификатор следующего в цепочке окна просмотра сохраняется в переменной hwndNextViewer.

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

Извлечение данных из Clipboard и их рисование выполняется во время обработки сообщения WM_PAINT. При этом используются описанные нами ранее процедуры.

Вначале приложение проверяет, содержит ли Clipboard данные в формате метафайла. Если да, то данные извлекаются и отображаются в окне приложения. Если нет, проверяется формат CF_BITMAP.

Если доступен формат CF_BITMAP, выполняется попытка чтения из Clipboard палитры, сопровождающей битовое изображение DDB. Найденная палитра выбирается в контекст отображения и реализуется. Затем обработчик сообщения WM_PAINT рисует битовое изображение, вызывая функцию DrawBitmap.

В том случае, когда ни один из перечисленных выше форматов недоступен, выполняется попытка прочитать данные в текстовом формате. В случае успеха прочитанный текст рисуется в окне функцией DrawText.

После завершения работы с Clipboard приложение вызывает функцию CloseClipboard, закрывая Clipboard.

Обработчики сообщений WM_CHANGECBCHAIN и WM_DRAWCLIPBOARD были описаны ранее. Их задачей является, соответственно, отслеживание изменений в списке окон просмотра Clipboard и изменений содержимого Clipboard.

Наше приложение отслеживает также изменения в системной палитре, обрабатывая сообщения WM_PALETTECHANGED и WM_QUERYNEWPALETTE. Соответствующие алгоритмы описаны в 14 томе "Библиотеки системного программиста" в главе "Цвет и цветовые палитры".

Обработчик сообщения WM_DESTROY удаляет главное окно приложения из списка окон просмотра Clipboard, вызывая функцию ChangeClipboardChain.

Файл clipshow.hpp содержит определения идентификаторов строк меню (листинг 2.8).


Листинг 2.8. Файл clipshow/clipshow.hpp


#define CM_HELPABOUT  24000
#define CM_FILEEXIT   24001

В файле описания ресурсов определено меню и пиктограмма (листинг 2.9).


Листинг 2.9. Файл clipshow/clipshow.rc


#include "clipshow.hpp"
APP_MENU MENU 
BEGIN
        POPUP "&File"
        BEGIN
                MENUITEM "E&xit",     CM_FILEEXIT
        END
        POPUP "&Help"
        BEGIN
                MENUITEM "&About...", CM_HELPABOUT
        END
END
APP_ICON ICON "clipshow.ico"

Файл определения модуля приложения CLIPSHOW представлен в листинге 2.10.


Листинг 2.10. Файл clipshow/clipshow.def


NAME        CLIPSHOW
DESCRIPTION 'Приложение CLIPSHOW, (C) 1995, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   8120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

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