Поделиться через


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

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

Реализация команд выреза, копирования и вставки

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

Выбор данных

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

Создание меню "Изменить"

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

Обработка сообщения 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; 

Чтобы выполнить команды Копировать и Вырезать, процедура окна вызывает функцию, определяемую приложением EditCopy. Дополнительные сведения см. в копирование сведений в буфер обмена. Чтобы выполнить команду вставки, процедура окна вызывает функцию EditPaste, определяемую приложением. Дополнительные сведения о функции EditPaste см. в разделе Вставка сведений из буфера обмена.

Копирование сведений в буфер обмена

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

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

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

#define BOX_ELLIPSE  0 
#define BOX_RECT     1 
 
#define CCH_MAXLABEL 80 
#define CX_MARGIN    12 
 
typedef struct tagLABELBOX {  // box 
    RECT rcText;    // coordinates of rectangle containing text 
    BOOL fSelected; // TRUE if the label is selected 
    BOOL fEdit;     // TRUE if text is selected 
    int nType;      // rectangular or elliptical 
    int ichCaret;   // caret position 
    int ichSel;     // with ichCaret, delimits selection 
    int nXCaret;    // window position corresponding to ichCaret 
    int nXSel;      // window position corresponding to ichSel 
    int cchLabel;   // length of text in 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; 
 
    // Open the clipboard, and empty it. 
 
    if (!OpenClipboard(hwndMain)) 
        return FALSE; 
    EmptyClipboard(); 
 
    // Get a pointer to the structure for the selected label. 
 
    pbox = (PLABELBOX) GetWindowLong(hwndSelected, 0); 
 
    // If text is selected, copy it using the CF_TEXT format. 
 
    if (pbox->fEdit) 
    { 
        if (pbox->ichSel == pbox->ichCaret)     // zero length
        {   
            CloseClipboard();                   // selection 
            return FALSE; 
        } 
 
        if (pbox->ichSel < pbox->ichCaret) 
        { 
            ich1 = pbox->ichSel; 
            ich2 = pbox->ichCaret; 
        } 
        else 
        { 
            ich1 = pbox->ichCaret; 
            ich2 = pbox->ichSel; 
        } 
        cch = ich2 - ich1; 
 
        // Allocate a global memory object for the text. 
 
        hglbCopy = GlobalAlloc(GMEM_MOVEABLE, 
            (cch + 1) * sizeof(TCHAR)); 
        if (hglbCopy == NULL) 
        { 
            CloseClipboard(); 
            return FALSE; 
        } 
 
        // Lock the handle and copy the text to the buffer. 
 
        lptstrCopy = GlobalLock(hglbCopy); 
        memcpy(lptstrCopy, &pbox->atchLabel[ich1], 
            cch * sizeof(TCHAR)); 
        lptstrCopy[cch] = (TCHAR) 0;    // null character 
        GlobalUnlock(hglbCopy); 
 
        // Place the handle on the clipboard. 
 
        SetClipboardData(CF_TEXT, hglbCopy); 
    } 
 
    // If no text is selected, the label as a whole is copied. 
 
    else 
    { 
        // Save a copy of the selected label as a local memory 
        // object. This copy is used to render data on request. 
        // It is freed in response to the WM_DESTROYCLIPBOARD 
        // message. 
 
        pboxLocalClip = (PLABELBOX) LocalAlloc( 
            LMEM_FIXED, 
            sizeof(LABELBOX) 
        ); 
        if (pboxLocalClip == NULL) 
        { 
            CloseClipboard(); 
            return FALSE; 
        } 
        memcpy(pboxLocalClip, pbox, sizeof(LABELBOX)); 
        pboxLocalClip->fSelected = FALSE; 
        pboxLocalClip->fEdit = FALSE; 
 
        // Place a registered clipboard format, the owner-display 
        // format, and the CF_TEXT format on the clipboard using 
        // delayed rendering. 
 
        SetClipboardData(uLabelFormat, NULL); 
        SetClipboardData(CF_OWNERDISPLAY, NULL); 
        SetClipboardData(CF_TEXT, NULL); 
    } 
 
    // Close the clipboard. 
 
    CloseClipboard(); 
 
    return TRUE; 
}

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

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

  1. Открывает буфер обмена при помощи вызова функции OpenClipboard.
  2. Определяет, какой из доступных форматов буфера обмена следует получить.
  3. Извлекает дескриптор данных в выбранном формате, вызвав функцию GetClipboardData.
  4. Вставляет копию данных в документ. Дескриптор, возвращаемый GetClipboardData, по-прежнему принадлежит буферу обмена, поэтому приложение не должно освободить его или оставить заблокированным.
  5. Закрытие буфера обмена происходит через вызов функции CloseClipboard.

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

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

