Шаг 62.
Библиотека OWL.
Применение буфера обмена для работы с текстом

    На этом шаге мы рассмотрим работу буфера обмена с текстом.

    Простейшими данными буфера обмена являются CF_TEXT, которые представляют простейший ANSI-текст. Единственными специальными символами, распознаваемыми этим форматом, являются возврат каретки, переход на новую строку и "нули" (NULLS). Символы "возврат каретки" и "переход на новую строку" означают окончания строк, тогда как NULL отмечает окончание текста. Данные такого типа обычно передаются в буфер обмена, когда пользователь вырезает или копирует текст из текстового редактора определенного типа. Например, редактор Borland IDE передает текст именно в таком формате.

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

    В следующем примере приведен текст простой программы-редактора, которая обеспечивает поддержку Clipboard. После запуска программы появляется окно, представленное на рисунке 1.


Рис.1. Результат работы приложения

    В окне вы можете набрать текст, можете выполнять с ним операции копирования и вставки. Чтобы скопировать текст в буфер обмена Clipboard, выделите текст, перемещая по нему указатель мыши, затем выберите пункт Сору в меню Edit. Чтобы вставить текст обратно в Windows, воспользуйтесь пунктом Paste в меню Edit.

#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\editfile.h> 
#include "pr62_1.rc"
 
// Класс приложения.
class TApp: public TApplication
{
  public:
	 TApp():TApplication() {}
	 void InitMainWindow();
};

// Класс окна-клиента.
class TCWndw : public TEditFile
{
  public:
	  TCWndw ();
  protected:
          BOOL textInClip;

          void EvSetFocus (HWND); 
          void CmEditCopy (); 
          void CmEditPaste();

          void CmEnableEditPaste (TCommandEnabler &commandEnabler);

	  DECLARE_RESPONSE_TABLE (TCWndw);
};

DEFINE_RESPONSE_TABLE1 (TCWndw, TEditFile)
         EV_WM_SETFOCUS,
         EV_COMMAND (CM_EDITCOPY,  CmEditCopy), 
         EV_COMMAND (CM_EDITPASTE, CmEditPaste), 
         EV_COMMAND_ENABLE (CM_EDITPASTE, CmEnableEditPaste),
END_RESPONSE_TABLE;

//***********************************
// Реализация окна TCWndw.
//***********************************
// TCWndw::TCWndw()
// Это конструктор окна-клиента.
TCWndw::TCWndw (): TEditFile ()
{
  // Сбросить некоторые флаги стиля.
  Attr.Style &= ~ (WS_BORDER | WS_VSCROLL | WS_HSCROLL);
}

// TCWndw::EvSetFocus()
// Эта функция вызывается, когда окно-клиент 
// этой программы получает фокус.
void TCWndw::EvSetFocus (HWND)
{
  // Проверить текст в Clipboard.
  if (IsClipboardFormatAvailable (CF_TEXT)) textInClip = TRUE; 
  else textInClip = FALSE;
  // Выполнить нормальную обработку WM_SETFOCUS. 
  DefaultProcessing();
}

// TCWndw::CmEditCopy()
// Эта функция вызывается, когда пользователь 
// выбирает команду Сору в меню Edit.
void TCWndw::CmEditCopy() 
{
  UINT startPos, endPos;
  // Получить начальную и конечную позиции 
  // выделенного текста. 
  GetSelection (startPos, endPos);
  // Получить дескриптор блока памяти, достаточно
  // большого для хранения выбранного текста.
  HANDLE hMem = GlobalAlloc (GHND, endPos-startPos+1);
  // Получить указатель на блок и фиксировать этот блок в памяти.
  LPSTR s = (LPSTR) GlobalLock (hMem) ;
  // Скопировать выделенный текст и разблокировать память. 
  GetSubText (s, startPos, endPos); 
  GlobalUnlock (hMem);
  // Открыть и очистить буфер обмена Clipboard. 
  TClipboard &clipboard = OpenClipboard(); 
  clipboard.EmptyClipboard();
  // Передать в буфер выделенный текст. 
  clipboard.SetClipboardData (CF_TEXT, hMem);
  // Закрыть буфер обмена. 
  clipboard.CloseClipboard();
  // Включить пункт меню Paste. 
  textInClip = TRUE;
}

