Использование Буфера обмена (Clipboard)


Перевод: Sergey Kulish
Источник: Using the Clipboard
Опубликовано: 07.07.2004
Версия текста: 1.0

Реализация команд Вырезать (Cut), Копировать (Copy) и Вставить (Paste)
Выделение данных
Создание меню Правка (Edit)
Обработка сообщения WM_INITMENUPOPUP
Обработка сообщения WM_COMMAND
Копирование информации в буфер обмена
Вставка данных из буфера обмена
Регистрация формата буфера обмена
Обработка сообщений WM_RENDERFORMAT и WM_RENDERALLFORMATS
Обработка сообщения WM_DESTROYCLIPBOARD
Использование самоотображающегося (Owner-Display) формата буфера обмена
Создание окна просмотра буфера обмена
Добавление окна в цепочку наблюдателей за буфером обмена
Обработка сообщения WM_CHANGECBCHAIN
Удаление окна из цепочки наблюдателей за буфером обмена
Обработка сообщения WM_DRAWCLIPBOARD

В этой статье приводятся примеры решения следующих задач:

Реализация команд Вырезать (Cut), Копировать (Copy) и Вставить (Paste)

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

Выделение данных

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

Создание меню Правка (Edit)

Приложение должно загружать таблицу акселераторов (accelerator table) содержащую стандартные быстрые клавиши (keyboard accelerators) для пунктов меню Правка. Для обработки этих клавиш необходимо добавить функцию TranslateAccelerator в цикл обработки сообщений вашего приложения. Для получения дополнительной информации об быстрых клавишах смотрите статью Быстрые клавиши (Keyboard Accelerators).

Обработка сообщения WM_INITMENUPOPUP

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

Далее приведен пример обработки WM_INITMENUPOPUP для приложения, которое мы назовем Label.

case WM_INITMENUPOPUP:
    InitMenu((HMENU) wParam);
    break;

Функция InitMenu определена так:

void WINAPI InitMenu(HMENU hmenu)
{
    int  cMenuItems = GetMenuItemCount(hmenu);
    int  nPos;
    UINT id;
    UINT fuFlags;
    PLABELBOX pbox = (hwndSelected == NULL) ? NULL :
        (PLABELBOX) GetWindowLong(hwndSelected, 0);

    for (nPos = 0; nPos < cMenuItems; nPos++)
    {
        id = GetMenuItemID(hmenu, nPos);

        switch (id)
        {
            case IDM_CUT:
            case IDM_COPY:
            case IDM_DELETE:
                if (pbox == NULL || !pbox->fSelected)
                    fuFlags = MF_BYCOMMAND | MF_GRAYED;
                else if (pbox->fEdit)
                    fuFlags = (id != IDM_DELETE && pbox->ichSel
                            == pbox->ichCaret) ?
                        MF_BYCOMMAND | MF_GRAYED :
                        MF_BYCOMMAND | MF_ENABLED;
                else
                    fuFlags = MF_BYCOMMAND | MF_ENABLED;

                EnableMenuItem(hmenu, id, fuFlags);
                break;

            case IDM_PASTE:
                if (pbox != NULL && pbox->fEdit)
                    EnableMenuItem(hmenu, id,
                        IsClipboardFormatAvailable(CF_TEXT) ?
                            MF_BYCOMMAND | MF_ENABLED :
                            MF_BYCOMMAND | MF_GRAYED
                    );
                else
                    EnableMenuItem(hmenu, id,
                        IsClipboardFormatAvailable(
                                uLabelFormat) ?
                            MF_BYCOMMAND | MF_ENABLED :
                            MF_BYCOMMAND | MF_GRAYED
                    );

        }
    }
}

Обработка сообщения WM_COMMAND

Для работы с командами меню, добавьте обработку WM_COMMAND в оконную процедуру главного окна вашего приложения. Далее приведен пример обработки WM_COMMAND в оконной процедуре приложения Label.

case WM_COMMAND:
    switch (LOWORD(wParam))
    {
        case IDM_CUT:
            if (EditCopy())
                EditDelete();
            break;

        case IDM_COPY:
            EditCopy();
            break;

        case IDM_PASTE:
            EditPaste();
            break;

        case IDM_DELETE:
            EditDelete();
            break;

        case IDM_EXIT:
            DestroyWindow(hwnd);
    }
    break;

