Использование буфера обмена
В этом разделе приведены примеры кода для следующих задач:
- Реализация команд выреза, копирования и вставки
- Выбор данных
- Создание меню "Изменить"
WM_INITMENUPOPUP
Обработка сообщенияWM_COMMAND
Обработка сообщения- Копирование сведений в буфер обмена
- Вставка сведений из буфера обмена
- Регистрация формата буфера обмена
WM_RENDERFORMAT
Обработка иWM_RENDERALLFORMATS
обработка сообщенийWM_DESTROYCLIPBOARD
Обработка сообщения- Использование формата буфера обмена владельца
- Мониторинг содержимого буфера обмена
- Запрос номера последовательности буфера обмена
- Создание прослушивателя формата буфера обмена
- Создание окна просмотра буфера обмена
- Добавление окна в цепочку просмотра буфера обмена
Реализация команд выреза, копирования и вставки
В этом разделе описывается, как в приложении реализованы стандартные команды "Вырезать", "Копировать" и "Вставка ". В этом разделе используются эти методы для размещения данных в буфере обмена с использованием зарегистрированного формата буфера обмена, 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, определяемая приложением, копирует текущий выбор в буфер обмена. Эта функция выполняет следующие действия:
- Открывает буфер обмена, вызвав функцию
OpenClipboard
. - Очищает буфер обмена путем вызова
EmptyClipboard
функции. - Вызывает функцию
SetClipboardData
один раз для каждого формата буфера обмена, который предоставляет приложение. - Закрывает буфер обмена, вызвав функцию
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
приложением, вставляет содержимое буфера обмена. Эта функция выполняет следующие действия:
- Открывает буфер обмена, вызвав функцию
OpenClipboard
. - Определяет, какие из доступных форматов буфера обмена для извлечения.
- Извлекает дескриптор данных в выбранном формате, вызвав функцию
GetClipboardData
. - Вставляет копию данных в документ. Дескриптор, возвращаемый
GetClipboardData
по-прежнему, принадлежит буферу обмена, поэтому приложение не должно освободить его или оставить его заблокированным. - Закрывает буфер обмена, вызвав функцию
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;
Использование формата буфера обмена владельца
Если окно помещает сведения в буфер обмена с помощью формата буфера 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_DRAWCLIPBOARD
Обработка сообщения- Пример средства просмотра буфера обмена
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
:
- Определите, какие из доступных форматов буфера обмена для отображения.
- Извлеките данные буфера обмена и отобразите его в окне. Или если формат буфера обмена задан
CF_OWNERDISPLAY
, отправьтеWM_PAINTCLIPBOARD
сообщение владельцу буфера обмена. - Отправьте сообщение в следующее окно в цепочке просмотра буфера обмена.
Пример обработки 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;
}