// TCWndw::CmEditPaste()
// Эта функция вызывается, когда пользователь
// выбирает команду Paste из меню Edit.
void TCWndw::CmEditPaste() 
{
  // Открыть буфер обмена.
  TClipboard &clipboard = OpenClipboard();
  // Проверить наличие текста в буфере обмена.
  if (clipboard.IsClipboardFormatAvailable (CF_TEXT))
  {
    // Получить дескриптор текста в буфере обмена.
    HANDLE hMem = clipboard.GetClipboardData (CF_TEXT);
    // Проверить правильность дескриптора.
    if (hMem)
    {
      // Получить указатель на текст.
      LPSTR  s = (LPSTR) GlobalLock (hMem);
      // Ввести  текст  в  окно  редактирования. 
      Insert (s);
      // Разблокировать блок памяти, содержащий текст. 
      GlobalUnlock (hMem);
    }
  }
  // Закрыть буфер обмена Clipboard. 
  clipboard.CloseClipboard();
}

// TCWndw::CmEnableEditPaste()
// Эта функция является переключателем доступности команды
// Edit/Paste и определяет, доступен или нет этот пункт меню.
void TCWndw::CmEnableEditPaste (TCommandEnabler &commandEnabler)
{
  commandEnabler.Enable (textInClip);
}

//***********************************
// Реализация класса TApp.
//***********************************
// TApp::InitMainWindow()
// Эта функция создает главное окно приложения.
void TApp::InitMainWindow()
{
  // Создать окно-клиент.
  TEditFile *client = new TCWndw;
  // Создать окно обрамления.
  TFrameWindow *wndw = new TFrameWindow 
     (0, "Буфер обмена", client);
  // Добавить меню к главному окну. 
  wndw->AssignMenu (MENU_1);
  // Определить расположение и размеры окна.
  wndw->Attr.X = 40;
  wndw->Attr.Y = 40;
  wndw->Attr.W = GetSystemMetrics (SM_CXSCREEN) / 1.5;
  wndw->Attr.H = GetSystemMetrics (SM_CYSCREEN) / 1.5;
  // Установить указатель окна. 
  SetMainWindow(wndw);
} 

int OwlMain(int,char *[])
{
  return TApp().Run();
}

    Файл ресурсов:

#ifndef WORKSHOP_INVOKED
#include "windows.h"
#endif

#include <owl\editfile.rh>

#define MENU_1         100 
#define CM_EXIT        24310
#define CM_EDITCOPY    24323 
#define CM_EDITPASTE   24324

#ifdef RC_INVOKED

MENU_1 MENU
{
  POPUP "&File"
  {
	  MENUITEM "E&xit", CM_EXIT
  }
  POPUP "&Edit" 
  {
          MENUITEM "&Copy", CM_EDITCOPY
          MENUITEM "&Paste", CM_EDITPASTE 
  }
}

STRINGTABLE LOADONCALL MOVEABLE DISCARDABLE 
{
  IDS_UNTITLEDFILE, "(Без имени)"
  IDS_FILECHANGED,  "Вы хотите сохранить изменения?"
}

#endif
Текст этого приложения можно взять здесь.

    Меню Edit программы является достаточно "умным", чтобы знать, когда в буфере обмена Clipboard содержатся данные правильного типа, - то есть, если данные в Clipboard имеют формат, отличный от CF_TEXT, опция Paste блокируется. Чтобы проверить это, запустите Windows Paint и скопируйте какое-нибудь изображение в буфер обмена Clipboard (чтобы скопировать часть окна Paint, используйте "ножницы" из панели инструментов). Затем вернитесь назад в текстовый редактор и взгляните на меню Edit. Опция Paste должна быть блокирована. Теперь переключитесь на другой текстовый редактор и скопируйте часть текста в буфер обмена Clipboard. Вернитесь в нашу программу и вы увидите, что опция Paste снова доступна.

    Теперь рассмотрим объявление окна-клиента в начале текста программы:

