Новый механизм перетаскивания не работает должным образом в Qt-Quick (Qt 5.3)

#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 (см. прокомментированную строку выше):

  1. Drag.dragType не установлен (по умолчанию Drag.Internal ):

    Перетаскивание работает так, как описано, но передаются только сигналы, введенные и завершенные. Другие сигналы (DragStarted, dragFinished и dropped) подавляются. Таким образом, нет способа отреагировать на падение в DropArea .

  2. 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 каким-то образом сломан…