Пример "Fridge Magnets"
Файлы:
Пример "Fridge Magnets" показывает, как предоставить более одного типа MIME-закодированных данных с операцией перетаскивания. В этом приложении пользователь может поиграться с набором магнитиков на холодильник, используя перетаскивание для образования новых предложений из слов нанесённых на магнитики. Пример состоит из двух классов:
Сначала мы взглянем на класс DragWidget, затем мы быстро изучим класс DragLabel. Определение класса DragWidgetКласс DragWidget унаследован от QWidget, предоставляет поддержку для операций перетаскивания: class DragWidget : public QWidget { public: DragWidget(QWidget *parent = 0); protected: void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); void dropEvent(QDropEvent *event); }; Чтобы разрешить перетаскивание, мы просто переопределяем обработчики событий dragEnterEvent(), dragMoveEvent() и dropEvent(), унаследованных от QWidget. Реализация класса DragWidgetВ конструкторе мы сначала открываем файл, содержащий слова наших магнитиков на холодильник: DragWidget::DragWidget(QWidget *parent) : QWidget(parent) { QFile dictionaryFile(":/dictionary/words.txt"); dictionaryFile.open(QFile::ReadOnly); QTextStream inputStream(&dictionaryFile); QFile - устройство ввода/вывода для чтения и записи текстовых и двоичных файлов и ресурсов, а его также может быть использовано само или в комбинации с QTextStream или QDataStream. Мы выбрали использование класса QTextStream для чтения содержимого файла, предоставляющего удобный интерфейс для чтения и записи текста. int x = 5; int y = 5; while (!inputStream.atEnd()) { QString word; inputStream >> word; if (!word.isEmpty()) { DragLabel *wordLabel = new DragLabel(word, this); wordLabel->move(x, y); wordLabel->show(); x += wordLabel->width() + 2; if (x >= 245) { x = 5; y += wordLabel->height() + 2; } } } Затем мы создаём магнитики: Пока есть данные (метод QTextStream::atEnd() возвращает true если в потоке больше нет данных для чтения), мы читаем по одной строке за раз используя метод QTextStream'а, readLine(). Для каждой строки мы создаём объект DragLabel, используя прочитанную строку как текст, вычисляем его позицию и обеспечиваем, чтобы он был виден, вызывая метод QWidget::show(). QPalette newPalette = palette(); newPalette.setColor(QPalette::Window, Qt::white); setPalette(newPalette); setMinimumSize(400, qMax(200, y)); setWindowTitle(tr("Fridge Magnets")); Также мы устанавливаем минимальный размер, заголовок окна и палитру виджета FridgeMagnets. setAcceptDrops(true); } В заключение, чтобы дать возможность нашему пользователю перемещать магнитики, мы также должны установить свойство виджета FridgeMagnets, acceptDrops. Установка этого свойства равным true сообщает системе, что этот виджет может принимать события отпускания (события, которые отправляются когда действия перетаскивания завершены). Когда действие отпускания входит в пределы нашего виджета, мы получаем событие входа перетаскивания. QDragEnterEvent наследует большинство из своей функциональности из класса QDragMoveEvent, который в свою очередь наследует большинство своей функциональности из QDropEvent. Обратите внимание на то, что мы должны принять это событие для того, чтобы получить события перемещения перетаскивания, которые отправляются пока выполняется действие перетаскивания. Событие вхождения перетаскивания всегда непосредственно следует за событием перемещения перетаскивания. В нашей реализации dragEnterEvent() мы сначала определяем поддерживается ли MIME-тип события или нет: void DragWidget::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat("application/x-fridgemagnet")) { if (children().contains(event->source())) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->acceptProposedAction(); } Если тип - "application/x-fridgemagnet" и событие исходит от любых из этих виджетов магнитиков на холодильник в приложении, мы сначала устанавливаем действия отпускания события используя метод QDropEvent::setDropAction(). Действие отпускания события - действие выполняемое с данными цели. Qt::MoveAction показывает, что данные переместились из источник в приёмник. Затем мы вызываем метод accept() события чтобы показать, что мы обработали событие. Вообще, непринятые события могут передаваться родительскому виджету. Если событие исходит из любого другого виджета, мы просто принимаем предлагаемое действие. } else if (event->mimeData()->hasText()) { event->acceptProposedAction(); } else { event->ignore(); } } Мы также принимаем предлагаемое действие если MIME-тип события - text/plain, т.е., если QMimeData::hasText() возвращает true. Если событие имеет любой другой тип, с другой стороны, мы вызываем метод ignore() события позволяя передачу события дальше. void DragWidget::dragMoveEvent(QDragMoveEvent *event) { if (event->mimeData()->hasFormat("application/x-fridgemagnet")) { if (children().contains(event->source())) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->acceptProposedAction(); } } else if (event->mimeData()->hasText()) { event->acceptProposedAction(); } else { event->ignore(); } } События перемещения перетаскивания происходят, когда курсор входит в пределы виджета, когда он перемещается внутри виджета и когда на клавиатуре нажата клавиша-модификатор в то время, как виджет имеет фокус ввода. Наш виджет будет часто получать события перемещения перетаскивания пока объект перетаскивания находится внутри его границ. Переопределим метод dragMoveEvent(), а также рассмотрим событие точно таким же способом как мы делали с событиями вхождения перетаскивания. void DragWidget::dropEvent(QDropEvent *event) { if (event->mimeData()->hasFormat("application/x-fridgemagnet")) { const QMimeData *mime = event->mimeData(); Обратите внимание на то, что обработчик события dropEvent() ведёт себя немного по-другому: Сначала мы получаем MIME-данные события. Класс QMimeData предоставляет контейнер для данных, которые записывать информацию о своём MIME-типе. Объекты QMimeData связывают данные, которые они содержат соответствующие MIME-типы, чтобы гарантировать, что информация может быть безопасно переданы между приложениями, и скопированы внутри одного и того же приложения. QByteArray itemData = mime->data("application/x-fridgemagnet"); QDataStream dataStream(&itemData, QIODevice::ReadOnly); QString text; QPoint offset; dataStream >> text >> offset; DragLabel *newLabel = new DragLabel(text, this); newLabel->move(event->pos() - offset); newLabel->show(); if (children().contains(event->source())) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->acceptProposedAction(); } Затем мы извлекаем данные, связанные с MIME-типом "application/x-fridgemagnet", используя поток данных и создаём новый объект DragLabel. Класс QDataStream предоставляет преобразование двоичных данных в последовательную форму для QIODevice (поток данных - двоичный поток закодированной информации, которая на 100% независима от основной операционной системы компьютера, ЦПУ или порядка байтов). В заключение, мы перемещаем магнитик в позицию события перед проверкой если событие исходит из любого виджета магнитика в этом приложении. Если это так, мы устанавливаем событие отпускания перетаскивания равным Qt::MoveAction и вызываем метод accept() события. В противном случае, мы просто принимаем предлагаемое действие. } else if (event->mimeData()->hasText()) { QStringList pieces = event->mimeData()->text().split(QRegExp("\\s+"), QString::SkipEmptyParts); QPoint position = event->pos(); foreach (QString piece, pieces) { DragLabel *newLabel = new DragLabel(piece, this); newLabel->move(position); newLabel->show(); position += QPoint(newLabel->width(), 0); } event->acceptProposedAction(); } else { event->ignore(); } } Если MIME-тип события - text/plain, т.е., если QMimeData::hasText() возвращает true, мы извлекаем его текст и разделяем его на слова. Для каждого слова создаём новое действие DragLabel и показываем его в позиции события плюс смещение, зависящее от количества слов в тексте. В заключение мы принимаем предлагаемое действие. Если событие имеет любой другой тип, мы вызываем метод ignore() события, позволяя передачу события далье. Это завершает реализацию DragWidget. Теперь, давайте взглянем на класс DragLabel. Определение класса DragLabelКаждый магнитик на холодильник представляется экземпляром класса DragLabel: class DragLabel : public QLabel { public: DragLabel(const QString &text, QWidget *parent); protected: void mousePressEvent(QMouseEvent *event); private: QString labelText; }; Ранее мы установили свойство acceptDrops виджета нашего главного приложения и переопределили обработчики событий QWidget'а - dragEnterEvent(), dragMoveEvent() и dropEvent(), чтобы поддержать перетаскивание. Кроме того, мы должны переопределить метод mousePressEvent() в нашем виджете магнитика, чтобы дать пользователю возможность забрать магнитик в первом месте. Реализация класса DragLabelВ конструкторе DragLabel мы сначала создаёем объект QImage, на котором мы рисуем текст магнитика на холодильник и рамку: DragLabel::DragLabel(const QString &text, QWidget *parent) : QLabel(parent) { QFontMetrics metric(font()); QSize size = metric.size(Qt::TextSingleLine, text); QImage image(size.width() + 12, size.height() + 12, QImage::Format_ARGB32_Premultiplied); image.fill(qRgba(0, 0, 0, 0)); QFont font; font.setStyleStrategy(QFont::ForceOutline); Его размер зависит от текущего размера шрифта, а его формат - QImage::Format_ARGB32_Premultiplied (т.е., изображение сохраняется используя premultiplied 32-битный формат ARGB (0xAARRGGBB)). Затем мы создаём объект шрифта, который использует шрифт приложения по умолчанию, и устанавливаем его стилевую стратегию. Стилевая стратегия сообщает алгоритму сопоставления шрифтов какой будет использоваться тип шрифтов, чтобы найти соответствующее семейство по умолчанию. QFont::ForceOutline заставляет использовать контурные шрифты. QPainter painter; painter.begin(&image); painter.setRenderHint(QPainter::Antialiasing); painter.setBrush(Qt::white); painter.drawRoundRect(QRectF(0.5, 0.5, image.width()-1, image.height()-1), 25, 25); painter.setFont(font); painter.setBrush(Qt::black); painter.drawText(QRect(QPoint(6, 6), size), Qt::AlignCenter, text); painter.end(); Чтобы отрисовать текст и рамку в изображении, мы используем класс QPainter. QPainter предоставляет высоко оптимизированные методы для выполнения большей части рисования, требующегося ГПИ программ. Он может рисовать всё от линий простых до сложных фигур наподобие секторов и дуг. Он также отрисовывает выравненный текст и растровые изображения. Рисовальщик может быть активирован передачей в конструктор устройства рисования или используя метод begin(), как сделали мы в этом примере. Метод end() его деактивирует. Обратите внимание на то, что последняя функция вызывается автоматически на разрушение когда рисовальщик активируется своим конструктором. Подсказка отображения QPainter::Antialiasing обеспечивает, что механизм рисования будет сглаживать края примитивов, если это возможно. setPixmap(QPixmap::fromImage(image)); labelText = text; } Когда рисование выполнено, мы преобразуем наше изображение в растровое используя метод QPixmap'а, fromImage(). Этот метод также принимает необязательный аргумент флагов и преобразует переданное изображение в растровое используя заданные флаги для управления преобразованием (аргумент флагов - побитовое ИЛИ всех Qt::ImageConversionFlags; передача 0 для флагов установит все опции по умолчанию). В заключение мы устанавливаем свойство pixmap метки и сохраняем текст метки для последующего использования. Обратите внимание на то, что установка растрового изображения очищает любое предыдущее содержимое и отключает быстрые" клавиши метки-партнёра, если есть. Теперь давайте взглянем на обработчик событий mousePressEvent(): void DragLabel::mousePressEvent(QMouseEvent *event) QByteArray itemData; QDataStream dataStream(&itemData, QIODevice::WriteOnly); dataStream << labelText << QPoint(event->pos() - rect().topLeft()); События мыши происходят, когда нажимается или отпускается кнопка мыши внутри виджета, или когда переместился курсор мыши. Переопределив метод mousePressEvent() мы гарантировали, что мы получим события нажатия кнопок мыши для виджета магнитика. Когда же мы получаем такое событие, мы сначала создайм байтовый массив для сохранения данных нашего элемента, а объект QDataStream - чтобы направить поток данных в байтовый массив. QMimeData *mimeData = new QMimeData; mimeData->setData("application/x-fridgemagnet", itemData); mimeData->setText(labelText); Затем мы создаём новый объект QMimeData. Как было сказано выше, объекты QMimeData связывают данные, которые они хранят, с соответствующими MIME-типами, чтобы гарантировать что информация может быть безопасно переслана между приложениями. Метод setData() устанавливает данные, связанные с переданным MIME-типом. В нашем случае мы связываем наш элемент данных с пользовательским типом "application/x-fridgemagnet". Обратите внимание на то, что мы связываем текст магнитика с MIME-типом text/plain используя метод QMimeData'а, setText(). Мы всегда увидим как наш главный виджет обнаруживает оба этих MIME-типа с помощью своих обработчиков событий. QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); drag->setHotSpot(event->pos() - rect().topLeft()); drag->setPixmap(*pixmap()); hide(); В заключение, мы создаём объект QDrag. Класс QDrag, который обрабатывает большинство деталей операции перетаскивания, предоставляя поддержку для передачи данных перетаскивания основанных на MIME. Передаваемые операцией перетаскивания данные содержатся в объекте QMimeData. Когда мы вызываем метод QDrag'а, setMimeData(), право собственности на наш элемент данных передаётся объекту QDrag. Также мы задаём активную точку курсора, т.е. его позицию во время выполнения перетаскивания, чтобы она была верхним левым углом нашего магнитика. Мы вызываем метод setPixmap(), чтобы установить растровое изображение, используемое для представления данных во время операции перетаскивания. Обычно это растровое изображение показывает пиктограмму, которая представляет MIME-тип передаваемых данных, но может быть использована любое растровое изображение. В этом примере мы решили использовать само изображение магнитика чтобы сделать магнитик появляющимся при перемещении, немедленно скрывая активированный виджет. if (drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction) == Qt::MoveAction) close(); else show(); } Затем мы начинаем перетаскивание используя метод QDrag'а, exec(), требуя, чтобы магнитик перемстился когда перетаскивание завершено. Метод возвращает выполняемое действие отпускания; если это действие эквивалентно Qt::MoveAction, мы закрываем активированный виджет магнитика, поскольку затем мы создаём новый магнитик (с теми же данными) в позиции отпускания (смотрите реализацию метода dropEvent() наши основных виджетов). В противном случае, если отпускание происходит за пределами главного виджета, мы просто показываем виджет в его первоначальной позиции.
|
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |