2. Обмен данными через Clipboard

В этой главе мы расскажем вам о важной компоненте операционной системы Windows - об универсальном буфере обмена Clipboard. Как пользователь Windows вы, безусловно, знаете, что Clipboard предназначен для обмена информацией между различными приложениями Windows. Например, вы можете "вырезать" из графического изображения, редактируемого приложением Paintbrush, произвольный фрагмент и вставить его в документ, созданный в текстовом процессоре Microsoft Word for Windows или в ячейку электронной таблицы, созданной с помощью Microsoft Excel.

Мы называем Clipboard универсальным буфером обмена, так как пользователь может записать в него самую различную информацию.

Методика работы с Clipboard одинакова для всех приложений и обычно заключается в том, что пользователь выделяет нужную часть документа или изображения, а затем выбирает из меню "Edit" строки "Copy" или "Cut". В первом случае выделенный фрагмент копируется в Clipboard, во втором - также копируется, но после копирования фрагмент удаляется из документа.

Для вставки фрагмента из Clipboard в документ пользователь выбирает строки "Paste" или "Paste Special..." из меню "Edit". Назначение строки "Paste" вроде бы понятно - она используется для вставки фрагмента из Clipboard в документ. Чтобы понять для чего нужна загадочная строка "Paste Special...", а заодно и уточнить действие строки "Paste", проведем небольшой эксперимент.

Запустите приложение Paintbrush и загрузите в него любое изображение (либо нарисуйте что-нибудь сами). Выделите фрагмент изображения и скопируйте его в Clipboard, выбрав из меню "Edit" строку "Copy". Затем запустите приложение Clipboard или ClipBook Viewer (если вы работаете в Windows for Workgroups). Это приложение предназначено для просмотра содержимого универсального буфера обмена Clipboard.

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

Рис. 2.1. Выбор формата отображения

В нашем случае вы сможете выбрать либо формат по умолчанию "Default Format", формат "Bitmap" или "Picture".

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

Фактически приложения записывают данные в Clipboard одновременно в нескольких форматах. Например, Paintbrush записывает в Clipboard изображение в виде объекта, предназначенного для вставки в документ по технологии OLE, в виде битового изображения DDB, а также в виде метафайла.

Когда пользователь вставляет содержимое Clipboard в документ, приложение может выбирать наиболее подходящий формат. В некоторых случаях выбор оставляется за пользователем. Для того чтобы при вставке фрагмента указать формат данных явным образом, пользователь должен выбрать из меню "Edit" строку "Paste Special...". В результате на экране появляется диалоговая панель, с помощью которой можно сделать выбор (рис. 2.2). Заметим, что не каждое приложение предоставляет пользователю возможность выбора формата данных при вставке из Clipboard, поэтому, работая с некоторыми приложениями, вы можете и не найти строку "Paste Special..." в меню "Edit".

Рис. 2.2. Диалоговая панель "Paste Special" в приложении Microsoft Word for Windows версии 2.2.

Таким образом, Clipboard может содержать данные одновременно в нескольких форматах. Что это за форматы?

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

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

Сразу возникает вопрос: а сможет ли приложение Clipboard или ClipBook из Windows for Workgroups (либо другое приложение, предназначенное для просмотра содержимого Clipboard) отображать данные, которые находятся в нестандартном формате?

Самостоятельно - нет, так как откуда это приложение узнает способ отображения? Но приложение, зарегистрировавшее нестандартный формат данных для Clipboard может помочь в этом приложению, показывающему содержимое Clipboard. Когда приложение Clipboard или ClipBook приступит к рисованию нестандартных данных, оно пошлет сообщение тому приложению, которое зарегистрировало этот формат данных. После этого приложение, владеющее нестандартным форматом, должно само нарисовать данные в окне приложения Clipboard или ClipBook (рис. 2.3).

Рис. 2.3. Содержимое Clipboard нарисовано приложением Microsoft Write

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

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

Чтобы понять, что такое отложенная запись, разберемся вначале с обычной записью данных.

Как вы думаете, где хранятся данные, записанные в Clipboard?

Очевидно, в памяти. Но в какой? Эта память должна быть доступна для всех приложений. Кроме того, содержимое памяти не должно исчезать при завершении работы отдельных приложений.

Например, вы можете запустить Paintbrush, скопировать из него фрагмент изображения в Clipboard, затем завершить работу приложения Paintbrush и запустить Word for Windows. Несмотря на то, что приложение Paintbrush не запущено, Clipboard по-прежнему содержит фрагмент изображения, который можно вставить в документ, редактируемый с помощью Word for Windows.

Всем перечисленным выше требованиям удовлетворяет глобальная память, принадлежащая операционной системе Windows. Такая память должна иметь атрибут GMEM_DDESHARE.

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

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

Механизм отложенной записи данных экономит оперативную память. В момент, когда пользователь копирует данные в Clipboard, при отложенной записи никакой передачи данных не происходит, хотя Clipboard отмечается как содержащий эти данные в одном или нескольких форматах. Когда какому-либо приложению потребуется тот или иной формат данных, приложение, записавшее в Clipboard данные, получает сообщение о необходимости выполнения фактической записи данных и предоставляет данные в нужном формате "потребителю".

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

Если же приложение, выполнившее отложенную запись, завершает свою работу, оно должно записать данные в Clipboard во всех возможных форматах. Иначе как их потом оттуда взять?

И еще одно замечание.

Содержимым Clipboard должен управлять пользователь и только пользователь.

Все действия с Clipboard должны выполняться только тогда, когда пользователь выбирает соответствующие строки из меню "Edit", нажимает на клавиши-акселераторы, кнопки органа управления Toolbar, дублирующие эти строки, или пользуется другими органами управления, специально предназначенными для работы с Clipboard. Ни в коем случае не следует очищать Clipboard при запуске вашего приложения или выполнять с Clipboard другие, нестандартные и неожиданные для пользователя действия.

2.1. Простейшие приемы использования Clipboard

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

Функции для работы с Clipboard

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

Перед тем как выполнить запись или чтение данных, приложение должно получить доступ к Clipboard, открыв ее при помощи функции OpenClipboard:

BOOL WINAPI OpenClipboard(HWND hwnd);

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

Если доступ к Clipboard получен, функция OpenClipboard вернет значение TRUE. Если же Clipboard уже открыт другим приложением, эта функция вернет значение FALSE.

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

BOOL WINAPI CloseClipboard(void);

Эта функция в случае успеха возвращает значение TRUE, а в случае ошибки - FALSE.

Содержимое открытого Clipboard может быть сброшено функцией EmptyClipboard:

BOOL WINAPI EmptyClipboard(void);

Функция EmptyClipboard возвращает значение TRUE при нормальном завершении или FALSE при ошибке.

Для выполнения записи данных в Clipboard приложение должно использовать функцию SetClipboardData:

HANDLE WINAPI SetClipboardData(UINT uFormat, HANDLE  hData);

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

Для параметра uFormat вы можете использовать константу, соответствующую одному из предопределенных форматов, или полученную от функции RegisterClipboardFormat.

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

Приведем список предопределенных форматов данных для Clipboard. Соответствующие константы определены в файле windows.h.