#define BOX_ELLIPSE  0 
#define BOX_RECT     1 
 
#define CCH_MAXLABEL 80 
#define CX_MARGIN    12 
 
typedef struct tagLABELBOX {  // box 
    RECT rcText;    // coordinates of rectangle containing text 
    BOOL fSelected; // TRUE if the label is selected 
    BOOL fEdit;     // TRUE if text is selected 
    int nType;      // rectangular or elliptical 
    int ichCaret;   // caret position 
    int ichSel;     // with ichCaret, delimits selection 
    int nXCaret;    // window position corresponding to ichCaret 
    int nXSel;      // window position corresponding to ichSel 
    int cchLabel;   // length of text in atchLabel 
    TCHAR atchLabel[CCH_MAXLABEL]; 
} LABELBOX, *PLABELBOX;

Ниже приведена функция 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 the application is in edit mode, 
    // get the clipboard text. 
 
    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) 
            { 
                // Call the application-defined ReplaceSelection 
                // function to insert the text and repaint the 
                // window. 
 
                ReplaceSelection(hwndSelected, pbox, lptstr); 
                GlobalUnlock(hglb); 
            } 
        } 
        CloseClipboard(); 
 
        return; 
    } 
 
    // If the application is not in edit mode, 
    // create a label window. 
 
    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 в функцию инициализации экземпляра приложения, как показано ниже.

// Register a clipboard format. 
 
// We assume that atchTemp can contain the format name and
// a null-terminator, otherwise it is truncated.
//
LoadString(hinstCurrent, IDS_FORMATNAME, atchTemp, 
    sizeof(atchTemp)/sizeof(TCHAR)); 
uLabelFormat = RegisterClipboardFormat(atchTemp); 
if (uLabelFormat == 0) 
    return FALSE;

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

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

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

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

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

case WM_RENDERFORMAT: 
    RenderFormat((UINT) wParam); 
    break; 
 
case WM_RENDERALLFORMATS:
    if (OpenClipboard(hwnd))
    {
        if (GetClipboardOwner() == hwnd)
        {
            RenderFormat(uLabelFormat);
            RenderFormat(CF_TEXT);
        }
        CloseClipboard();
    }
    break;

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

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

#define BOX_ELLIPSE  0 
#define BOX_RECT     1 
 
#define CCH_MAXLABEL 80 
#define CX_MARGIN    12 
 
