The OpenNET Project / Index page

[ новости /+++ | форум | wiki | теги | ]

Каталог документации / Раздел "Программирование, языки" / Оглавление документа

9.2. Поддержка нестандартных типов данных при перетаскивании.

До сих пор мы имели дело с предопределенными типами перетаскиваемых объектов. Например, мы использовали QUriDrag, для перетаскивания файлов, и QTextDrag -- для текста. Оба этих класса являются наследниками QDragObject, который служит базой для всех перемещаемых объектов. В свою очредь, класс QDragObject наследует свойства абстрактного класса QMimeSource, предназначенного для хранения данных различных типов.

Если вы пожелаете перемещать объекты с текстовой информацией, с изображениями, с именами файлов или с информацией о цвете, то можно использовать предопределенные классы Qt: QTextDrag, QImageDrag, QUriDrag и QColorDrag. Но если вам необходимо перемещать нестандартные типы данных, то у вас есть два пути:

Класс QStoredDrag может хранить любые двоичные данные, что позволяет использовать его для любых типов MIME. Например, если вам потребуется перетащить некоторые данные, хранящиеся в файле формата (фиктивного) ASDF, то можно рекомендовать примерно такой код:
void MyWidget::startDrag() 
{ 
  QByteArray data = toAsdf(); 
  if (!data.isEmpty()) { 
    QStoredDrag *drag = new QStoredDrag("octet-stream/x-asdf", this); 
    drag->setEncodedData(data); 
    drag->setPixmap(QPixmap::fromMimeSource("asdf.png")); 
    drag->drag(); 
  } 
}
      
Однако, QStoredDrag имеет ряд неудобств. Одно из них заключается в том, что он может хранить только один MIME тип. Если мы предполагаем использовать механизм "drag and drop" только в пределах одного приложения, то это не является большой проблемой. Но когда необходимо реализовать взаимодействие между различными приложениями, то одного MIME типа, как правило бывает недостаточно.

Другое неудобство состоит в необходимости преобразования данных в QByteArray, даже если приемник не может принимать данные этого типа. При достаточно большом объеме данных, это может привести к неоправданной потере производительности. Было бы намного удобнее, если бы преобразование выполнялось в момент сброса перетаскиваемого объекта.

Решение этих двух проблем заключается в создании дочернего класса от QDragObject и реализации двух виртуальных методов format() и encodedData(), используемых Qt для получения сведений о перетаскиваемых объектах. Чтобы показать -- как это можно сделать, мы создадим класс CellDrag, который будет хранить данные из одной или нескольких ячеек таблицы QTable.

class CellDrag : public QDragObject 
{ 
public: 
  CellDrag(const QString &text, QWidget *parent = 0, 
           const char *name = 0); 
  const char *format(int index) const; 
  QByteArray encodedData(const char *format) const; 
  
  static bool canDecode(const QMimeSource *source); 
  static bool decode(const QMimeSource *source, QString &str); 
  
private: 
  QString toCsv() const; 
  QString toHtml() const; 
  QString plainText; 
};
      
Класс CellDrag порожден от класса QDragObject. В нем только две функции имеют прямое отношение к механизму "drag and drop" -- это format() и encodedData(). Дополнительно, только лишь для удобства, он предоставляет в распоряжение программиста статические функции canDecode() и decode(), которые извлекают данные в момент сброса.
CellDrag::CellDrag(const QString &text, QWidget *parent, 
                   const char *name) 
    : QDragObject(parent, name) 
{ 
  plainText = text; 
}
      
Конструктору передается строка в текстовом виде, которая будет перемещаться. Это обычный текст, который может содержать символы табуляции и перевода строки. Этот текстовый тип мы использовали в Главе 4, когда добавляли в приложение Spreadsheet поддержку буфера обмена (см. раздел Реализация меню Edit).
const char *CellDrag::format(int index) const 
{ 
  switch (index) { 
    case 0: 
      return "text/csv"; 
    case 1: 
      return "text/html"; 
    case 2:
      return "text/plain"; 
    default: 
      return 0; 
  } 
}
      
Функция format() перекрывает метод родительского класса QMimeSource и возвращает различные MIME типы, поддерживаемые объектом при перетаскивании. В нашем примере поддерживаются три типа данных: CSV (от англ. Comma-Separated Values -- Данные, Разделенные Запятыми), HTML и простой текст.

Когда Qt пытается определить -- какой MIME тип поддерживается перетаскиваемым объектом, она вызывает format() с аргументом index, равным 0, 1, 2... и так до тех пор, пока format() не вернет пустой указатель. Типы MIME для CSV и HTML были взяты из официального списка, который вы найдете по адресу: http://www.iana.org/assignments/media-types/ .

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

