Работа с буфером обмена
ОГЛАВЛЕНИЕ
Реализация команд Cut, Copy, и Paste
В этом разделе приводится пример, реализующий эти команды используя два формата буфера обмена: CF_OWNERDISPLAY и CF_TEXT. Обычно, перед тем, как информация будет скопирована в буфер обмена (clipboard), пользователь должен выделить определённый кусок текста. Для этого в приложении должны быть реализованы все возможности. После того, как текст будет выделен, необходимо реализовать всплывающее меню по правой кнопке мыши, а так же акселераторы к пунктам этого меню.
Чтобы сделать всплывающее меню, приложение должно обработать сообщение WM_INITMENUPOPUP:
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 в главную оконную процедуру:
Пример:
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 (См. Вставка данных из буфера обмена).
Копирование информации в буфер обмена
Процесс копирования информации в буфер обмена осуществляется следующим образом:
- Открываем буфер обмена функцией OpenClipboard.
- Очищаем буфер обмена функцией EmptyClipboard.
- Вызываем функцию SetClipboardData для каждого формата буфера обмена, которые поддерживает приложение.
- Закрываем буфер обмена функцией CloseClipboard.
Для того чтобы скопировать выделенный текст, используется следующая структура:
#define BOX_ELLIPSE 0
#define BOX_RECT 1
#define CCH_MAXLABEL 80
#define CX_MARGIN 12
typedef struct tagLABELBOX {
RECT rcText; // координаты прямоугольника с текстом
BOOL fSelected; // TRUE если выделен label
BOOL fEdit; // TRUE если текст выделен
int nType; // прямоугольное или овальное
int ichCaret; // позиция каретки
int ichSel; // with ichCaret, delimits selection
int nXCaret; // window position corresponding to ichCaret
int nXSel; // window position corresponding to 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();
// Получаем указатель на структуру LABELBOX.
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);
}
// Если текст не выделен, то копируем весь label.
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;
// Помещаем в буфер обмена данные трёх форматов.
SetClipboardData(uLabelFormat, NULL);
SetClipboardData(CF_OWNERDISPLAY, NULL);
SetClipboardData(CF_TEXT, NULL);
}
// Закрываем буфер обмена.
CloseClipboard();
return TRUE;
}
Вставка данных из буфера обмена
Процесс вставки информации из буфера обмена осуществляется следующим образом:
- Открываем буфер обмена функцией OpenClipboard.
- Определяем форматы данных, хранящихся в буфере обмена.
- Получаем хэндл данных нужного формата при помощи функции GetClipboardData.
- Вставляем копию данных в документ.
Владельцем хэндла возвращённого GetClipboardData остаётся всё ещё буфер обмена, поэтому приложение не должно его освобождать.
- Закрываем буфер обмена функцией CloseClipboard.
Определение структуры LABELBOX:
#define BOX_ELLIPSE 0
#define BOX_RECT 1
#define CCH_MAXLABEL 80
#define CX_MARGIN 12
typedef struct tagLABELBOX {
RECT rcText; // координаты прямоугольника с текстом
BOOL fSelected; // TRUE если будет копироваться весь label
BOOL fEdit; // TRUE если текст выделен
int nType; // прямоугольное или овальное
int ichCaret; // позиция каретки
int ichSel; // with ichCaret, delimits selection
int nXCaret; // window position corresponding to ichCaret
int nXSel; // window position corresponding to ichSel
int cchLabel; // длина текста в 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 (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;
}
// Если приложение не находится в режиме редактирования,
// то создаём окно label-а.
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 хэндл равный NULL, при этом приложение сгенерирует сообщение WM_RENDERFORMAT либо WM_RENDERALLFORMATS и Вы должны позаботиться, чтобы обработать эти сообщения. В данном случае перед вызовом функции SetClipboardData нельзя открывать буфер обмена.
Сообщения WM_RENDERFORMAT и WM_RENDERALLFORMATS обрабатываются следующим образом:
case WM_RENDERFORMAT:
RenderFormat((UINT) wParam);
break;
case WM_RENDERALLFORMATS:
RenderFormat(uLabelFormat);
RenderFormat(CF_TEXT);
break;
Код функции RenderFormat представлен ниже.
Как обычно, определение структуры LABELBOX:
#define BOX_ELLIPSE 0
#define BOX_RECT 1
#define CCH_MAXLABEL 80
#define CX_MARGIN 12
typedef struct tagLABELBOX {
RECT rcText; // координаты прямоугольника с текстом
BOOL fSelected; // TRUE если будет копироваться весь label
BOOL fEdit; // TRUE если текст выделен
int nType; // прямоугольное или овальное
int ichCaret; // позиция каретки
int ichSel; // with ichCaret, delimits selection
int nXCaret; // window position corresponding to ichCaret
int nXSel; // window position corresponding to ichSel
int cchLabel; // длина текста в 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)
{
// Выделяем буфер для текста.
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;
Использование формата буфера обмена CF_OWNERDISPLAY
Если Вы помещаете данные в буфер обмена, используя формат CF_OWNERDISPLAY, то необходимо проделать следующее:
- Обработать сообщение WM_PAINTCLIPBOARD. Это сообщение посылается владельцу буфера обмена, когда его "окно" должно быть перерисовано.
- Обработать сообщение WM_SIZECLIPBOARD. Это сообщение посылается владельцу буфера обмена, когда размеры его "окна" изменились либо изменилось его содержимое.
Обычно при этом, устанавливается положение скрола и диапазон скроллирования окна буфера обмена.
- Обработать сообщения WM_HSCROLLCLIPBOARD и WM_VSCROLLCLIPBOARD. Эти сообщения посылаются владельцу буфера обмена, когда "окно" буфера обмена было проскроллировано.
- Обработать сообщение WM_ASKCBFORMATNAME. Это сообщение посылает буфер обмена приложению, чтобы узнать формат.
Ниже представлена оконная процедура, с обработкой этих сообщений.
Пример:
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.
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);
// Устанавливаем диапазон скроллирования в ноль.
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;
}