Опубликован: 13.07.2012 | Доступ: свободный | Студентов: 460 / 8 | Оценка: 5.00 / 5.00 | Длительность: 18:06:00
Специальности: Программист
Лекция 20:

Буфер обмена и перетаскивание

< Лекция 19 || Лекция 20 || Лекция 21 >
Аннотация: В этой лекции вы познакомитесь с классами, предоставляющими доступ к буферу обмена целевой операционной системы, а также реализующими обмен данными посредством механизма "перетащил и бросил" (drag and drop).

Цель лекции: Научиться создавать приложения, поддерживающие обмен данными путём использования буфера обмена целевой операционной системы, а также механизма "перетащил и бросил" (drag and drop)

Одной из часто возникающих пользовательских задач является обмен данными между приложениями, либо частями одного приложения. Помимо загрузки данных из файлов, это реализуется использованием буфера обмена (clipboard), а также механизмом "перетащил и бросил" (drag and drop), который часто называют просто перетаскиванием.

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

Как дополнение к буферу обмена реализуется механизм перетаскивания, который заключается в перемещении мышью информации из одного объекта в другой. Например, можно перемещать между папками файлы, а также другие папки, сбрасывать файлы из файлового менеджера на соответствующие приложения, запуская процедуры обработки содержащейся информации.

Рассмотрим оба эти способа обмена данными на примере одной программы, простейшего текстового редактора. Он будет поддерживать основные операции работы с буфером обмена целевой операционной системы (вырезать, скопировать, вставить), а также открытие текстовых файлов для редактирования посредством механизма "drag and drop".

В основе механизма перетаскивания лежат две операции: захвата и собственно перетаскивания и сброса. Виджет может быть при этом как источником перемещения информации, так и её приёмником.

Для поддержки в приложении Juce приёма сбрасываемых данных, класс компонента, отвечающего за это, должен быть унаследован либо от класса DragAndDropTarget (в случае, если виджет служит приёмником информации от других частей приложения), либо от FileDragAndDropTarget (в случае, если происходит обмен данными между программами).

Создадим класс TTextEditor, унаследовав его от TextEditor и FileDragAndDropTarget ( пример 20.1).

#ifndef _TTextEditor_h_
#define _TTextEditor_h_
//--------------------------------------------------
#include "../JuceLibraryCode/JuceHeader.h"
//--------------------------------------------------
// Класс текстового редактора - приёмника информации при выполнении
// операции перетаскивания
class TTextEditor : public TextEditor,
          public FileDragAndDropTarget
{
public:
  TTextEditor(const String& sComponentName = L"TextEditor");
  ~TTextEditor();

  // Проверяет, необходимо ли обрабатывать сброшенные файлы
  bool isInterestedInFileDrag(const StringArray&);
  // Функция обработки файлов, сброшенных на компонент
  void filesDropped(const StringArray&, int, int);
};
//---------------------------------------------------
#endif
    
Листинг 20.1. Объявление класса TTextEditor (файл TTextEditor.h)

Класс FileDragAndDropTarget включает две чистые виртуальные функции:

  • virtual bool FileDragAndDropTarget::isInterestedInFileDrag(const StringArray& files) — вызывается для проверки, заинтересован ли компонент-приёмник в обработке предлагаемого набора перетаскиваемых файлов files. Принимаемый параметр — абсолютные пути к этому набору файлов. В том случае, если метод возвращает true, компонент должен выполнять какие-то действия, связанные с этим типом объектов;
  • virtual void FileDragAndDropTarget::filesDropped(const StringArray& files, int x, int y) — вызывается, когда пользователь сбросил файлы на компонент, после чего над этими файлами можно совершать какие-то действия. Первый параметр метода аналогичен таковому у предыдущей функции. Параметры x и y содержат координаты указателя мыши относительно компонента-приёмника.