QByteArray CellDrag::encodedData(const char *format) const 
{ 
  QByteArray data; 
  QTextOStream out(data); 
  
  if (qstrcmp(format, "text/csv") == 0) { 
    out << toCsv(); 
  } else if (qstrcmp(format, "text/html") == 0) { 
    out << toHtml(); 
  } else if (qstrcmp(format, "text/plain") == 0) { 
    out << plainText; 
  } 
  return data; 
}
      
Функция encodedData() возвращает данные в заказанном формате. Аргумент format, обычно содержит одну из строк, которую возвращает функция format(), но мы не можем безоговорочно утверждать это, поскольку не все приложения проверяют тип MIME вызовом format(). В приложениях Qt такая проверка обычно выполняется вызовом provides() внутри QDragEnterEvent и QDragMoveEvent (как мы это видели ранее).

Для преобразования QString в QByteArray, лучше использовать QTextStream.

QString CellDrag::toCsv() const 
{ 
  QString out = plainText; 
  out.replace("\\", "\\\\"); 
  out.replace("\"", "\\\""); 
  out.replace("\t", "\", \"");
  out.replace("\n", "\"\n\""); 
  out.prepend("\""); 
  out.append("\""); 
  return out; 
}
   
QString CellDrag::toHtml() const 
{ 
  QString out = QStyleSheet::escape(plainText); 
  out.replace("\t", "<td>"); 
  out.replace("\n", "\n<tr><td>"); 
  out.prepend("<table>\n<tr><td>"); 
  out.append("\n</table>"); 
  return out; 
}
      
Функции toCsv() и toHtml() выполняют преобразование символов табуляции и перевода строки в соответствующие элементы формата CSV и HTML. Например, данные
      Red    Green    Blue 
      Cyan   Yellow   Magenta
      
будут преобразованы в
      "Red",   "Green",   "Blue" 
      "Cyan",  "Yellow",  "Magenta"     
      
или в
      <table> 
      <tr><td>Red<td>Green<td>Blue 
      <tr><td>Cyan<td>Yellow<td>Magenta 
      </table>
      
Преобразование выполняется простой заменой одних символов другими, с помощью QString::replace(). Для экранирования специальных символов HTML используется статическая функция QStyleSheet::escape().
bool CellDrag::canDecode(const QMimeSource *source) 
{ 
  return source->provides("text/plain"); 
}
      
Функция canDecode() возвращает true, если перетаскиваемые данные могут быть декодированы, в противном случае возвращается false.

Хотя мы и предусматриваем поддержку трех форматов для перетаскиваемых данных, мы будем принимать только данные в простом текстовом виде, поскольку для наших нужд этого будет более чем достаточно. Если пользователь попытается переместить ячейки из QTable в HTML-редактор, то данные будут преобразованы в HTML-таблицу. Но если пользователь попробует переместить произвольную HTML-таблицу (например, из браузера) в QTable, то эти данные не будут восприняты приложением.

bool CellDrag::decode(const QMimeSource *source, QString &str) 
{ 
  QByteArray data = source->encodedData("text/plain");
  str = QString::fromLocal8Bit((const char *)data, data.size()); 
  return !str.isEmpty(); 
}
      
И, наконец, функция decode() преобразует text/plain данные в QString. Здесь мы предполагаем, что используется 8-ми битная кодировка символов.

Если вы пожелаете точно указывать кодировку символов, для перемещаемых данных, вы можете задать параметр charset формата text/plain, напимер:

      text/plain;charset=US-ASCII 
      text/plain;charset=ISO-8859-1 
      text/plain;charset=Shift_JIS
      
Итак. Мы закончили описание реализации класса CellDrag. Нам осталось только интегрировать его с QTable. Оказывается, класс QTable уже выполняет почти все, что нам нужно. Единственное, что нам остается сделать -- это вызвать setDragEnabled(true) в конструкторе и перекрыть метод QTable::dragObject(), который будет возвращать CellDrag:
QDragObject *MyTable::dragObject() 
{ 
  return new CellDrag(selectionAsString(), this); 
}
      
Мы не приводим текст функции selectionAsString(), поскольку он почти полностью совпадает с текстом функции Spreadsheet::copy().

Чтобы добавить поддержку приема данных, сбрасываемых на таблицу, необходимо перекрыть методы contentsDragEnterEvent() и contentsDropEvent() точно так же, как мы это делали в приложении "Project Chooser".




Спонсоры:
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2021 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру