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