typedef struct tagLABELBOX {  // box 
    RECT rcText;    // coordinates of rectangle containing text 
    BOOL fSelected; // TRUE if the label is selected 
    BOOL fEdit;     // TRUE if text is selected 
    int nType;      // rectangular or elliptical 
    int ichCaret;   // caret position 
    int ichSel;     // with ichCaret, delimits selection 
    int nXCaret;    // window position corresponding to ichCaret 
    int nXSel;      // window position corresponding to ichSel 
    int cchLabel;   // length of text in atchLabel 
    TCHAR atchLabel[CCH_MAXLABEL]; 
} LABELBOX, *PLABELBOX;
void WINAPI RenderFormat(UINT uFormat) 
{ 
    HGLOBAL hglb; 
    PLABELBOX pbox; 
    LPTSTR  lptstr; 
    int cch; 
 
    if (pboxLocalClip == NULL) 
        return; 
 
    if (uFormat == CF_TEXT) 
    { 
        // Allocate a buffer for the text. 
 
        cch = pboxLocalClip->cchLabel; 
        hglb = GlobalAlloc(GMEM_MOVEABLE, 
            (cch + 1) * sizeof(TCHAR)); 
        if (hglb == NULL) 
            return; 
 
        // Copy the text from pboxLocalClip. 
 
        lptstr = GlobalLock(hglb); 
        memcpy(lptstr, pboxLocalClip->atchLabel, 
            cch * sizeof(TCHAR)); 
        lptstr[cch] = (TCHAR) 0; 
        GlobalUnlock(hglb); 
 
        // Place the handle on the clipboard. 
 
        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, необходимо выполнить следующее:

  • Обработайте сообщение WM_PAINTCLIPBOARD. Это сообщение отправляется владельцу буфера обмена, когда необходимо перерисовать часть окна просмотра буфера обмена.
  • Обработайте сообщение WM_SIZECLIPBOARD. Это сообщение отправляется владельцу буфера обмена при изменении размера окна просмотра буфера обмена или его содержимого. Как правило, окно реагирует на это сообщение, задав позиции прокрутки и диапазоны для окна просмотра буфера обмена. В ответ на это сообщение приложение Label также обновляет структуру SIZE для окна просмотра буфера обмена.
  • Обработайте сообщения WM_HSCROLLCLIPBOARD и WM_VSCROLLCLIPBOARD. Эти сообщения отправляются владельцу буфера обмена при возникновении события полосы прокрутки в окне просмотра буфера обмена.
  • Обработайте сообщение WM_ASKCBFORMATNAME. Окно просмотра буфера обмена отправляет это сообщение приложению, чтобы получить имя формата отображения владельца.

Процедура окна для приложения 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) 
    { 
        //
        // Handle other messages.
        //
        case WM_PAINTCLIPBOARD: 
            // Determine the dimensions of the label. 
 
            SetRect(&rc, 0, 0, 
                pboxLocalClip->rcText.right + CX_MARGIN, 
                pboxLocalClip->rcText.top * 2 + cyText 
            ); 
 
            // Center the image in the clipboard viewer window. 
 
            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; 
            } 
 
            // Paint the image, using the specified PAINTSTRUCT 
            // structure, by calling the application-defined 
            // PaintLabel function. 
 
            lpps = (LPPAINTSTRUCT) GlobalLock((HGLOBAL) lParam); 
            PaintLabel(lpps, pboxLocalClip, &rc); 
            GlobalUnlock((HGLOBAL) lParam); 
            break; 
 
        case WM_SIZECLIPBOARD: 
            // Save the dimensions of the window in a static 
            // RECT structure. 
 
            lprc = (LPRECT) GlobalLock((HGLOBAL) lParam); 
            memcpy(&rcViewer, lprc, sizeof(RECT)); 
            GlobalUnlock((HGLOBAL) lParam); 
 
            // Set the scroll ranges to zero (thus eliminating 
            // the need to process the WM_HSCROLLCLIPBOARD and 
            // WM_VSCROLLCLIPBOARD messages). 
 
            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; 
}

Мониторинг содержимого буфера обмена

Существует три способа мониторинга изменений в буфере обмена. Самый старый метод — создать окно просмотра буфера обмена. Windows 2000 добавила возможность запрашивать номер последовательности буфера обмена, а Windows Vista добавила поддержку прослушивателей форматов буфера обмена. Окна просмотра буфера обмена поддерживаются для обратной совместимости с более ранними версиями Windows. Новые программы должны использовать прослушиватели формата буфера обмена или номер последовательности буфера обмена.

Запрос номера последовательности буфера обмена

При каждом изменении содержимого буфера обмена 32-разрядное значение, известное как порядковый номер буфера обмена, увеличивается. Программа может получить текущий номер последовательности буфера обмена, вызвав функцию GetClipboardSequenceNumber. Сравнивая значение, возвращаемое с значением, возвращаемым предыдущим вызовом GetClipboardSequenceNumber, программа может определить, изменилось ли содержимое буфера обмена. Этот метод более подходит для программ, которые кэшируют результаты на основе текущего содержимого буфера обмена и должны знать, допустимы ли вычисления перед использованием результатов из этого кэша. Обратите внимание, что это не метод уведомления и не следует использовать в цикле опроса. Чтобы получать уведомления при изменении содержимого буфера обмена, используйте прослушиватель формата буфера обмена или средство просмотра буфера обмена.