Для выполнения команд Копировать (Copy) и Вырезать (Cut), оконная процедура вызывает определенную в приложении функцию EditCopy. Подробно эта функция рассматривается в разделе “Копирование информации в буфер обмена”. Для выполнения команды Вставить (Paste), оконная процедура вызывает определенную в приложении функцию EditPaste. Подробно эта функция рассматривается в разделе “Вставка информации из буфера обмена.”

Копирование информации в буфер обмена

Определенная в приложении Label функция EditCopy копирует текущее выделение в буфер обмена. При этом функция выполняет следующие действия:

  1. Открывает буфер обмена, вызывая функцию OpenClipboard.
  2. Очищает буфер обмена вызывая функцию EmptyClipboard.
  3. Вызывает по одному разу функцию SetClipboardData для каждого формата буфера обмена поддерживаемого приложением.
  4. Закрывает буфер обмена вызовом функции CloseClipboard.

В зависимости от текущего выделения, функция EditCopy копирует либо диапазон текста, либо структуру, представляющую метку (label, отсюда и название нашего приложения - Label) целиком. Структура LABELBOX, определена так.

#define BOX_ELLIPSE  0
#define BOX_RECT     1

#define CCH_MAXLABEL 80
#define CX_MARGIN    12

typedef struct tagLABELBOX {  // box
    RECT rcText;    // координаты прямоугольника содержащего текст
    BOOL fSelected; // TRUE если метка выбрана
    BOOL fEdit;     // TRUE если выделен текст
    int nType;      // прямоугольный или элиптический
    int ichCaret;   // позиция каретки
    int ichSel;     // ichCaret, граница выделения
    int nXCaret;    // позиция окна соотвествующая ichCaret
    int nXSel;      // позиция окна соотвествующая ichSel
    int cchLabel;   // длина текста в atchLabel
    TCHAR atchLabel[CCH_MAXLABEL];
} LABELBOX, *PLABELBOX;

Ниже приведена функция EditCopy:

BOOL WINAPI EditCopy(VOID)
{
    PLABELBOX pbox;
    LPTSTR  lptstrCopy;
    HGLOBAL hglbCopy;
    int ich1, ich2, cch;

    if (hwndSelected == NULL)
        return FALSE;

    // Открываем буфер обмена и очищаем его.
    if (!OpenClipboard(hwndMain))
        return FALSE;
    EmptyClipboard();

    // Получаем указатель на структуру для выделенной метки.
    pbox = (PLABELBOX) GetWindowLong(hwndSelected, 0);

    // Если выделен текст, то копируем его, используя формат CF_TEXT.
    if (pbox->fEdit)
    {
        if (pbox->ichSel == pbox->ichCaret)     // длина - ноль
        {  
            CloseClipboard();                   // выделение
            return FALSE;
        }

        if (pbox->ichSel < pbox->ichCaret)
        {
            ich1 = pbox->ichSel;
            ich2 = pbox->ichCaret;
        }
        else
        {
            ich1 = pbox->ichCaret;
            ich2 = pbox->ichSel;
        }
        cch = ich2 - ich1;

        // Размещаем объект для текста в глобальной памяти.
        hglbCopy = GlobalAlloc(GMEM_MOVEABLE,
            (cch + 1) * sizeof(TCHAR));
        if (hglbCopy == NULL)
        {
            CloseClipboard();
            return FALSE;
        }

        // Блокируем дескриптор и копируем текст в буфер.
        lptstrCopy = GlobalLock(hglbCopy);
        memcpy(lptstrCopy, &pbox->atchLabel[ich1], cch * sizeof(TCHAR));
        lptstrCopy[cch] = (TCHAR) 0;    // нулевой символ
        GlobalUnlock(hglbCopy);

        // Помещаем дескриптор в буфер обмена.
        SetClipboardData(CF_TEXT, hglbCopy);
    }
    // Если текст не выделен, копируем метку полностью.
    else
    {
        // Сохраняем копию выделенной метки как объект локальной памяти.
        // Эта копия используется для отображения данных по запросу.
        // Память освобождается по приходу сообщения WM_DESTROYCLIPBOARD.
        pboxLocalClip = (PLABELBOX) LocalAlloc(
            LMEM_FIXED,
            sizeof(LABELBOX)
        );
        if (pboxLocalClip == NULL)
        {
            CloseClipboard();
            return FALSE;
        }
        memcpy(pboxLocalClip, pbox, sizeof(LABELBOX));
        pboxLocalClip->fSelected = FALSE;
        pboxLocalClip->fEdit = FALSE;

        // Помещаем регистрируемый формат буфера обмена,
        // самоотображающийся (owner-display) формат, и формат CF_TEXT
        // в буфер обмена использую отложенную (delayed) отрисовку.
        SetClipboardData(uLabelFormat, NULL);
        SetClipboardData(CF_OWNERDISPLAY, NULL);
        SetClipboardData(CF_TEXT, NULL);
    }

    // Закрываем буфер обмена
    CloseClipboard();

    return TRUE;
}