Помимо вышеперечисленных, класс FileDragAndDropTarget включает ряд других виртуальных методов:

  • virtual void FileDragAndDropTarget::fileDragEnter(const StringArray& files, int x, int y) — вызывается, когда пользователь начинает перетаскивать над компонентом файлы;
  • virtual void FileDragAndDropTarget::fileDragExit(const StringArray& files) — вызывается, когда пользователь заканчивает перетаскивать над компонентом файлы (мышь находится за пределами компонента);
  • virtual void FileDragAndDropTarget::fileDragMove(const StringArray& files, int x, int y) — вызывается, когда пользователь перетаскивает над компонентом файлы (т.е. данный метод объединяет действие функций fileDragEnter и fileDragExit).

Однако для нашего примера достаточно написать реализацию методов isInterestedInFileDrag и filesDropped ( пример 20.2).

#include "TTextEditor.h"
//---------------------------------------------------
#define tr(s) String::fromUTF8(s)
//---------------------------------------------------
TTextEditor::TTextEditor(const String& sComponentName) 
            : TextEditor(sComponentName)
{
  Font NewFont(20.0);
  this->setFont(NewFont);
  this->setMultiLine(true);
  this->setReturnKeyStartsNewLine(true);
  this->setReadOnly(false);
  this->setScrollbarsShown(true);
  this->setCaretVisible(true);
  this->setPopupMenuEnabled(true);
  this->setText(String::empty);
}
//----------------------------------------------------
TTextEditor::~TTextEditor()
{
}
//----------------------------------------------------
bool TTextEditor::isInterestedInFileDrag(const StringArray&)
{
  // Обрабатываются любые файлы
  return true;
}
//----------------------------------------------------
void TTextEditor::filesDropped(const StringArray& sFiles, int iX, int iY)
{
  // Получаем брошенный на компонент файл
  // по пути к нему
  File DroppedFile(sFiles[0]);
  // Если это текстовый файл...
  if(DroppedFile.getFileExtension() == ".txt")
  {
    // выводим его содержимое на компонент
    FileInputStream Input(DroppedFile);
    this->setText(Input.readEntireStreamAsString());
    repaint();
  }
  // Иначе выводим предупреждение
  else AlertWindow::showMessageBox(
            AlertWindow::WarningIcon, 
            tr("Ошибка"), 
            tr("Неподдерживаемый тип файла!"), 
            tr("Принять"), 0
            );
}
//----------------------------------------------
Листинг 20.2. Реализация класса TTextEditor (файл TTextEditor.cpp)

Как видно из листинга, после того, как пользователь бросил на наш компонент TTextEditor файл, мы проверяем по его расширению, является ли он текстовым. Разумеется, можно было бы устроить эту проверку в методе isInterestedInFileDrag, но мне вариант обработки информации из файла в сочетании с проверкой его типа представился более удобным. Поэтому метод isInterestedInFileDrag просто возвращает true, т.е. принимаются все файлы. В том случае, если брошенный на компонент файл оказывается несоответствующего типа, об этом выводится предупреждение ( пример 20.2).

Помимо текстового редактора, компонент содержимого нашей программы будет включать меню, содержащее пункты "Файл" и "Правка". Соответственно, первый пункт меню будет содержать подпункты "Открыть..." и "Выход", а второй - "Вырезать", "Копировать", "Вставить" ( рис. 20.1). Описание работы с меню см. в "Элементы управления. Компоненты меню" .

Работа простейшего текстового редактора с поддержкой drag and drop

Рис. 20.1. Работа простейшего текстового редактора с поддержкой drag and drop

Объявление класса компонента содержимого представлено в листинге 20.3 .

#ifndef _TCentralComponent_h_
#define _TCentralComponent_h_
//--------------------------------------------------
#include "../JuceLibraryCode/JuceHeader.h"
//--------------------------------------------------
class TTextEditor;
//--------------------------------------------------
// Класс компонента содержимого.
// Наследует класс модели меню
class TCentralComponent  : public Component,
            public MenuBarModel
{
public:
  TCentralComponent();
  ~TCentralComponent();

  // Идентификаторы команд меню
  enum TMenuOptions
  {
    FileOpen = 1, // Открыть файл
    FileQuit = 2, // Завершить работу с программой
    EditCut = 3, // Вырезать текст
    EditCopy = 4, // Скопировать текст в буфер
    EditPaste = 5 // Вставить текст из буфера
  };

  void paint(Graphics&);
  void resized();
  // Функции, наследуемые от MenuBarModel
  const StringArray getMenuBarNames();
  void menuItemSelected(int, int);

private:
  // Наш текстовый редактор
  // с поддержкой drag and drop
  TTextEditor* pTextEditor;
  
  // Изображения - пиктограммы меню
  Image ImageOpen_png;
  Image ImageExit_png;
  Image ImageCut_png;
  Image ImageCopy_png;
  Image ImagePaste_png;

  // Предотвращает создание копии конструктора и оператора =
  TCentralComponent(const TCentralComponent&);
  const TCentralComponent& operator= (const TCentralComponent&);
};
//-----------------------------------------------
#endif
Листинг 20.3. Объявление класса компонента содержимого TCentralComponent (файл TCentralComponent.h)