Константа Описание формата данных
CF_TEXT Текстовые данные в виде массив символов. Каждая строка завершается комбинацией символов "\r\n", весь массив закрыт двоичным нулем
CF_OEMTEXT Аналогично предыдущему, однако буфер содержит символы в кодировке OEM
CF_BITMAP Битовое изображение в формате, который зависит от устройства отображения (DDB)
CF_METAFILEPICT Метафайл
CF_DIB Битовое изображение в формате, который не зависит от устройства отображения (DIB)
CF_PALETTE Палитра цветов. Используется при записи в Clipboard битовых изображений DIB
CF_SYLK Формат Microsoft Symbolic Link. Этот формат предназначен для передачи текстовых данных, причем каждая строка заканчивается комбинацией символов "\r\n". Используется в некоторых программных продуктах Microsoft
CF_DIF Формат Data Interchange Format. Аналогично формату CF_SYLK, формат CF_DIF предназначен для передачи текстовых данных
CF_TIFF Графическое изображение в формате Tag Image File Format, который был разработан Microsoft, Aldus Corporation и Hewlett-Packard
CF_PENDATA Этот формат данных используется в расширении операционной системы Windows, педназначенном для работы с перьевым вводом
CF_RIFF Формат Resource Interchange File Format. Мы рассказывали о нем в томе "Библиотеки системного программиста", посвященном системам мультимедиа
CF_WAVE Звуковые данные. Подмножество формата Resource Interchange File Format
CF_OWNERDISPLAY Формат данных определяется приложением, записавшим такие данные в Clipboard. Это же приложение отвечает за отображение данных
CF_DSPTEXT Текстовое представление данных, формат которых определен приложением
CF_DSPBITMAP Представление данных, формат которых определен приложением, в виде битового изображения
CF_DSPMETAFILEPICT Представление данных, формат которых определен приложением, в виде метафайла

Для чтения данных из Clipboard используется функция GetClipboardData:

HANDLE  WINAPI GetClipboardData(UINT uFormat);

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

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

Запись данных в Clipboard

Для того чтобы записать данные в Clipboard, вы должны выполнить следующую последовательность действий.

На первый взгляд, указанная последовательность действий понятна, однако при ее внимательном изучении возникает ряд вопросов.

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

Дело в том, что при записи в Clipboard никакого перемещения данных не происходит. Функция SetClipboardData изменяет атрибуты передаваемого ей блока памяти таким образом, что этот блок памяти изменяет своего "хозяина", переходя в собственность операционной системы Windows и приобретая атрибут GMEM_DDESHARE.

Если приложение заказало глобальный блок памяти без атрибута GMEM_DDESHARE, этот блок памяти принадлежит приложению. Оно отвечает за уничтожение такого блока памяти. Если же приложение "забыло" уничтожить заказанный ей глобальный блок памяти, Windows уничтожает блок памяти сама при завершении приложения.

Напомним, что одно из требований к блоку памяти, предназначенного для хранения данных Clipboard, заключается в том, чтобы этот блок не уничтожался при завершении работы приложения, создавшего его.

Если приложение снабдит блок памяти атрибутом GMEM_DDESHARE, Windows не будет автоматически уничтожать его при завершении работы приложения. Приложение обязано уничтожить его самостоятельно, как только этот блок памяти перестанет быть нужным.

Обычно блоки памяти с атрибутом GMEM_DDESHARE используются в Windows версии 3.1 для организации общего поля памяти, доступного всем приложениям. Так как в Windows версии 3.0 и 3.1 для адресации памяти все приложения обращаются к одной локальной таблице дескрипторов LDT, они все могут адресоваться к одному блоку памяти, если будут знать его селектор.

Память Clipboard в Windows версий 3.0 и 3.1 устроена как раз в виде набора таких блоков памяти "общего пользования" (по одному на каждый формат данных). Функция SetClipboardData получает у приложения его "личный" глобальный блок памяти с данными, и отдает его в "коллективное пользование", добавляя атрибут GMEM_DDESHARE.

Из всего сказанного выше следует правило:

Если вы записали данные в Clipboard, отдав идентификатор глобального блока памяти функции SetClipboardData, больше не используйте этот идентификатор для адресации данных

Почему нельзя использовать идентификатор блока памяти, переданный функции SetClipboardData?

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

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

Для исключения конфликтных ситуаций, связанных с попыткой одновременного доступа к Clipboard со стороны нескольких приложений, используйте следующее правило:

Все операции с Clipboard от его открытия и до закрытия должны выполняться в одном обработчике сообщения. Нельзя открывать Clipboard в обработчике одного сообщения, а закрывать в обработчике другого сообщения. Между открытием и закрытием Clipboard нельзя создавать диалоговые панели или выполнять другие действия, которые могут повлечь за собой создание диалоговых панелей (например, вызов функций SendMessage или PeekMessage)

Еще одно замечание относительно атрибута блока памяти GMEM_DDESHARE.

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

Такой метод будет превосходно работать в Windows версии 3.1, и... не будет работать в Windows NT. Дело в том, что 32-разрядная операционная система Windows NT создает для каждого 32-разрядного приложения отдельную локальную таблицу дескрипторов LDT и, следовательно, отдельное адресное пространство. Поэтому никакое приложение ни при каких обстоятельствах не может иметь доступа к адресному пространству другого приложения.

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

Чтение данных из Clipboard

Рассмотрим теперь процедуру чтения данных из Clipboard. Приложение должно сделать следующее.

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

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

Приложение CLIPTXT

Работу описанных выше функций вы можете проверить при помощи приложения CLIPTXT (рис. 2.4). Главное меню этого приложения содержит временное меню "Edit". Пользуясь строками "Copy" и "Paste" этого меню вы сможете записать в Clipboard текстовую строку, заданную в приложении, а также просмотреть в главном окне приложения содержимое Clipboard в текстовом формате.

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

Файл cliptxt.cpp содержит все функции, определенные в приложении (листинг 2.1).

Листинг 2.1. Файл cliptxt/cliptxt.cpp