Вставка данных из буфера обмена

Реализованая в приложении Label функция EditPaste вставляет содержимое буфера обмена. При этом функция выполняет следующие действия:

1. Открывает буфер обмена вызовом функции OpenClipboard.
2. Определяет доступный формат для извлечения данных из буфера обмена.
3. Получает дескриптор данных в выбранном формате вызовом функции GetClipboardData.
4. Вставляет копию данных в документ.

ПРЕДУПРЕЖДЕНИЕ

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

5. Закрывает буфер обмена вызывая функцию CloseClipboard.

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

Определение структуры LABELBOX было приведено выше.

Далее приведена функция EditPaste.

VOID WINAPI EditPaste(VOID)
{
    PLABELBOX pbox;
    HGLOBAL   hglb;
    LPTSTR    lptstr;
    PLABELBOX pboxCopy;
    int cx, cy;
    HWND hwnd;

    pbox = hwndSelected == NULL ? NULL :
        (PLABELBOX) GetWindowLong(hwndSelected, 0);

    // Если сейчас режим правки,
    // получить текст из буфера обмена.
    if (pbox != NULL && pbox->fEdit)
    {
        if (!IsClipboardFormatAvailable(CF_TEXT))
            return;
        if (!OpenClipboard(hwndMain))
            return;

        hglb = GetClipboardData(CF_TEXT);
        if (hglb != NULL)
        {
            lptstr = GlobalLock(hglb);
            if (lptstr != NULL)
            {
                // Вызов пользовательской функции ReplaceSelection
                // для вставки текста и перерисовки окна
                ReplaceSelection(hwndSelected, pbox, lptstr);
                GlobalUnlock(hglb);
            }
        }
        CloseClipboard();

        return;
    }

    // Если не режим правки,
    // создать окно метки.
    if (!IsClipboardFormatAvailable(uLabelFormat))
        return;
    if (!OpenClipboard(hwndMain))
        return;

    hglb = GetClipboardData(uLabelFormat);
    if (hglb != NULL)
    {
        pboxCopy = GlobalLock(hglb);
        if (pboxCopy != NULL)
        {
            cx = pboxCopy->rcText.right + CX_MARGIN;
            cy = pboxCopy->rcText.top * 2 + cyText;

            hwnd = CreateWindowEx(
                WS_EX_NOPARENTNOTIFY | WS_EX_TRANSPARENT,
                atchClassChild, NULL, WS_CHILD, 0, 0, cx, cy,
                hwndMain, NULL, hinst, NULL
            );
            if (hwnd != NULL)
            {
                pbox = (PLABELBOX) GetWindowLong(hwnd, 0);
                memcpy(pbox, pboxCopy, sizeof(LABELBOX));
                ShowWindow(hwnd, SW_SHOWNORMAL);
                SetFocus(hwnd);
            }
            GlobalUnlock(hglb);
        }
    }
    CloseClipboard();
}

Регистрация формата буфера обмена

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

// Зарегистрировать формат буфера обмена

// Мы предполагаем что строка atchTemp содержит имя формата
// с терминальным нулем на конце, иначе она обрезается.
LoadString(hinstCurrent, IDS_FORMATNAME, atchTemp,
    sizeof(atchTemp)/sizeof(TCHAR));