Как мы знаем из "Ввод и отображение текстовой информации" , класс TextEditor включает собственные методы вырезания (cut), копирования (copy) и вставки (paste) информции, однако в Juce имеется специальный класс для работы с буфером обмена целевой операционной системы, SystemClipboard. Последний содержит два метода:

  • static void SystemClipboard::copyTextToClipboard(const String& text) — копирует текст text в буфер обмена;
  • static const String SystemClipboard::getTextFromClipboard() — вставляет текст из буфера.

Для того, чтобы вырезать текст в буфер, мы вначале копируем в буфер обмена выделенный пользователем текст с помощью метода const String TextEditor::getHighlightedText() const, а затем вставляем под курсором пустую строку ( пример 20.4).

void TCentralComponent::menuItemSelected(int iMenuItemID, int iTopLevelMenuIndex)
{
  // Обработчики команд меню
  switch(iMenuItemID)
  {
    // Если выбран пункт "Выход"...
    case FileQuit:
    {
      // Завершаем работу программы
      JUCEApplication::quit();
      break;
    }
    // Если выбран пункт "Открыть"...
    case FileOpen:
    {
      // Задаём в качестве стартовой директории для поиска файла 
      // папку "Документы" текущего пользователя
       File InitialFile;
       InitialFile =
         InitialFile.getSpecialLocation(File::userDocumentsDirectory);

      // В качестве фильтра поиска файлов задаём текстовый файл
      WildcardFileFilter TextFileFilter(
              "*.txt", 
              String::empty, 
              tr("Текстовые файлы")
              );

      // Компонент для отображения списка файлов
      FileBrowserComponent FileBrowser(
              FileBrowserComponent::openMode | 
              FileBrowserComponent::canSelectFiles, 
              InitialFile, & TextFileFilter, NULL);

            // Диалог для отображения FileBrowser
            FileChooserDialogBox OpenFileDialog(
                    tr("Открыть файл"),
                    tr("Выберите текстовый файл, который вы хотите открыть..."), 
         FileBrowser, false, Colours::azure
         );
      OpenFileDialog.setSize(600, 400);

      // Если пользователь выбрал файл...
      if(OpenFileDialog.show())
      {
        // открываем его
        InitialFile = FileBrowser.getSelectedFile(0);
        FileInputStream Input(InitialFile);
        pTextEditor->setText(Input.readEntireStreamAsString());
      }
    }
    // Если выбран пункт "Вырезать"...
    case EditCut:
    {
      // Работает аналогично pTextEditor->cut();
      SystemClipboard::copyTextToClipboard(
            pTextEditor->getHighlightedText());
      pTextEditor->insertTextAtCaret(String::empty);
      break;
    }
    // Если выбран пункт "Копировать"...
    case EditCopy:
    {
      // Работает аналогично  pTextEditor->copy();
      SystemClipboard::copyTextToClipboard(
            pTextEditor->getHighlightedText());
      break;
    }
    // Если выбран пункт "Вставить"...
    case EditPaste:
    {
      // Работает аналогично pTextEditor->paste();
      pTextEditor->insertTextAtCaret(
            SystemClipboard::getTextFromClipboard());
      break;
    }
  }
Листинг 20.4. Реализация метода menuItemSelected класса компонента содержимого TCentralComponent (файл TCentralComponent.cpp)

Реализацию механизма drag and drop, связанную с обменом данными между двумя частями одной и той же программы показывает демонстрационное приложение Juce (выберите в меню Demo > Drag-and-drop). При перетаскивании элементов списка из левого верхнего угла программы на расположенный в нижнем правом углу виджет, на последнем появляется информация о номере брошенного элемента ( рис. 20.2).

Работа демонстрационного приложения Juce (демонстрируется технология drag and drop между частями одного приложения)

Рис. 20.2. Работа демонстрационного приложения Juce (демонстрируется технология drag and drop между частями одного приложения)

Посмотреть реализацию примера можно, перейдя в папку <каталог Juce>/juce/extras/JuceDemo/Source/demos и открыв файл DragAndDropDemo.cpp.

Виджет в правом нижнем углу программы, как понятно, наследует классу DragAndDropTarget. Его функции-члены аналогичны описанным выше для FileDragAndDropTarget: носят те же имена и выполняют те же действия, за исключением метода virtual void DragAndDropTarget::itemDropped(const String& sourceDescription, Component* sourceComponent, int x, int y). Подобно методу filesDropped класса FileDragAndDropTarget, он вызывается тогда, когда пользователь бросил что-либо на компонент-приёмник. В качестве источника перетаскиваемых объектов для DragAndDropTarget выступает виджет класса, унаследованного от DragAndDropContainer. Последний обеспечивает возможность перетаскивания для своих дочерних компонентов, а также блоков текста.

В демонстрационном приложении Juce субкомпонентами виджета, унаследованного от DragAndDropContainer, являются как список — источник перетаскиваемых объектов, так и виджет-приёмник, класс которого наследует DragAndDropTarget.

Для того чтобы начать перетаскивание, любой субкомпонент может просто вызвать метод void DragAndDropContainer::startDragging(const var& sourceDescription, Component* sourceComponent, const Image& dragImage = Image::null, bool allowDraggingToOtherJuceWindows = false, const Point<int>* imageOffsetFromMouse = nullptr) для того, чтобы вступить во взаимодействие с объектом класса, унаследованного от DragAndDropTarget.

Метод принимает следующие параметры:

  • sourceDescription — строка или какая-либо переменная, используемая для описания перетаскиваемого объекта. Она передаётся компоненту-приёмнику;
  • sourceComponent — перетаскиваемый компонент;
  • dragImage — изображение, появляющееся под указателем мыши при перетаскивании. Если параметр принимает нулевое значение, вместо этого будет отображаться миниатюра перетаскиваемого объекта;
  • allowDraggingToOtherJuceWindows — в случае, если параметр принимает значение true, перетаскиваемый компонент отображается как отдельное окно окружения рабочего стола, которое может быть перетащено на объект класса, унаследованного от DragAndDropTargets, как родительского компонента, так и другого приложения;
  • imageOffsetFromMouse — если параметр dragImage не является нулевым изображением, то значение этого параметра задаёт смещение пиктограммы от указателя мыши. Если параметр не задан, то указатель мыши будет располагаться в центре изображения. Если dragImage — нулевое изображение, то этот параметр будет проигнорирован.
  • При разработке программ, осуществляющих взаимодействие с другими приложениями, могут оказаться полезны два метода класса DragAndDropContainer:
  • static bool DragAndDropContainer::performExternalDragDropOfFiles(const StringArray& files, bool canMoveFiles) — позволяет перетащить и сбросить набор файлов на другое приложение;
  • static bool DragAndDropContainer::performExternalDragDropOfText(const String& text) — позволяет перетащить и сбросить на другое приложение блок текста text.

Краткие итоги

В этой лекции вы познакомились с классами Juce, предоставляющими доступ к буферу обмена целевой операционной системы (SystemClipboard), а также реализующими обмен данными посредством механизма "перетащил и бросил" (DragAndDropTarget и FileDragAndDropTarget). Источником объектов, принимаемых DragAndDropTarget служит DragAndDropContainer.

Упражнение

Напишите программу, позволяющую перетаскивать блок текста из одной её части в другую.

Дополнительные материалы

Архив с исходными текстами примера Вы можете скачать здесь

< Лекция 19 || Лекция 20 || Лекция 21 >