// ----------------------------------------
// Запись данных в текстовом формате в Clipboard
// и чтение текстовых данных из Clipboard
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <mem.h>
#include "cliptxt.hpp"

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[]   = "CliptxtClass";
char const szWindowTitle[] = "Clipboard Demo";
char const szClipboardText[] =
  "Этот текст будет записан\r\n"
  "в универсальный буфер обмена Clipboard\r\n"
  "приложением CLIPTXT";

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

  // Разрешаем запуск нескольких копий приложения
  if(!hPrevInstance)
    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 HGLOBAL hglbTextCopyBuf;
  LPSTR lpTextCopy;
  static HGLOBAL hglbTextPasteBuf;
  LPSTR lpTextPaste;
  static HGLOBAL hglbClipBuf;
  LPSTR lpClipBuf;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Заказываем буфер для хранения текста,
      // прочитанного из Clipboard.
      // Начальный размер этого буфера - 1 байт
      hglbTextPasteBuf = GlobalAlloc(GHND, 1);
      if(hglbTextPasteBuf == NULL)
        return -1;

      // Фиксируем буфер в памяти и получаем его адрес
      lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf);
      if(hglbTextPasteBuf == NULL)
        return -1;

      // Записываем в буфер пустую строку
      lpTextPaste[0] = '\0';

      // Расфиксируем буфер
      GlobalUnlock(hglbTextPasteBuf);
      return 0;
    }
    case WM_PAINT:
    {
      hdc = BeginPaint(hwnd, &ps);

      // Фиксируем текстовый буфер в памяти,
      // в случае успеха отображаем его содержимое
      // во внутренней области главного окна
      lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf);
      if(lpTextPaste != NULL)
      {
        GetClientRect(hwnd, &rc);
        DrawText(hdc, lpTextPaste, -1, &rc,
          DT_LEFT | DT_EXPANDTABS);
        GlobalUnlock(hglbTextPasteBuf);
      }
      EndPaint(hwnd, &ps);
    }
    case WM_COMMAND:
    {
      switch (wParam)
      {
        // Копируем строку текста в Clipboard
        case CM_EDITCOPY:
        {
          // Заказываем глобальный блок памяти для строки
          hglbTextCopyBuf = GlobalAlloc(GHND,
            sizeof(szClipboardText) + 1);
          if(hglbTextCopyBuf != NULL)
          {
            // Фиксируем блок памяти
            lpTextCopy = (LPSTR)GlobalLock(hglbTextCopyBuf);
            if(lpTextCopy != NULL)
            {
              // Копируем строку текста в блок памяти
              lstrcpy(lpTextCopy, szClipboardText);

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

              // Открываем Clipboard и очищаем его
              OpenClipboard(hwnd);
              EmptyClipboard();

              // Записываем данные в Clipboard
              SetClipboardData(CF_TEXT, hglbTextCopyBuf);

              // Закрываем Clipboard
              CloseClipboard();
            }
            else
              MessageBox(hwnd, "Мало памяти",
                (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
          }
          else
            MessageBox(hwnd, "Мало памяти",
              (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
          return 0;
        }

        // Чтение текстовых данных из Clipboard
        case CM_EDITPASTE:
        {
          // Открываем Clipboard
          OpenClipboard(hwnd);

          // Получаем идентификатор блока памяти,
          // содержащего текстовые данные Clipboard
          hglbClipBuf = GetClipboardData(CF_TEXT);

          // Если в Clipboard есть данные в текстовом
          // формате, читаем их
          if(hglbClipBuf != NULL)
          {
            // Фиксируем блок памяти Clipboard
            lpClipBuf = (LPSTR)GlobalLock(hglbClipBuf);
            if(lpClipBuf != NULL)
            {
              // Изменяем размер буфера, предназначенного для
              // хранения данных, прочитанных из Clipboard,
              // устанавливая его равным размеру блока
              // данных Clipboard
              hglbTextPasteBuf = 
                 GlobalReAlloc(hglbTextPasteBuf,
                 GlobalSize(hglbClipBuf), GMEM_NODISCARD);

              // Фиксируем буфер в памяти и переписываем
              // в него содержимое Clipboard
              lpTextPaste =
                (LPSTR)GlobalLock(hglbTextPasteBuf);
              if(lpTextPaste != NULL)
              {
                lstrcpy(lpTextPaste, lpClipBuf);

                // Перерисовываем главное окно приложения
                InvalidateRect(hwnd, NULL, TRUE);

                // Расфиксируем буфер
                GlobalUnlock(hglbTextPasteBuf);
              }
              else
                MessageBox(hwnd, "Мало памяти",
                  (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);

              // Расфиксируем блок памяти Clipboard 
              GlobalUnlock(hglbClipBuf);
            }
            else
              MessageBox(hwnd, "Мало памяти",
                (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
          }
          else
            MessageBox(hwnd, "Формат CF_TEXT недоступен",
            (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);

          // Закрываем Clipboard
          CloseClipboard();
          return 0;
        }

        case CM_HELPABOUT:
        {
          MessageBox(hwnd,
            "Приложение CLIPTXT\n(C) Фролов А.В., 1995",
            (LPSTR)szWindowTitle, MB_OK |
            MB_ICONINFORMATION);
          return 0;
        }
        case CM_FILEEXIT:
        {
          DestroyWindow(hwnd);
          return 0;
        }
        default:
          return 0;
      }
    }
    case WM_DESTROY:
    {
      // Освобождаем буфер, предназначенный для хранения
      // данных, прочитанных из Clipboard
      if(hglbTextPasteBuf != NULL)
        GlobalFree(hglbTextPasteBuf);

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

Обработчик сообщения WM_CREATE заказывает буфер hglbTextPasteBuf, в который будет записан текст, вставленный из Clipboard. Первоначально размер этого буфера равен 1 байту, так как при создании главного окна приложения в него записывается пустая строка, состоящая из одного нулевого байта. Впоследствии при вставке текста из Clipboard размер буфера будет изменяться таким образом, чтобы в нем можно было разместить весь текст.

После записи пустой строки буфер расфиксируется.

Задача обработчика сообщения WM_PAINT заключается в рисовании содержимого буфера hglbTextPasteBuf. Для рисования используется функция DrawText.

Заметьте, что мы фиксируем буфер hglbTextPasteBuf в памяти только на время рисования. Перед возвратом управления обработчик сообщения WM_PAINT расфиксирует буфер.

Когда пользователь выбирает строку "Copy" из меню "Edit", обработчик сообщения WM_COMMAND выполняет описанные нами ранее действия по копированию текстовой строки szClipboardText в Clipboard.

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

Теперь блок памяти подготовлен для записи в Clipboard. Приложение открывает Clipboard и очищает его с помощью функций OpenClipboard и EmptyClipboard:

OpenClipboard(hwnd);
EmptyClipboard();

После этого блок памяти hglbTextCopyBuf передается функции SetClipboardData. Для завершения процесса Clipboard закрывается функцией CloseClipboard:

SetClipboardData(CF_TEXT, hglbTextCopyBuf);
CloseClipboard();

Все! Теперь Clipboard содержит следующий текст:

Этот текст будет записан
в универсальный буфер обмена Clipboard
приложением CLIPTXT

Вы можете убедиться в этом с помощью приложения Clipboard или ClipBook Viewer (если вы используете Windows for Workgroups).

Если пользователь выберет строку "Paste" из меню "Edit", обработчик сообщения WM_COMMAND скопирует данные из Clipboard в буфер hglbTextPasteBuf, подготовленный приложением на этапе создания главного окна.

В процессе копирования приложение открывает Clipboard функцией OpenClipboard и получает из него текстовые данные, вызывая функцию GetClipboardData:

hglbClipBuf = GetClipboardData(CF_TEXT);

Для получения адреса данных блок фиксируется функцией GlobalLock:

lpClipBuf = (LPSTR)GlobalLock(hglbClipBuf);

Перед тем как скопировать данные из Clipboard в буфер hglbTextPasteBuf его размер делается равным размеру блока памяти hglbClipBuf при помощи функции GlobalReAlloc:

hglbTextPasteBuf = 
   GlobalReAlloc(hglbTextPasteBuf,
   GlobalSize(hglbClipBuf), GMEM_NODISCARD);

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

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

В завершении процесса копирования данных из Clipboard блоки памяти hglbTextPasteBuf и hglbClipBuf расфиксируются функцией GlobalUnlock. Затем Clipboard закрывается функцией CloseClipboard.

При завершении работы приложения обработчик сообщения WM_DESTROY уничтожает блок памяти hglbTextPasteBuf, который использовался для хранения данных, прочитанных из Clipboard:

if(hglbTextPasteBuf != NULL)
  GlobalFree(hglbTextPasteBuf);

Файл cliptxt.hpp (листинг 2.2) содержит определения констант для идентификации строк меню приложения CLIPTXT.

Листинг 2.2. Файл cliptxt/cliptxt.hpp

#define CM_HELPABOUT  24000
#define CM_EDITPASTE  24001
#define CM_EDITCOPY   24002
#define CM_FILEEXIT   24003

В файле cliptxt.rc (листинг 2.3) определено главное меню приложения и пиктограмма, в которую превращается главное окно приложения при свертке.

Листинг 2.3. Файл cliptxt/cliptxt.rc

#include "cliptxt.hpp"
APP_MENU MENU 
BEGIN
  POPUP "&File"
  BEGIN
    MENUITEM "E&xit",     CM_FILEEXIT
  END
  POPUP "&Edit"
  BEGIN
    MENUITEM "&Copy",     CM_EDITCOPY
    MENUITEM "&Paste",    CM_EDITPASTE
  END
  POPUP "&Help"
  BEGIN
    MENUITEM "&About...", CM_HELPABOUT
  END
END
APP_ICON ICON "cliptxt.ico"

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

Листинг 2.4. Файл cliptxt/cliptxt.def

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

Clipboard и редактор текста EDIT

Орган управления, созданный на базе предопределенного класса окна "EDIT" (редактор текста), способен обмениваться данными с Clipboard. В 12 томе "Библиотеки системного программиста" мы перечисляли сообщения, которые можно посылать редактору текста с помощью функции SendMessage. Для обмена данных между редактором текста и Clipboard приложение может посылать редактору сообщения WM_COPY, WM_CUT и WM_PASTE:

SendMessage(hEdit, WM_PASTE, 0, 0L);

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

Пример организации взаимодействия органа управления EDIT и буфера обмена Clipboard приведен в 13 томе "Библиотеки системного программиста" в разделе "Приложение SMARTPAD".

2.2. Запись и чтение графических изображений

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

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

Мы рассмотрим широко распространенную задачу передачи через Clipboard битовых изображений DIB и DDB.

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

Например, графический редактор Micrografx Designer версии 3.02 при записи изображений в Clipboard помимо своих нестандартных форматов использует форматы CF_BITMAP и CF_METAFILEPICT. Приложение Paintbrush сохраняет данные аналогичным образом, добавляя к своим собственным форматам форматы CF_BITMAP, CF_PALETTE и CF_METAFILEPICT. А вот графический редактор Microsoft Draw, который поставляется вместе с текстовым процессором Microsoft Word for Windows версии 2.0, сохраняет данные в Clipboard используя свои собственные форматы данных и формат CF_METAFILEPICT.

По возможности ваше приложение должно быть способно сохранять и читать изображения в перечисленных выше форматах. В этом случае пользователь сможет вставить сохраненное вашим приложением изображение из Clipboard в какой-либо стандартный графический редактор.

Несмотря на то, что для записи DIB в Clipboard предусмотрен специальный формат данных CF_DIB (блок памяти, который содержит биты изображения и начинается со структуры BITMAPINFO), многие приложения записывают DIB в виде DDB и цветовой палитры, созданных на основе DIB. Это потому, что не все приложения умеют читать из Clipboard данные в формате CF_DIB. Поэтому запись DIB сводится к записи DDB и палитры. Как мы уже говорили, следует также записать DIB в формате метафайла, к чему мы еще вернемся.

Рассмотрим по отдельности способы записи DDB, палитры и метафайла в Clipboard.

Запись DDB

Запись DDB в Clipboard выполняется достаточно просто.

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

Все перечисленные выше действия можно проделать, например, так:

OpenClipboard(hwnd);
EmptyClipboard();
if(hBitmap)
  SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();

Отметим, что ваше приложение не должно удалять DDB, идентификатор которого был использован при вызове функции SetClipboardData. После записи в Clipboard память, занимаемая DDB, переходит в распоряжение Windows, и ваше приложение не должно ее использовать.

Запись палитры

Если приложение должно уметь записывать в Clipboard многоцветное изображение DIB, вместе с DDB необходимо записать в Clipboard цветовую палитру, используемую этим изображением. Напомним, что файл в формате DIB может содержать наряду с битами изображения специальную таблицу цветов, на базе которой создается цветовая палитра. Вы можете получить дополнительную информацию о работе с DIB и цветовыми палитрами из 14 тома "Библиотеки системного программиста".

Для записи цветовой палитры в Clipboard вам достаточно передать идентификатор палитры функции SetClipboardData, указав этой функции в качестве первого параметра константу CF_PALETTE:

if(hPal)
  SetClipboardData(CF_PALETTE, hPal);

Разумеется, перед записью палитры необходимо открыть Clipboard.

Если палитра и DDB создаются на базе DIB, и ваше приложение записывает и то, и другое в Clipboard, нужно выполнить следующие действия:

Запись метафайла

Практически все графические редакторы, такие как Paintbrush, Micrografix Designer, Photo Finish и т. п., при записи фрагмента изображения в Clipboard сохраняют данные не только в своих собственных форматах, но и в формате метафайла. В этом разделе мы рассмотрим процесс записи метафайла в Clipboard, а немного позже расскажем об особенностях чтения метафайла из Clipboard и проигрывания его в контексте отображения.

Сначала перечислим шаги, которые ваше приложение должно выполнить для записи метафайла в Clipboard.

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

Первые шаги процедуры записи метафайла в Clipboard (создание метафайла и настройка контекста метафайла) несложны.

Для создания метафайла в оперативной памяти следует воспользоваться функцией CreateMetaFile, передав ей в качестве параметра значение NULL:

hdcMeta = CreateMetaFile((LPSTR)NULL);

Далее необходимо выбрать начало логической системы координат и размеры изображения. Как правило, для метафайла, сохраняемого в Clipboard, используются режимы отображения MM_ISOTROPIC или MM_ANISOTROPIC. В этом случае при записи графического изображения размером (ptSize.x, ptSize.y) вы можете определить начало логической системы координат и размеры изображены следующим образом:

SetWindowOrgEx(hdcMeta, 0, 0, NULL);
SetWindowExtEx(hdcMeta, ptSize.x, ptSize.y, NULL);

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

На следующем шаге приложение должно нарисовать в контексте метафайла изображение, которое будет сохранено в Clipboard. Приведем фрагмент приложения BMPINFO, рассмотренного ниже, который рисует в контексте метафайла битовое изображение DIB:

// Создаем палитру из DIB
hPal = DIBCreatePalette(hDib);
if(hPal)
{
  // Выбираем и реализуем палитру в контекст
  // метафайла
  hOldPal = SelectPalette(hdcMeta, hPal, FALSE);
  RealizePalette(hdcMeta);
}
// Рисуем DIB в контексте метафайла
DIBPaint(hdcMeta, 0, 0, hDib);

// Выбираем старую палитру
if(hPal)
  SelectPalette(hdcMeta, hOldPal, FALSE);

Процедура рисования DIB в контексте метафайла ничем не отличается от аналогичной процедуры для контекста отображения. Эта процедура, а также функции DIBCreatePalette и DIBPaint были описаны в 14 томе "Библиотеки системного программиста" (см. разделы "Рисование изображений DIB" и "Приложение BMPINFO").

После того как изображение нарисовано, следует закрыть метафайл:

hMF = CloseMetaFile(hdcMeta);

Теперь нужно создать и заполнить заголовок метафайла.

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

hMeta = GlobalAlloc(GHND, sizeof(METAFILEPICT));
lpMeta = (LPMETAFILEPICT)GlobalLock(hMeta);

Структура METAFILEPICT и указатель на нее описаны в файле windows.h следующим образом:

typedef struct tagMETAFILEPICT

{

int mm;

int xExt;

int yExt;

HMETAFILE hMF;

} METAFILEPICT;

typedef METAFILEPICT FAR* LPMETAFILEPICT;

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

lpMeta->mm = MM_ANISOTROPIC;

Поле hMF должно содержать идентификатор метафайла, полученный от функции CloseMetaFile:

lpMeta->hMF = hMF;

Наибольшую трудность вызывает заполнение полей xExt и yExt. Эти поля заполняются по-разному в зависимости от выбранного режима отображения.

Если используются режимы отображения, отличные от MM_ISOTROPIC или MM_ANISOTROPIC, в поля xExt и yExt следует записать размеры изображения в тех единицах измерения, которые соответствуют режиму отображения, указанному в поле mm.

Однако, как мы уже говорили, для обеспечения возможности масштабирования изображения после вставки из Clipboard практически все приложения устанавливают в заголовке метафайла режимы отображения MM_ISOTROPIC или MM_ANISOTROPIC. Для этих режимов возможны несколько вариантов заполнения полей xExt и yExt.

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

Во-вторых, приложение может записать в поля xExt и yExt положительные значения - предпочтительный размер изображения в сотых долях миллиметра (такая единица измерения используется в режиме отображения MM_HIMETRIC). Если приложение прочитало из Clipboard изображение, для которого установлены предпочтительные размеры, оно может использовать эти размеры для рисования. Оно также может проигнорировать предпочтительные размеры и нарисовать изображение по-своему, что, однако, приведет в некоторых случаях к искажению изображения (например, при уменьшении размеров битового изображения).

В-третьих, приложение может записать в поля xExt и yExt отрицательные значения. Отношение этих отрицательных значений передает информацию об отношении ширины к высоте изображения. При этом информация об абсолютных размерах изображения не передается.

Итак, выбрав один из перечисленных выше вариантов, необходимо заполнить поля xExt и yExt, завершив таким образом формирование заголовка метафайла:

lpMeta->xExt = xPicSize;
lpMeta->yExt = yPicSize;

Перед записью данных в Clipboard нужно расфиксировать блок памяти, содержащий заголовок метафайла и передать его функции SetClipboardData:

GlobalUnlock(hMeta);
if(hMeta)
  SetClipboardData(CF_METAFILEPICT, hMeta);

Новая версия приложения BMPINFO

В 14 томе "Библиотеки системного программиста" мы привели исходные тексты приложения BMPINFO, с помощью которого можно просматривать битовые изображения DIB. Новая версия этого приложения позволяет сохранять загруженное изображение в Clipboard, пользуясь форматами CF_BITMAP, CF_PALETTE и CF_METAFILE.

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

Основной файл исходных текстов приложения (листинг 2.5) претерпел небольшие изменения, связанные с добавлением нового временного меню "Edit". Это меню содержит строку "Copy", предназначенную для копирования загруженного изображения в Clipboard.

Листинг 2.5. Файл bmpinfo/bmpinfo.cpp

// ----------------------------------------
// Приложение BMPINFO
// Просмотр и анализ bmp-файлов в формате DIB
// с возможностью копирования
// соответствующего DDB и палитры в Clipboard
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mem.h>
#pragma hdrstop
#include "dib.hpp"
#include "bmpinfo.hpp"

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
char const szClassName[]   = "BmpInfoClass";
char const szWindowTitle[] = "Bitmap Information";
short cxClient, cyClient;
// =====================================
// Функция WinMain
// =====================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpszCmdLine, int nCmdShow)
{
  MSG  msg;
  HWND hwnd;
  if(!hPrevInstance)
    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)GetStockObject(LTGRAY_BRUSH);
  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;
  static HFILE hfDIBFile;
  static HDIB hDib;
  static HPALETTE hPal, hOldPal;
  static DWORD dwFileSize;
  switch (msg)
  {
    case WM_CREATE:
    {
      hfDIBFile = NULL;
      hDib = NULL;
      hPal = NULL;
      return 0;
    }
    case WM_SIZE:
    {
      cxClient = LOWORD(lParam);
      cyClient = HIWORD(lParam);
      return 0;
    }
    case WM_PAINT:
    {
      hdc = BeginPaint(hwnd, &ps);
      if((hDib != NULL) &&
         (DIBType(hDib) == WINRGB_DIB))
      {
        if(hPal)
        {
          hOldPal = SelectPalette(hdc, hPal, FALSE);
          RealizePalette(hdc);
        }
        DIBPaint(hdc, 0, 0, hDib);
        if(hPal)
        {
          SelectPalette(hdc, hOldPal, FALSE);
        }
      }
      else
      {
        if(hDib) DIBInfo(hDib, dwFileSize);
      }
      EndPaint(hwnd, &ps);
      return 0;
    }
    case WM_COMMAND:
    {
      switch (wParam)
      {
        case CM_HELPABOUT:
        {
          MessageBox(hwnd,
            "Bitmap Information, v.1.1\n"
            "(C) Frolov A.V., 1995",
            "About BMPINFO", MB_OK | MB_ICONINFORMATION);
          return 0;
        }
        case CM_FILEOPEN:
        {
          hfDIBFile = DIBSelectFile();
          if(hfDIBFile != NULL)
          {
            hDib = DIBReadFile(hfDIBFile, &dwFileSize);
            if((hDib != NULL) &&
               (DIBType(hDib) == WINRGB_DIB))
            {
              hPal = DIBCreatePalette(hDib);
            }
            InvalidateRect(hwnd, NULL, TRUE);
          }
          return 0;
        }
        case CM_FILEINFO:
        {
          if(hDib != NULL)
            DIBInfo(hDib, dwFileSize);
          return 0;
        }
        case CM_FILEEXIT:
        {
          DestroyWindow(hwnd);
          return 0;
        }
        // Копирование рисунка в Clipboard
        case CM_EDITCOPY:
        {
          if(hDib == NULL) return 0;
          DIBCopyToClipboard(hDib, hwnd);
          return 0;
        }
      }
    }
    case WM_PALETTECHANGED:
    {
       if (hwnd == (HWND) wParam)
         break;
    }
    case WM_QUERYNEWPALETTE:
    {
      HDC hdc;
      HPALETTE hOldPal;
      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;
    }
    case WM_DESTROY:
    {
      if(hPal)
        DeletePalette(hPal);
      if(hDib)
        GlobalFree(hDib);
      PostQuitMessage(0);
      return 0;
    }
    default:
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Обработчик сообщения от строки "Copy" временного меню "Edit" вызывает функцию DIBCopyToClipboard, определенную в файле dib.cpp. Эта функция копирует изображение в Clipboard сразу в нескольких форматах. Через первый параметр функции передается идентификатор загруженного изображения DIB, через второй - идентификатор главного окна приложения.

В листинге 2.6 мы привели фрагмент файла dib.cpp, содержащий новые функции, добавленные для работы с Clipboard.

Листинг 2.6. Фрагмент файла bmpinfo/dib.cpp

// -----------------------------------------------------
// Функции для работы с файлами в формате DIB
// -----------------------------------------------------
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mem.h>
#pragma hdrstop
#include "dib.hpp"

// -------------------------------
// Функция SizeToHiMetric
// Преобразование ширины и высоты для
// режима отображения MM_HIMETRIC
// -------------------------------
static void
SizeToHiMetric(int *width, int *height)
{
  HDC hDC = GetDC(0);

  if(hDC)
  {
    // Определяем количество пикселов на один
    // логический дюйм по горизонтали и вертикали
    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);
  }
}

// -------------------------------
// Функция DIBCopyToClipboard
// Копирование DIB в Clipboard
// -------------------------------
BOOL DIBCopyToClipboard(HDIB hDib, HWND hwnd)
{
  POINT ptSize;
  HDC hdcMeta, hdc, hdcMem;
  HMETAFILE hMF;
  HBITMAP hBitmap;
  HPALETTE hPal, hOldPal;
  HGLOBAL hMeta;
  LPMETAFILEPICT lpMeta;
  int x, y;

  // Если DIB не загружен или он имеет тип,
  // отличный от WINRGB_DIB, копирование не выполняем
  if(hDib == NULL) return FALSE;
  if(DIBType(hDib) != WINRGB_DIB) return FALSE;

  // Открываем и очищаем Clipboard
  if(!OpenClipboard(hwnd)) return FALSE;
  EmptyClipboard();

  // Определяем размеры DIB
  DIBGetBmpRect(hDib, &ptSize);
  x = ptSize.x;
  y = ptSize.y;

  // Создаем DDB
  // Копируем в него содержимое окна,
  // размеры копируемой области равны
  // размерам DIB
  hdc = GetDC(hwnd);
  hdcMem = CreateCompatibleDC(hdc);
  hBitmap = CreateCompatibleBitmap(hdc, x, y);
  if(hBitmap)
  {
    SelectObject(hdcMem, hBitmap);
    StretchBlt(hdcMem, 0, 0, x, y,
       hdc, 0, 0, x, y, SRCCOPY);
  }
  DeleteDC(hdcMem);
  ReleaseDC(hwnd, hdc);

  // Создаем метафайл в памяти
  hdcMeta = CreateMetaFile((LPSTR)NULL);

  // Устанавливаем начало координат и размеры изображения
  SetWindowOrgEx(hdcMeta, 0, 0, NULL);
  SetWindowExtEx(hdcMeta, ptSize.x, ptSize.y, NULL);

  // Создаем палитру из DIB
  hPal = DIBCreatePalette(hDib);
  if(hPal)
  {
    // Выбираем и реализуем палитру в контекст
    // метафайла
    hOldPal = SelectPalette(hdcMeta, hPal, FALSE);
    RealizePalette(hdcMeta);
  }

  // Рисуем DIB в контексте метафайла
  DIBPaint(hdcMeta, 0, 0, hDib);

  // Выбираем старую палитру
  if(hPal)
    SelectPalette(hdcMeta, hOldPal, FALSE);

  // Закрываем метафайл
  hMF = CloseMetaFile(hdcMeta);

  // Заказываем память для заголовка метафайла
  hMeta = GlobalAlloc(GHND, sizeof(METAFILEPICT));
  lpMeta = (LPMETAFILEPICT)GlobalLock(hMeta);

  // Преобразуем координаты для
  // режима отображения MM_HIMETRIC
  SizeToHiMetric(&x, &y);

  // Заполняем заголовок метафайла
  lpMeta->mm = MM_ANISOTROPIC;
  lpMeta->xExt = x;
  lpMeta->yExt = y;
  lpMeta->hMF = hMF;

  // Расфиксируем память заголовка метафайла
  GlobalUnlock(hMeta);

  // Записываем метафайл в Clipboard
  if(hMeta)
    SetClipboardData(CF_METAFILEPICT, hMeta);

  // Записываем DDB в Clipboard
  if(hBitmap)
    SetClipboardData(CF_BITMAP, hBitmap);

  // Записываем палитру в Clipboard
  if(hPal)
    SetClipboardData(CF_PALETTE, hPal);

  // Закрываем Clipboard
  CloseClipboard();
  return TRUE;
}

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

Для выполнения преобразования функция SizeToHiMetric определяет количество пикселов в одном логическом дюйме для контекста отображения, связанного с экраном дисплея. Далее ширина (высота) изображения (в пикселах) умножается на значение 2540 (1 дюйм соответствует 25,4 миллиметра) и делится на количество пикселов в одном логическом дюйме.

Функция DIBCopyToClipboard выполняет копирование данных в Clipboard одновременно в нескольких форматах, используя методики, описанные нами в предыдущих разделах. Для определения размеров DIB вызывается функция DIBGetBmpRect, определенная в файле dib.cpp (в листинге не представлена).

Чтение DDB

Для чтения данных из Clipboard в формате CF_BITMAP (который соответствует битовому изображению DDB) ваше приложение должно выполнить следующие действия:

Приведем фрагмент кода, читающий данные из Clipboard в формате CF_BITMAP:

OpenClipboard(hwnd);
hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP);
if(hBitmap != NULL)
  DrawBitmap(hdc, 0, 0, hBitmap);
CloseClipboard();

Для рисования DDB используется функция DrawBitmap, описанная нами ранее в 14 томе "Библиотеки системного программиста". Исходный текст этой функции вы сможете также найти в проекте приложения CLIPSHOW, которое будет описано ниже.

Чтение палитры

Записывая изображение DDB в формате CF_BITMAP в Clipboard, приложения обычно сохраняют там же и цветовую палитру в формате CF_PALETTE. Если ваше приложение работает с многоцветными изображениями, оно должно извлекать из Clipboard не только битовое изображение, но и палитру.

Действия, необходимые для чтения палитры, аналогичны действиям, выполняемым при чтении DDB:

Следующий фрагмент кода демонстрирует извлечение палитры, ее выбор и реализацию после извлечения изображения DDB:

hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP);
if(hBitmap != NULL)
{
  hPal = (HPALETTE)GetClipboardData(CF_PALETTE);
  if(hPal)
  {
    hOldPal = SelectPalette(hdc, hPal, FALSE);
    RealizePalette(hdc);
  }
  DrawBitmap(hdc, 0, 0, hBitmap);
}

Чтение метафайла

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

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

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);
  }
}

После фиксирования заголовка метафайла приложение сохраняет текущий контекст отображения функцией SaveDC, так как перед проигрыванием метафайла этот контекст будет изменен в соответствии с содержимым полей заголовка метафайла. После проигрывания контекст отображения восстанавливается при помощи функции RestoreDC.

Проигрывание метафайла выполняется функцией PlayMetaFile из программного интерфейса Windows, причем идентификатор метафайла берется из заголовка. Однако перед проигрыванием необходимо выполнить некоторые подготовительные действия, для чего в нашем приложении вызывается функция PrepareMetaFile.

Прежде всего, эта функция устанавливает контекст отображения, указанный в заголовке метафайла, вызывая функцию SetMapMode:

void PrepareMetaFile(HDC hdc, LPMETAFILEPICT lpmfp,
  int cxClient, int cyClient)
{
  int x, y;

  SetMapMode(hdc, lpmfp->mm);

  if(lpmfp->mm == MM_ISOTROPIC ||
     lpmfp->mm == MM_ANISOTROPIC)
  {
    if(lpmfp->xExt == 0)
      SetViewportExtEx(hdc, cxClient, cyClient, NULL);

    else if(lpmfp->xExt > 0)
    {
      x = lpmfp->xExt;
      y = lpmfp->yExt;
      HiMetricToSize(&x, &y);
      SetViewportExtEx(hdc, x, y, NULL);
    }

    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);
}

Далее для изотропного и анизотропного режимов отображения анализируется содержимое полей xExt и yExt заголовка метафайла с целью выбора размеров изображения в контексте отображения.

Если в заголовке метафайла размеры изображения не указаны (равны нулю), функция PrepareMetaFile устанавливает размер изображения, равным размеру внутренней области окна (размеры окна передаются функции через параметры).

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

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

Исходные тексты функций HiMetricToSize и HiMetricToSizeScaled приведены ниже в разделе "Приложение CLIPSHOW".

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

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

2.4. Отложенная запись

Мы уже говорили о том, что приложения, как правило, записывают данные в Clipboard одновременно в нескольких форматах. В некоторых случаях это может привести к неэкономному расходованию оперативной памяти. Например, если приложение копирует битовое изображение в форматах CF_DIB, CF_BITMAP вместе с CF_PALETTE, CF_METAFILEPICT и еще в нескольких собственных форматах (о собственных форматах читайте ниже), общий объем израсходованной для записи памяти может оказаться значительным. В то же время, если пользователь скопировал изображение в Clipboard только для того чтобы, например, вставить его в Paintbrush, может оказаться достаточным использование формата метафайла.

В этом разделе мы расскажем о том, как организовать отложенную запись (delayed rendering) в Clipboard и приведем исходные тексты приложения, выполняющего такую операцию с текстом.

Выполнение отложенной записи

Сам по себе процесс выполнения отложенной записи предельно прост: достаточно открыть и очистить Clipboard, вызвать функцию SetClipboardData, указав в первом параметре формат данных, а во втором - значение NULL, и закрыть Clipboard. Так, отложенная запись текстовых данных выглядит следующим образом:

OpenClipboard(hwnd);
EmptyClipboard();
SetClipboardData(CF_TEXT, NULL);
CloseClipboard();

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

Как только какое-либо приложение попытается прочитать записанные подобным образом данные, Windows обнаружит, что данных нет в памяти, так как для идентификатора соответствующего блока памяти указано значение NULL. В этом случае приложение, выполнившее отложенную запись, получит сообщение WM_RENDERFORMAT, в ответ на которое оно должно выполнить реальную запись данных.

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

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

SetClipboardData(wParam, hglbTextCopyBuf);

После выполнения операции не следует закрывать Clipboard.

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

В данной ситуации данные объявлены, но реально их нет в памяти. Следовательно, перед завершением приложения требуется выполнить запись данных в Clipboard во всех возможных форматах.

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

Эта проблема имеет простое решение. Перед завершением работы приложение, объявившее в Clipboard данные, но не предоставившее их, получит от Windows сообщение WM_RENDERALLFORMATS. В ответ на это сообщение оно должно выполнить реальную запись данных.

Вот возможная реализация обработчика сообщения WM_RENDERALLFORMATS:

case WM_RENDERALLFORMATS:
{
   OpenClipboard(hwnd);
   EmptyClipboard();
   SendMessage(hwnd, WM_RENDERFORMAT, CF_TEXT, 0L);
   SendMessage(hwnd, WM_RENDERFORMAT, CF_BITMAP, 0L);
   SendMessage(hwnd, WM_RENDERFORMAT, CF_PALETTE, 0L);
   CloseClipboard();
   return 0;
}

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

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

В указанной ситуации ваше приложение получит сообщение WM_DESTROYCLIPBOARD. Обработчик этого сообщения может уничтожить ненужный блок памяти.

Приложение CLIPRNDR

Для иллюстрации метода отложенной записи мы изменили приложение CLIPTXT, создав на его основе приложение CLIPRNDR. Внешний вид главного окна приложения и выполняемые им функции не изменились, однако теперь это приложение способно выполнять более экономную отложенную запись данных в Clipboard.

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

Листинг 2.11. Файл cliprndr/cliprndr.cpp

// ----------------------------------------
// Отложенная запись данных в Clipboard
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <mem.h>
#include "cliprndr.hpp"

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[]   = "ClipRndrClass";
char const szWindowTitle[] = "Delayed Rendering Demo";
char const szClipboardText[] =
  "Этот текст будет записан\r\n"
  "в универсальный буфер обмена Clipboard\r\n"
  "приложением CLIPRNDR";
// =====================================
// Функция WinMain
// =====================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpszCmdLine, int nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  if(!hPrevInstance)
    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 HGLOBAL hglbTextCopyBuf;
  LPSTR lpTextCopy;
  static HGLOBAL hglbTextPasteBuf;
  LPSTR lpTextPaste;
  static HGLOBAL hglbClipBuf;
  LPSTR lpClipBuf;

  switch (msg)
  {
    case WM_CREATE:
    {
      hglbTextPasteBuf = GlobalAlloc(GHND, 1);
      if(hglbTextPasteBuf == NULL)
        return -1;
      lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf);
      if(hglbTextPasteBuf == NULL)
        return -1;
      lpTextPaste[0] = '\0';
      GlobalUnlock(hglbTextPasteBuf);
      return 0;
    }
    case WM_PAINT:
    {
      hdc = BeginPaint(hwnd, &ps);
      lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf);
      if(lpTextPaste != NULL)
      {
        GetClientRect(hwnd, &rc);
        DrawText(hdc, lpTextPaste, -1, &rc,
          DT_LEFT | DT_EXPANDTABS);
        GlobalUnlock(hglbTextPasteBuf);
      }
      EndPaint(hwnd, &ps);
    }
    case WM_COMMAND:
    {
      switch (wParam)
      {
        // Выполняем отложенное копирование данных
        case CM_EDITCOPY:
        {
          OpenClipboard(hwnd);
          EmptyClipboard();
          SetClipboardData(CF_TEXT, NULL);
          CloseClipboard();
          return 0;
        }

        // Чтение текстовых данных из Clipboard
        case CM_EDITPASTE:
        {
          OpenClipboard(hwnd);
          hglbClipBuf = GetClipboardData(CF_TEXT);
          if(hglbClipBuf != NULL)
          {
            lpClipBuf = (LPSTR)GlobalLock(hglbClipBuf);
            if(lpClipBuf != NULL)
            {
              hglbTextPasteBuf =
                 GlobalReAlloc(hglbTextPasteBuf,
              GlobalSize(hglbClipBuf), GMEM_NODISCARD);

              lpTextPaste = 
                 (LPSTR)GlobalLock(hglbTextPasteBuf);
              if(lpTextPaste != NULL)
              {
                lstrcpy(lpTextPaste, lpClipBuf);
                InvalidateRect(hwnd, NULL, TRUE);
                GlobalUnlock(hglbTextPasteBuf);
              }
              else
                MessageBox(hwnd, "Мало памяти",
                  (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
              GlobalUnlock(hglbClipBuf);
            }
            else
              MessageBox(hwnd, "Мало памяти",
                (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
          }
          else
            MessageBox(hwnd, "Формат CF_TEXT недоступен",
            (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
          CloseClipboard();
          return 0;
        }
        case CM_HELPABOUT:
        {
          MessageBox(hwnd,
            "Приложение CLIPTXT\n(C) Фролов А.В., 1995",
            (LPSTR)szWindowTitle,
            MB_OK | MB_ICONINFORMATION);
          return 0;
        }
        case CM_FILEEXIT:
        {
          DestroyWindow(hwnd);
          return 0;
        }
        default:
          return 0;
      }
    }
    // Копируем данные во всех форматах
    case WM_RENDERALLFORMATS:
    {
       OpenClipboard(hwnd);
       EmptyClipboard();

       // Инициируем копирование в текстовом формате
       SendMessage(hwnd, WM_RENDERFORMAT, CF_TEXT, 0L);
       CloseClipboard();
       return 0;
    }
    // Копируем данные в нужном формате
    case WM_RENDERFORMAT:
    {
      // Работаем только с текстовым форматом
      if(wParam != CF_TEXT) return 0;

      hglbTextCopyBuf = GlobalAlloc(GHND,
        sizeof(szClipboardText) + 1);
      if(hglbTextCopyBuf != NULL)
      {
        lpTextCopy = (LPSTR)GlobalLock(hglbTextCopyBuf);
        if(lpTextCopy != NULL)
        {
          lstrcpy(lpTextCopy, szClipboardText);
          GlobalUnlock(hglbTextCopyBuf);

          // Фактическая запись данных
          SetClipboardData(wParam, hglbTextCopyBuf);
        }
        else
          MessageBox(hwnd, "Мало памяти",
            (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
      }
      else
        MessageBox(hwnd, "Мало памяти",
          (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
      return 0;
    }
    case WM_DESTROY:
    {
      if(hglbTextPasteBuf != NULL)
        GlobalFree(hglbTextPasteBuf);
      PostQuitMessage(0);
      return 0;
    }
    default:
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

В приложении CLIPRNDR использованы рассмотренные нами приемы. Так как данные, предназначенные для записи в Clipboard, определены статически, приложение не освобождает занимаемую ими память и, соответственно, не обрабатывает сообщение WM_DESTROYCLIPBOARD.

Файл cliprndr.hpp содержит определения констант (листинг 2.12).

Листинг 2.12. Файл cliprndr/cliprndr.hpp

#define CM_HELPABOUT 24000
#define CM_EDITPASTE 24001
#define CM_EDITCOPY  24002
#define CM_FILEEXIT  24003

Файл описания ресурсов приведен в листинге 2.13.

Листинг 2.13. Файл cliprndr/cliprndr.rc

#include "cliprndr.hpp"
APP_MENU MENU 
BEGIN
        POPUP "&File"
        BEGIN
                MENUITEM "E&xit",     CM_FILEEXIT
        END
        POPUP "&Edit"
        BEGIN
                MENUITEM "&Copy",     CM_EDITCOPY
                MENUITEM "&Paste",    CM_EDITPASTE
        END
        POPUP "&Help"
        BEGIN
                MENUITEM "&About...", CM_HELPABOUT
        END
END
APP_ICON ICON "cliprndr.ico"

Файл определения модуля вы найдете в листинге 2.14.

Листинг 2.14. Файл cliprndr/cliprndr.def

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

2.5. Нестандартный формат данных

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

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

Поэтому лучшим решением будет создание собственного формата данных для записи в Clipboard.

Как создать свой формат данных для записи в Clipboard?

Для этого достаточно зарегистрировать формат данных при помощи функции RegisterClipboardFormat:

UINT WINAPI RegisterClipboardFormat(LPCSTR lpszFormatName);

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

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

Два различных приложения, две копии одного приложения могут зарегистрировать формат с одним и тем же именем, при этом функция RegisterClipboardFormat вернет один и тот же идентификатор формата. Поэтому два приложения всегда смогут "договориться", если они знают имя нестандартного формата данных.

При использовании нестандартного формата данных возникает проблема отображения этих данных приложениями, предназначенными для динамического просмотра содержимого Clipboard, а также проблема вставки нестандартных данных в приложения, не рассчитанные на этот формат данных.

Можно частично решить эту проблему, использовав так называемые форматы отображения (display formats) CF_DSPTEXT, CF_DSPBITMAP и CF_DSPMETAFILEPICT. Эти форматы предназначены для представления, соответственно, текстовых данных, битовых изображений и метафайлов. Обычные приложения не воспринимают форматы отображения, поэтому для них следует записать в Clipboard данные не только в форматах отображения, но и в стандартных форматах.

Форматы отображения не всегда позволяют точно изобразить данные, которые записаны в Clipboard в нестандартном формате. Например, текстовый процессор может записать в Clipboard текст и параметры его шрифтового оформления. Форматы CF_TEXT и CF_DSPTEXT не позволяют передать особенности шрифтового оформления, поэтому при просмотре содержимого Clipboard вы увидите "сырой" текст, набранный системным шрифтом.

Как же заставить приложение, предназначенное для просмотра Clipboard, максимально точно отобразить нестандартные данные, записанные приложением?

Для этого следует воспользоваться форматом данных CF_OWNERDISPLAY. Если ваше приложение при вызове функции SetClipboardData указывает этот формат данных, отображение в окне просмотра Clipboard будет выполнять то приложение, которое записало нестандартные данные.

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

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

Параметр wParam сообщения WM_PAINTCLIPBOARD содержит идентификатор окна просмотра Clipboard, в котором нужно нарисовать данные.

Через параметр lParam передается идентификатор глобального блока памяти, содержащего структуру типа PAINTSTRUCT, определяющую внутреннюю область окна просмотра. Перед использованием блок памяти следует зафиксировать функцией GlobalLock, а после использования - расфиксировать функцией GlobalUnlock.

Когда окно просмотра Clipboard изменяет свои размеры, владелец Clipboard, записавший туда данные в формате CF_OWNERDISPLAY, получит сообщение WM_SIZECLIPBOARD.

Параметр wParam сообщения WM_SIZECLIPBOARD содержит идентификатор окна просмотра Clipboard. Через параметр lParam передается идентификатор глобального блока памяти, содержащего структуру типа RECT, определяющую новые размеры внутренней области окна просмотра Clipboard.

Есть еще три сообщения, которые владелец Clipboard получает от окна просмотра Clipboard.

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

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

Окно просмотра Clipboard может быть снабжено полосами просмотра. Поэтому владелец Clipboard может получить сообщения WM_HSCROLLCLIPBOARD и WM_VSCROLLCLIPBOARD. В процессе обработки сообщений владелец Clipboard может использовать функцию ScrollWindow для свертки окна просмотра.

Параметр wParam этих сообщений содержит идентификатор окна просмотра Clipboard. Через младшее слово параметра lParam передается код полосы просмотра (константы с префиксом имени SB, такие как SB_TOP, SB_BOTTOM, SB_LINEUP и т. п.). Старшее слово параметра lParam содержит значение позиции полосы просмотра.

2.6. Другие функции для работы с Clipboard

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

Функция IsClipboardFormatAvailable позволяет проверить доступность формата данных, указанного в ее единственном параметре:

BOOL WINAPI IsClipboardFormatAvailable(UINT uFormat);

Если указанный формат доступен, функция возвращает значение TRUE, в противном случае - FALSE.

Для того чтобы получить идентификаторы всех доступных форматов данных Clipboard, воспользуйтесь функцией EnumClipboardFormats:

UINT WINAPI EnumClipboardFormats(UINT uFormat);

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

С помощью функции CountClipboardFormats можно определить количество различных форматов данных Clipboard, доступных на момент вызова:

int  WINAPI CountClipboardFormats(void);

Функция GetOpenClipboardWindow возвращает идентификатор окна, открывшего на момент вызова Clipboard, или NULL при ошибке:

HWND WINAPI GetOpenClipboardWindow(void);

Функция GetClipboardOwner возвращает идентификатор окна, которое на момент вызова владеет Clipboard:

HWND WINAPI GetClipboardOwner(void);

Если вы знаете идентификатор формата данных Clipboard, то с помощью функции GetClipboardFormatName сможете получить текстовую строку имени этого формата:

int WINAPI GetClipboardFormatName(
  UINT uFormat, LPSTR lpszFormatName, int cbMax);

При вызове функции вы должны передать код формата через первый параметр, а адрес буфера, в который будет записана строка - через второй. Через третий параметр необходимо передать функции размер буфера.