uLabelFormat = RegisterClipboardFormat(atchTemp);
if (uLabelFormat == 0)
    return FALSE;

Обработка сообщений WM_RENDERFORMAT и WM_RENDERALLFORMATS

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

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

Чтобы отрисовывать формат буфера обмена оконная процедура должна поместить дескриптор данных для буфера обмена, используя функцию SetClipboardData. При этом буфер обмена нельзя открывать до вызова функции SetClipboardData.

Приложение Label обрабатывает сообщения WM_RENDERFORMAT и WM_RENDERALLFORMATS следующим образом:

case WM_RENDERFORMAT:
    RenderFormat((UINT) wParam);
    break;

case WM_RENDERALLFORMATS:
    RenderFormat(uLabelFormat);
    RenderFormat(CF_TEXT);
    break;

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

void WINAPI RenderFormat(UINT uFormat)
{
    HGLOBAL hglb;
    PLABELBOX pbox;
    LPTSTR  lptstr;
    int cch;

    if (pboxLocalClip == NULL)
        return;

    if (uFormat == CF_TEXT)
    {
        // Выделить память под буфер для текста
        cch = pboxLocalClip->cchLabel;
        hglb = GlobalAlloc(GMEM_MOVEABLE,
            (cch + 1) * sizeof(TCHAR));
        if (hglb == NULL)
            return;

        // Скопировать текст из pboxLocalClip.
        lptstr = GlobalLock(hglb);
        memcpy(lptstr, pboxLocalClip->atchLabel,
            cch * sizeof(TCHAR));
        lptstr[cch] = (TCHAR) 0;
        GlobalUnlock(hglb);

        // Поместить дескриптор в буфер обмена
        SetClipboardData(CF_TEXT, hglb);
    }
    else if (uFormat == uLabelFormat)
    {
        hglb = GlobalAlloc(GMEM_MOVEABLE, sizeof(LABELBOX));
        if (hglb == NULL)
            return;
        pbox = GlobalLock(hglb);
        memcpy(pbox, pboxLocalClip, sizeof(LABELBOX));
        GlobalUnlock(hglb);

        SetClipboardData(uLabelFormat, hglb);
    }
}

Обработка сообщения WM_DESTROYCLIPBOARD

Окно может обработать сообщение WM_DESTROYCLIPBOARD для того, чтобы освободить ресурсы задействованные на поддержку задерживаемой отрисовки формата. К примеру приложение Label, при копировании в буфер обмена, распределяет память под локальный объект. Приложение уничтожает этот объект в ответ на сообщение WM_DESTROYCLIPBOARD, как показано далее.

case WM_DESTROYCLIPBOARD:
    if (pboxLocalClip != NULL)
    {
        LocalFree(pboxLocalClip);
        pboxLocalClip = NULL;
    }
    break;

Использование самоотображающегося (Owner-Display) формата буфера обмена

Если окно помещает информацию в буфер обмена используя формат буфера обмена CF_OWNERDISPLAY, то оно должно делать следующее:

ПРИМЕЧАНИЕ

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

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