Создание прослушивателя формата буфера обмена

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

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

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

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

  • Добавьте окно в цепочку просмотра буфера обмена.
  • Обработайте сообщение WM_CHANGECBCHAIN.
  • Обработайте сообщение WM_DRAWCLIPBOARD.
  • Удалите окно из цепочки просмотра буфера обмена, прежде чем оно будет уничтожено.

Добавление окна в цепочку просмотра буфера обмена

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

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

case WM_CREATE: 
 
    // Add the window to the clipboard viewer chain. 
 
    hwndNextViewer = SetClipboardViewer(hwnd); 
    break;

Фрагменты кода показаны для следующих задач:

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

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

В следующем примере показана обработка сообщения WM_CHANGECBCHAIN.

case WM_CHANGECBCHAIN: 
 
    // If the next window is closing, repair the chain. 
 
    if ((HWND) wParam == hwndNextViewer) 
        hwndNextViewer = (HWND) lParam; 
 
    // Otherwise, pass the message to the next link. 
 
    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); 
 
            // Branch depending on the clipboard format. 
 
            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); 
                    break; 
 
                default: 
                    GetClientRect(hwnd, &rc); 
                    DrawText(hdc, "Unable to display format.", -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: 
 
            // Add the window to the clipboard viewer chain. 
 
            hwndNextViewer = SetClipboardViewer(hwnd); 
            break; 
 
        case WM_CHANGECBCHAIN: 
 
            // If the next window is closing, repair the chain. 
 
            if ((HWND) wParam == hwndNextViewer) 
                hwndNextViewer = (HWND) lParam; 
 
            // Otherwise, pass the message to the next link. 
 
            else if (hwndNextViewer != NULL) 
                SendMessage(hwndNextViewer, uMsg, wParam, lParam); 
 
            break; 
 
        case WM_DESTROY: 
            ChangeClipboardChain(hwnd, hwndNextViewer); 
            PostQuitMessage(0); 
            break; 
 
        case WM_DRAWCLIPBOARD:  // clipboard contents changed. 
 
            // Update the window by using Auto clipboard format. 
 
            SetAutoView(hwnd); 
 
            // Pass the message to the next window in clipboard 
            // viewer chain. 
 
            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 a menu is not the display menu, no initialization is necessary. 
 
    if (GetMenuItemID(hmenu, 0) != IDM_AUTO) 
        return; 
 
    // Delete all menu items except the first. 
 
    while (GetMenuItemCount(hmenu) > 1) 
        DeleteMenu(hmenu, 1, MF_BYPOSITION); 
 
    // Check or uncheck the Auto menu item. 
 
    fuFlags = fAuto ? MF_BYCOMMAND | MF_CHECKED : 
        MF_BYCOMMAND | MF_UNCHECKED; 
    CheckMenuItem(hmenu, IDM_AUTO, fuFlags); 
 
    // If there are no clipboard formats, return. 
 
    if (CountClipboardFormats() == 0) 
        return; 
 
    // Open the clipboard. 
 
    if (!OpenClipboard(hwnd)) 
        return; 
 
    // Add a separator and then a menu item for each format. 
 
    AppendMenu(hmenu, MF_SEPARATOR, 0, NULL); 
    uFormat = EnumClipboardFormats(0); 
 
    while (uFormat) 
    { 
        // Call an application-defined function to get the name 
        // of the clipboard format. 
 
        lpFormatName = GetPredefinedClipboardFormatName(uFormat); 
 
        // For registered formats, get the registered name. 
 
        if (lpFormatName == NULL) 
        {

        // Note that, if the format name is larger than the
        // buffer, it is truncated. 
            if (GetClipboardFormatName(uFormat, szFormatName, 
                    sizeof(szFormatName))) 
                lpFormatName = szFormatName; 
            else 
                lpFormatName = "(unknown)"; 
        } 
 
        // Add a menu item for the format. For displayable 
        // formats, use the format ID for the menu 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; 
}