#qt #drag-and-drop #qml #qt5 #qt-quick
#qt #перетаскивание #qml #qt5 #qt-quick
Вопрос:
Я попытался реализовать перетаскивание в Qt 5.3, используя новые типы QML Drag
, DragEvent
и DropArea
. Это оригинальный пример из документации Drag
типа QML с некоторыми небольшими изменениями:
import QtQuick 2.2
Item {
width: 800; height: 600
DropArea {
width: 100; height: 100; anchors.centerIn: parent
Rectangle {
anchors.fill: parent
color: parent.containsDrag ? "red" : "green"
}
onEntered: print("entered");
onExited: print("exited");
onDropped: print("dropped");
}
Rectangle {
x: 15; y: 15; width: 30; height: 30; color: "blue"
Drag.active: dragArea.drag.active
// Drag.dragType: Drag.Automatic
Drag.onDragStarted: print("drag started");
Drag.onDragFinished: print("drag finished");
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
}
}
Ожидаемое поведение: маленький синий прямоугольник (цель перетаскивания) можно перетаскивать с помощью мыши. При перетаскивании поверх большего зеленого прямоугольника в центре окна этот прямоугольник становится красным, а при выходе — зеленым. Кроме того, сигналы DragStarted, entered, exited, dropped и dragFinished передаются вовремя, и соответствующие обработчики сигналов распечатывают свои сообщения.
Опытное поведение:
Зависит от Drag.dragType
(см. прокомментированную строку выше):
-
Drag.dragType
не установлен (по умолчаниюDrag.Internal
):Перетаскивание работает так, как описано, но передаются только сигналы, введенные и завершенные. Другие сигналы (DragStarted, dragFinished и dropped) подавляются. Таким образом, нет способа отреагировать на падение в
DropArea
. -
Drag.dragType
установлено значениеDrag.Automatic
:Теперь передаются все сигналы, но синий прямоугольник (цель перетаскивания) не перемещается с помощью мыши. Вместо этого курсор мыши меняет свою форму, чтобы визуализировать возможные цели перетаскивания. После того, как мышь была отпущена, синий прямоугольник переходит в последнюю позицию мыши.
Ни один из этих двух вариантов не радует. Как я могу получить все сигналы и при этом иметь возможность перетаскивать цель перетаскивания? К сожалению, в документации почти ничего не сказано о перетаскивании в QML, особенно о зловещем Drag.dragType
.
Ответ №1:
Если вы откроете QQuickDrag
исходный код и посмотрите на различия между start()
, который используется Drag.Internal
, и startDrag()
который используется Drag.Automatic
, разница довольно очевидна. start()
настраивает прослушиватель изменений событий, который затем используется для обновления положения прикрепленного объекта. startDrag()
этого не делает.
Почему это работает именно так? Я понятия не имею! Документация по перетаскиванию QtQuick 2, безусловно, нуждается в улучшении.
Существует довольно простое решение: возьмите лучшее из обоих миров. Используйте Drag.Automatic
, но вместо настройки Drag.active
, вызовите start()
и drop()
вручную. Он не будет вызывать Drag.onDragStarted()
и Drag.onDragFinished()
, но вы, по сути, получаете их бесплатно в любом случае, прослушивая изменения в MouseArea
‘s drag.active
.
Вот концепция в действии:
import QtQuick 2.0
Item {
width: 800; height: 600
DropArea {
width: 100; height: 100; anchors.centerIn: parent
Rectangle {
anchors.fill: parent
color: parent.containsDrag ? "red" : "green"
}
onEntered: print("entered");
onExited: print("exited");
onDropped: print("dropped");
}
Rectangle {
x: 15; y: 15; width: 30; height: 30; color: "blue"
// I've added this property for simplicity's sake.
property bool dragActive: dragArea.drag.active
// This can be used to get event info for drag starts and
// stops instead of onDragStarted/onDragFinished, since
// those will neer be called if we don't use Drag.active
onDragActiveChanged: {
if (dragActive) {
print("drag started")
Drag.start();
} else {
print("drag finished")
Drag.drop();
}
}
Drag.dragType: Drag.Automatic
// These are now handled above.
//Drag.onDragStarted: print("drag started");
//Drag.onDragFinished: print("drag finished");
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
}
}
Я понимаю, что это не полностью удовлетворяющее решение, но оно соответствует вашему ожидаемому поведению.
Это решение предлагает:
- Уведомления для всех желаемых событий: перетаскивание начато, перетаскивание завершено, вход в область перетаскивания, выход из области перетаскивания и сброс в область перетаскивания.
- Анимация перетаскивания автоматически обрабатывается QtQuick. Квадрат не зависает на месте, как это происходит при запуске примера кода с
Drag.Automatic
.
Чего он не предлагает:
- Объяснение того, почему функциональность перетаскивания QtQuick работает именно так, или это даже предполагаемое поведение разработчиками. Текущая документация кажется неоднозначной.
Комментарии:
1. Спасибо за ваш подробный ответ. Хотя я все еще запутался в различных типах перетаскивания, ваше решение полностью удовлетворяет мои потребности, поскольку оно имитирует требуемые события и, следовательно, позволяет отделить источник перетаскивания и область перетаскивания. Тем не менее, я думаю, что команда Qt должна улучшить QtQuick DnD, поскольку он не очень полезен и интуитивно понятен.
2. Обратите внимание, что ситуация не изменилась в Qt 5.15.x, обходной путь из этого поста по-прежнему требуется.
Ответ №2:
Просто столкнулся с этим сам (используя Qt 5.2, но там существует та же проблема). У меня есть «ползунок» на оси X, и я просто хотел узнать, когда перетаскивание было завершено … вместо того, чтобы реагировать на каждое изменение положения по пути. Мой обходной путь включал взлом состояний / переходов с ScriptAction
для обеспечения логики. Это упрощенная версия для имитации ответа на сигнал «onDragFinished». Поэтому, хотя он не охватывает все ваши сигналы перетаскивания, он может направить вас в правильном направлении.
Rectangle {
id: sliderControl
height: coordinates.height
width: 80
color: "#F78181"
border.color: "#FE2E2E"
border.width: 1
opacity: 0.4
MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: sliderControl
drag.axis: Drag.XAxis
drag.minimumX: 0
drag.maximumX: view.width - sliderControl.width
hoverEnabled: true
}
states: [
State {
name: "dragging"
when: mouseArea.drag.active
},
State {
name: "finished_dragging"
when: !mouseArea.drag.active
}
]
transitions: [
Transition {
from: "dragging"
to: "finished_dragging"
ScriptAction {
script: console.log("finished dragging script");
}
}
]
}
ps — Я знаю, что такой «обходной путь» не соответствует параметрам вознаграждения, но я был очень расстроен, найдя только ваш вопрос (без решений), когда искал помощь по этому вопросу. Надеюсь, кто-нибудь еще, спотыкающийся на этом пути, найдет это полезным. К сожалению, я также понятия не имею, что происходит с QML Drag.dragType
.
Комментарии:
1. спасибо за ваш ответ. Я рад, что я там не один! 😉 Я также уже экспериментировал с некоторым подобным решением, но, к сожалению, в моем «реальном» коде инициирование перетаскивания
MouseArea
полностью не зависит отDropArea
, который должен обрабатывать удаление. Таким образом, передача события drop отMouseArea
кDropArea
довольно сложна / почти невозможна без взлома. Я начинаю думать, что механизм DnD в QtQuick каким-то образом сломан…