LRESULT CALLBACK MainWindowProc(hwnd, msg, wParam, lParam)
HWND hwnd;
UINT msg;
WPARAM wParam;
LPARAM lParam;
{
    static RECT rcViewer;

    RECT rc;
    LPRECT lprc;
    LPPAINTSTRUCT lpps;

    switch (msg)
    {
        //
        // Обработать другие сообщения
        //
        case WM_PAINTCLIPBOARD:
            // Определить размер метки
            SetRect(&rc, 0, 0,
                pboxLocalClip->rcText.right + CX_MARGIN,
                pboxLocalClip->rcText.top * 2 + cyText
            );

            // Отцентрировать изображение в окне просмотра буфера обмена
            if (rc.right < rcViewer.right)
            {
                rc.left = (rcViewer.right - rc.right) / 2;
                rc.right += rc.left;
            }
            if (rc.bottom < rcViewer.bottom)
            {
                rc.top = (rcViewer.bottom - rc.bottom) / 2;
                rc.bottom += rc.top;
            }

            // Нарисовать изображение, используя указанную структуру PAINTSTRUCT
            // вызвав пользовательскую функцию PaintLabel
            lpps = (LPPAINTSTRUCT) GlobalLock((HGLOBAL) lParam);
            PaintLabel(lpps, pboxLocalClip, &rc);
            GlobalUnlock((HGLOBAL) lParam);
            break;

        case WM_SIZECLIPBOARD:
            // Сохранить размер окна в статической структуре RECT
            lprc = (LPRECT) GlobalLock((HGLOBAL) lParam);
            memcpy(&rcViewer, lprc, sizeof(RECT));
            GlobalUnlock((HGLOBAL) lParam);

            // Установить диапазон прокрутки в нуль (таким образом мы
            // исключаем необходимость обрабатывать сообщения
            // WM_HSCROLLCLIPBOARD и WM_VSCROLLCLIPBOARD).
            SetScrollRange((HWND) wParam, SB_HORZ, 0, 0, TRUE);
            SetScrollRange((HWND) wParam, SB_VERT, 0, 0, TRUE);

            break;

        case WM_ASKCBFORMATNAME:
            LoadString(hinst, IDS_OWNERDISPLAY,
                (LPSTR) lParam, wParam);
            break;

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

Создание окна просмотра буфера обмена

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

Добавление окна в цепочку наблюдателей за буфером обмена

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

Следующий пример демонстрирует добавление окна в цепочку наблюдателей за буфером обмена в ответ на сообщение WM_CREATE.

case WM_CREATE:

    // Добавить окно в цепочку наблюдателей за буфером обмена.
    hwndNextViewer = SetClipboardViewer(hwnd);
    break;

Приведенные далее примеры кода:

Обработка сообщения WM_CHANGECBCHAIN

Окно просмотра буфера обмена получает сообщение WM_CHANGECBCHAIN когда другое окно удаляет себя из цепочки наблюдателей за буфером обмена. Если удаляемое окно - следующее в цепочке, то окно, получающее сообщение должно удалить его из цепочки и запомнить дескриптор окна, следующего за удаленным. Иначе, сообщение должно быть послано следующему окну в цепочке.

Следующий пример показывает, как обрабатывать сообщение WM_CHANGECBCHAIN.

case WM_CHANGECBCHAIN:

    // Если следующее окно закрывается, восстановить цепочку
    if ((HWND) wParam == hwndNextViewer)
        hwndNextViewer = (HWND) lParam;

    // Иначе пропустить сообщение к следующей ссылке
    else if (hwndNextViewer != NULL)
        SendMessage(hwndNextViewer, uMsg, wParam, lParam);

    break;

Удаление окна из цепочки наблюдателей за буфером обмена

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

case WM_DESTROY:
    ChangeClipboardChain(hwnd, hwndNextViewer);
    PostQuitMessage(0);
    break;

Обработка сообщения WM_DRAWCLIPBOARD

Сообщение WM_DRAWCLIPBOARD уведомляет окно просмотра буфера обмена о том, что содержимое буфера обмена изменилось. Окну следует сделать следующее при поступлении сообщения WM_DRAWCLIPBOARD:

  1. Определить, какой из доступных форматов буфера обмена следует использовать для отображения.
  2. Извлечь данные буфера обмена и отобразить их в окне. Или, если формат буфера обмена CF_OWNERDISPLAY, послать сообщение WM_PAINTCLIPBOARD владельцу буфера обмена.
  3. Послать сообщение следующему окну в цепочке просмотра буфера обмена.

Пример обработки сообщения WM_DRAWCLIPBOARD приведен в следующем листинге:

Пример просмотрщика буфера обмена
HINSTANCE hinst;
UINT uFormat = (UINT)(-1);
BOOL fAuto = TRUE;

LRESULT APIENTRY MainWndProc(hwnd, uMsg, wParam, lParam)
HWND hwnd;
UINT uMsg;
WPARAM wParam;
LPARAM lParam;
{
    static HWND hwndNextViewer;

    HDC hdc;
    HDC hdcMem;
    PAINTSTRUCT ps;
    LPPAINTSTRUCT lpps;
    RECT rc;
    LPRECT lprc;
    HGLOBAL hglb;
    LPSTR lpstr;
    HBITMAP hbm;
    HENHMETAFILE hemf;
    HWND hwndOwner;

    switch (uMsg)
    {
        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);

            // Ветвление в зависимости от формата из буфера обмена
            switch (uFormat)
            {
                case CF_OWNERDISPLAY:
                    hwndOwner = GetClipboardOwner();
                    hglb = GlobalAlloc(GMEM_MOVEABLE,
                        sizeof(PAINTSTRUCT));
                    lpps = GlobalLock(hglb);
                    memcpy(lpps, &ps, sizeof(PAINTSTRUCT));
                    GlobalUnlock(hglb);

                    SendMessage(hwndOwner, WM_PAINTCLIPBOARD,
                        (WPARAM) hwnd, (LPARAM) hglb);

                    GlobalFree(hglb);
                    break;

                case CF_BITMAP:
                    hdcMem = CreateCompatibleDC(hdc);
                    if (hdcMem != NULL)
                    {
                        if (OpenClipboard(hwnd))
                        {
                            hbm = (HBITMAP)
                                GetClipboardData(uFormat);
                            SelectObject(hdcMem, hbm);
                            GetClientRect(hwnd, &rc);

                            BitBlt(hdc, 0, 0, rc.right, rc.bottom,
                                hdcMem, 0, 0, SRCCOPY);
                            CloseClipboard();
                        }
                        DeleteDC(hdcMem);
                    }
                    break;

                case CF_TEXT:
                    if (OpenClipboard(hwnd))
                    {
                        hglb = GetClipboardData(uFormat);
                        lpstr = GlobalLock(hglb);

                        GetClientRect(hwnd, &rc);
                        DrawText(hdc, lpstr, -1, &rc, DT_LEFT);

                        GlobalUnlock(hglb);
                        CloseClipboard();
                    }
                    break;

                case CF_ENHMETAFILE:
                    if (OpenClipboard(hwnd))
                    {
                        hemf = GetClipboardData(uFormat);
                        GetClientRect(hwnd, &rc);
                        PlayEnhMetaFile(hdc, hemf, &rc);
                        CloseClipboard();
                    }
                    break;

                case 0:
                    GetClientRect(hwnd, &rc);
                    DrawText(hdc, "The clipboard is empty.", -1, 
                        &rc, DT_CENTER | DT_SINGLELINE | 
                        DT_VCENTER); 
                    DrawText(hdc, "Буфер обмена пуст.", -1,
                        &rc, DT_CENTER | DT_SINGLELINE |
                        DT_VCENTER);
                    break;

                default:
                    GetClientRect(hwnd, &rc);
                    DrawText(hdc, "Unable to display format.", -1, 
                        &rc, DT_CENTER | DT_SINGLELINE | 
                        DT_VCENTER);
                    DrawText(hdc, "Невозможно отобразить формат.", -1,
                        &rc, DT_CENTER | DT_SINGLELINE |
                        DT_VCENTER);
            }
            EndPaint(hwnd, &ps);
            break;

        case WM_SIZE:
            if (uFormat == CF_OWNERDISPLAY)
            {
                hwndOwner = GetClipboardOwner();
                hglb = GlobalAlloc(GMEM_MOVEABLE, sizeof(RECT));
                lprc = GlobalLock(hglb);
                GetClientRect(hwnd, lprc);
                GlobalUnlock(hglb);

                SendMessage(hwndOwner, WM_SIZECLIPBOARD,
                    (WPARAM) hwnd, (LPARAM) hglb);

                GlobalFree(hglb);
            }
            break;

        case WM_CREATE:

            // Добавление окна в цепочку просмотра буфера обмена
            hwndNextViewer = SetClipboardViewer(hwnd);
            break;

        case WM_CHANGECBCHAIN:

            // Если следующее окно закрывается, востановить цепочку
            if ((HWND) wParam == hwndNextViewer)
                hwndNextViewer = (HWND) lParam;

            // Иначе, пропустить сообщение к следующей ссылке
            else if (hwndNextViewer != NULL)
                SendMessage(hwndNextViewer, uMsg, wParam, lParam);

            break;

        case WM_DESTROY:
            ChangeClipboardChain(hwnd, hwndNextViewer);
            PostQuitMessage(0);
            break;

        case WM_DRAWCLIPBOARD:  // содержимое буфера обмена изменилось

            // Обновить окно используя формат буфера обмена Auto
            SetAutoView(hwnd);

            // Пропустить сообщение к следующему окну в цепочке просмотра буфера обмена
            SendMessage(hwndNextViewer, uMsg, wParam, lParam);
            break;

        case WM_INITMENUPOPUP:
            if (!HIWORD(lParam))
                InitMenu(hwnd, (HMENU) wParam);
            break;

        case WM_COMMAND:
            switch (LOWORD(wParam))
            {
                case IDM_EXIT:
                    DestroyWindow(hwnd);
                    break;

                case IDM_AUTO:
                    SetAutoView(hwnd);
                    break;

                default:
                    fAuto = FALSE;
                    uFormat = LOWORD(wParam);
                    InvalidateRect(hwnd, NULL, TRUE);
            }
            break;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    return (LRESULT) NULL;
}

void WINAPI SetAutoView(HWND hwnd)
{
    static UINT auPriorityList[] = {
        CF_OWNERDISPLAY,
        CF_TEXT,
        CF_ENHMETAFILE,
        CF_BITMAP
    };

    uFormat = GetPriorityClipboardFormat(auPriorityList, 4);
    fAuto = TRUE;

    InvalidateRect(hwnd, NULL, TRUE);
    UpdateWindow(hwnd);
}

void WINAPI InitMenu(HWND hwnd, HMENU hmenu)
{
    UINT uFormat;
    char szFormatName[80];
    LPCSTR lpFormatName;
    UINT fuFlags;
    UINT idMenuItem;

    // Если меню не отображается, то инициализация не нужна
    if (GetMenuItemID(hmenu, 0) != IDM_AUTO)
        return;

    // Удалить все пункты меню за исключением первого
    while (GetMenuItemCount(hmenu) > 1)
        DeleteMenu(hmenu, 1, MF_BYPOSITION);

    // Установить или снять галочку с пункта меню Auto
    fuFlags = fAuto ? MF_BYCOMMAND | MF_CHECKED :
        MF_BYCOMMAND | MF_UNCHECKED;
    CheckMenuItem(hmenu, IDM_AUTO, fuFlags);

    // Если нет форматов буфера обмена, выйти
    if (CountClipboardFormats() == 0)
        return;

    // Открыть буфер обмена
    if (!OpenClipboard(hwnd))
        return;

    // Добавить разделитель и затем пункты меню для каждого формата
    AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);
    uFormat = EnumClipboardFormats(0);

    while (uFormat)
    {
        // Вызвать функцию из этого приложения для получения имени
        // формата буфера обмена
        lpFormatName = GetPredefinedClipboardFormatName(uFormat);

        // Для зарегистрированных форматов, получить его зарегистрированное имя
        if (lpFormatName == NULL)
        {
        // Заметьте, что если название формата больше чем место
      // отведенное в буфере, то оно обрезается
            if (GetClipboardFormatName(uFormat, szFormatName,
                    sizeof(szFormatName)))
                lpFormatName = szFormatName;
            else
                lpFormatName = "(неизвестно)";
        }

        // Добавить пункт меню для формата. Для отображаемых форматов
        // используйте ID формата для ID меню.
        if (IsDisplayableFormat(uFormat))
        {
            fuFlags = MF_STRING;
            idMenuItem = uFormat;
        }
        else
        {
            fuFlags = MF_STRING | MF_GRAYED;
            idMenuItem = 0;
        }
        AppendMenu(hmenu, fuFlags, idMenuItem, lpFormatName);

        uFormat = EnumClipboardFormats(uFormat);
    }
    CloseClipboard();

}

BOOL WINAPI IsDisplayableFormat(UINT uFormat)
{
    switch (uFormat)
    {
        case CF_OWNERDISPLAY:
        case CF_TEXT:
        case CF_ENHMETAFILE:
        case CF_BITMAP:
            return TRUE;
    }
    return FALSE;
}


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