До сих пор мы имели дело с предопределенными типами перетаскиваемых объектов. Например, мы использовали QUriDrag, для перетаскивания файлов, и QTextDrag -- для текста. Оба этих класса являются наследниками QDragObject, который служит базой для всех перемещаемых объектов. В свою очредь, класс QDragObject наследует свойства абстрактного класса QMimeSource, предназначенного для хранения данных различных типов.
Если вы пожелаете перемещать объекты с текстовой информацией, с изображениями, с именами файлов или с информацией о цвете, то можно использовать предопределенные классы Qt: QTextDrag, QImageDrag, QUriDrag и QColorDrag. Но если вам необходимо перемещать нестандартные типы данных, то у вас есть два пути:
Сохранить информацию, в двоичном представлении, в объекте класса QStoredDrag.
Создать свой собственный класс перетаскиваемых объектов, породив его от QDragObject и перекрыв соответствующие виртуальные методы.
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".
Пред. | В начало | След. |
Drag and Drop. | На уровень выше | Расширенные возможности буфера обмена. |