class TCWndw : public TEditFile
{
  public:
	  TCWndw ();
  protected:
          BOOL textInClip;

          void EvSetFocus (HWND); 
          void CmEditCopy (); 
          void CmEditPaste();

          void CmEnableEditPaste (TCommandEnabler &commandEnabler);

	  DECLARE_RESPONSE_TABLE (TCWndw);
};

DEFINE_RESPONSE_TABLE1 (TCWndw, TEditFile)
         EV_WM_SETFOCUS,
         EV_COMMAND (CM_EDITCOPY,  CmEditCopy), 
         EV_COMMAND (CM_EDITPASTE, CmEditPaste), 
         EV_COMMAND_ENABLE (CM_EDITPASTE, CmEnableEditPaste),
END_RESPONSE_TABLE;

    Как видно, окно-клиент является производным от OWL-класса TEditFile. Используя окно этого класса, вы можете набрать текст, выделить текст с помощью мыши, производить операции копирования и вставки текста и многое другое, вводя коды этих функций в программу. Оконный класс TCWndw содержит функции отклика на сообщения для пунктов меню Сору и Paste, так же как и для сообщения Windows WM_SETFOCUS. Этот класс также содержит переключатель доступности команды Paste меню Edit. Этот переключатель доступности включает или блокирует команду Paste, основываясь на значении булевой переменной textInClip.


    Замечание. При программировании с помощью окна TEditFile OWL автоматически обрабатывает команды меню Edit до тех пор, пока используются правильные идентификаторы команд. Из-за этого значительная часть текста в приведенном примере является излишней, так как описывает функции, которые уже управляются OWL. Однако этот излишний текст показывает, как работает буфер обмена Clipboard, который, разумеется, является ключевым моментом программы.

    Окно-клиент, как и любой объект, создается собственным конструктором:

TCWndw::TCWndw (): TEditFile ()
{
  // Сбросить некоторые флаги стиля.
  Attr.Style &= ~ (WS_BORDER | WS_VSCROLL | WS_HSCROLL);
}

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

    В рассматриваемом текстовом редакторе включение команды Paste происходит, когда буфер обмена Clipboard содержит данные формата CF_TEXT, а блокирование этой команды происходит, когда в буфере Clipboard содержатся данные другого типа. Оказывается, вы не можете "обмануть" меню даже переключением на другую программу и сменой содержания буфера обмена Clipboard. Windows, при возвращении в редактор, заставляет команду Paste изменяться, чтобы отразить новое содержимое буфера обмена. Это возможно благодаря сообщению WM_SETFOCUS.

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

    Окно-клиент обрабатывает сообщение WM_SETFOCUS функцией EvSetFocus():

void TCWndw::EvSetFocus (HWND)
{
  // Проверить текст в Clipboard.
  if (IsClipboardFormatAvailable (CF_TEXT)) textInClip = TRUE; 
  else textInClip = FALSE;
  // Выполнить нормальную обработку WM_SETFOCUS. 
  DefaultProcessing();
}

    В этой функции программа сначала вызывает функцию Windows API IsClipboardAvailable() с параметром CF_TEXT. Эта функция возвращает TRUE, если в буфере обмена имеется запрашиваемый тип данных, и FALSE - в противном случае. (Для проверки нахождения других типов данных вам необходимо только заменить CF_TEXT на другой формат.) В этом случае, если функция IsClipboardFormatAvailable() возвращает TRUE, в буфере обмена Clipboard содержится текст, поэтому программа устанавливает флаг textInClip в TRUE. Благодаря переключателю доступности команд программы, изменение textInClip на TRUE включает команду Paste. Если в Clipboard содержится не текст, то программа устанавливает флаг textInClip в FALSE.

    Последнее, что проделывает функция EvSetFocus() - это вызов DefaultProcessing() для возврата сообщения WM_SETFOCUS в Windows, поэтому это сообщение может быть обработано обычным способом.

    Если программа получает сообщение CM_EDITCOPY (которое определено в файле ресурсов), оно означает, что пользователь выбрал команду Сору из меню Edit и хочет скопировать данные из основного окна в буфер обмена. Это сообщение обрабатывается функцией отклика на сообщение CmEditCopy():

void TCWndw::CmEditCopy() 
{
  UINT startPos, endPos;
  // Получить начальную и конечную позиции 
  // выделенного текста. 
  GetSelection (startPos, endPos);
  // Получить дескриптор блока памяти, достаточно
  // большого для хранения выбранного текста.
  HANDLE hMem = GlobalAlloc (GHND, endPos-startPos+1);
  // Получить указатель на блок и фиксировать этот блок в памяти.
  LPSTR s = (LPSTR) GlobalLock (hMem) ;
  // Скопировать выделенный текст и разблокировать память. 
  GetSubText (s, startPos, endPos); 
  GlobalUnlock (hMem);
  // Открыть и очистить буфер обмена Clipboard. 
  TClipboard &clipboard = OpenClipboard(); 
  clipboard.EmptyClipboard();
  // Передать в буфер выделенный текст. 
  clipboard.SetClipboardData (CF_TEXT, hMem);
  // Закрыть буфер обмена. 
  clipboard.CloseClipboard();
  // Включить пункт меню Paste. 
  textInClip = TRUE;
}

    Обычно объект класса, производного от TEditFile, управляет операциями вырезания, копирования и вставки, автоматически передавая данные при необходимости в буфер обмена. Однако, чтобы продемонстрировать, как работает Clipboard, программа забирает некоторые из этих функций. Сначала она получает начальную и конечную позиции выделенного текста, вызывая функцию окна-клиента GetSelection(), которая наследуется из класса TEdit. Текст, определяемый startPos и endPos является текстом, который должен быть скопирован в Clipboard.

    Затем программа вызывает функцию Windows API GlobalAlloc(), запрашивая память, достаточную для хранения текста. (Параметр этой функции GHMD определяет, что запрашивается перемещаемый блок памяти, который инициализирован нулевым значением. Вторым параметром этой функции является размер блока.) После размещения блока программа получает дескриптор блока памяти, а не его адрес. Дескриптор, установленный в NULL, означает, что выделение памяти невозможно. Хотя в программе не предусмотрена проверка дескриптора на нулевое значение, следует предусмотреть это в ваших собственных программах.


    Замечание. Для максимально эффективного использования ОЗУ Windows проделывает значительную его "перетасовку". Это означает, что в программе никогда не следует предполагать, что адрес объекта не меняется. Если вы должны быть уверены, что объект остается в заданном месте памяти, вы должны вызвать функцию Windows API GlobalLock(), чтобы блокировать объект в этом месте. Для разблокирования блока памяти, вызовите функцию Windows API GlobalUnlock().

    Перед копированием выделенного текста в предназначенный для этого блок памяти программа должна известить Windows о том, чтобы блок не перемещался до тех пор, пока не скопируется информация. Программа делает это, вызывая функцию Windows API GlobalLock() с помощью дескриптора блока. Эта функция "замораживает" объект в памяти и возвращает указатель на него.

    После этого вызова программа может благополучно вызывать функцию окна-клиента GetSubText() (унаследованную от TEdit) для копирования выделенного текста в блок памяти. Затем вызов функции GlobalUnlock() информирует Windows, что программа закончила взаимодействие с блоком. После разблокирования блока указатель s уже не является допустимым. Теперь программа должна обращаться к блоку памяти только посредством его дескриптора.

    Теперь, когда выделенный текст скопирован в память, программа может передать текст в буфер обмена. Сначала программа создает OWL-объект clipboard и открывает буфер обмена Windows Clipboard путем вызова унаследованной от TWindow функции окна-клиента OpenClipboard() . (He перепутайте эту функцию с одноименной функцией класса TClipboard.) Открытие буфера обмена Clipboard предотвращает доступ в него других приложений, когда его использует программа. Когда программа получила буфер обмена Clipboard, то ее первой задачей является его очистка при помощи вызова функции EmptyClipboard() объекта clipboard.

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


    Замечание. Термин "хозяин" на самом деле не означает, что программа "владеет" буфером Clipboard, а означает только то, что приложение-"хозяин" является источником данных, которые присутствуют в буфере обмена Clipboard в настоящий момент.

    После очистки буфера обмена, программа вызывает функцию SetClipboardData() объекта clipboard, который передает текст в буфер обмена. Первым параметром этой функции является тип данных, размещаемых в буфере. Вторым параметром является разблокированный дескриптор данных.

    Обычно после копирования текста в буфер обмена программа должна вызвать функцию Windows API CloseClipboard(), чтобы освободить буфер обмена Clipboard для доступа в него других приложений. OWL-класс TClipboard имеет свою собственную версию этой функции, которую вы вызываете для освобождения буфера. Программа вызывает функцию CloseClipboard() объекта clipboard, которую этот объект унаследовал от TClipboard.


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

    Последним, что проделывает функция CmEditCopy(), является установка флага textInChip в TRUE. При этом включается команда Paste из меню Edit, и, таким образом, пользователь может вставить текст, который был только что скопирован. В этом случае программе нет необходимости проверять формат данных в буфере обмена Clipboard, так как она сама их там разместила.

    Копирование данных в буфер обмена Clipboard - это только полдела. Приложение, которое поддерживает буфер обмена, также должно получить данные. В программе - текстовом редакторе сигнал для выполнения этой операции передается, когда пользователь выбирает команду Paste в меню Edit. В программе при этом вырабатывается сообщение CM_EDITPASTE (определенное в файле ресурсов), которым управляет функция CmEditPaste():

void TCWndw::CmEditPaste() 
{
  // Открыть буфер обмена.
  TClipboard &clipboard = OpenClipboard();
  // Проверить наличие текста в буфере обмена.
  if (clipboard.IsClipboardFormatAvailable (CF_TEXT))
  {
    // Получить дескриптор текста в буфере обмена.
    HANDLE hMem = clipboard.GetClipboardData (CF_TEXT);
    // Проверить правильность дескриптора.
    if (hMem)
    {
      // Получить указатель на текст.
      LPSTR  s = (LPSTR) GlobalLock (hMem);
      // Ввести  текст  в  окно  редактирования. 
      Insert (s);
      // Разблокировать блок памяти, содержащий текст. 
      GlobalUnlock (hMem);
    }
  }
  // Закрыть буфер обмена Clipboard. 
  clipboard.CloseClipboard();
}

    Здесь программа сначала строит OWL-объект clipboard, который также открывает буфер обмена Windows Clipboard. Перед копированием содержимого буфера в его окно программа, однако, должна убедиться, что копируемые данные имеют правильный формат. Это, как и прежде, проделывается с помощью вызова функции IsClipboardFormatAvailable(). Однако на этот раз вызванная функция является версией, унаследованной из OWL-класса TClipboard, а не версией из Windows API. Если же функция возвращает FALSE, то CmEditPaste() бездействует. Конечно, в программе этот вызов никогда не получит отказ, так как каждый раз, когда окно получает фокус, эта программа проверяет формат данных в буфере обмена Clipboard. Таким образом, когда в буфере Clipboard содержатся данные формата, отличного от CF_TEXT, команда Paste недоступна.

    После проверки правильности формата данных программа получает дескриптор данных буфера обмена Clipboard путем вызова функции GetClipboardData() объекта clipboard. Параметром этой функции является формат данных, необходимый программе. Если в буфере не содержатся данные этого типа, функция возвращает дескриптор со значением NULL.

    После получения программой дескриптора содержимого буфера обмена Clipboard, она должна определить адрес данных. Проделывается это с помощью вылова функции GlobalLock(), которая не только возвращает указатель данных, но также блокирует данные в памяти и Windows не может их переместить. Затем программа отображает текст из буфера обмена, вызывая функцию окна пользователя Insert() (наследуемую TEdit). Наконец, функция разблокировывает память и закрывает буфер обмена Clipboard.

    На следующем шаге мы рассмотрим работу буфера обмена с графической информацией.




Предыдущий шаг Содержание Следующий шаг