如何确保 Popup 在 QML Map 中可见

How to ensure Popup is visible inside a QML Map

我正在构建一个嵌入了 openstreetmap QML 组件的 Qt 5.11 应用程序。 我刚刚写了一个最小的复制案例。它包括在地图上显示对象(此处为五个蓝点)。悬停对象时,会显示一个带有一些文本的小弹出窗口。

当对象靠近边缘时,弹出窗口无法正确显示。 我虽然我会使用 visibleArea 检查这个,但是 属性 是在 Qt 5.12 中添加的。

我找不到让弹出窗口完全可见的解决方案。 Qt 5.11 中有我可以做的解决方法吗? 这里是 QML 文件。只需键入 qmlscene sample.qml 并将鼠标悬停在蓝点上即可查看。

import QtQuick 2.11
import QtQuick.Controls 2.4
import QtLocation 5.11
import QtPositioning 5.11
import QtQuick.Window 2.11

Window {
    id: root; width: 800; height: 600;

    Plugin { id: mapPlugin; name: "osm"; }

    ListModel {
        id: myModel
        ListElement { latitude: 48.2351164;          longitude: 6.8986936;           name: "The point on the center"; }
        ListElement { latitude: 48.235111272600186;  longitude: 6.9007217756551995;  name: "The point on the right"; }
        ListElement { latitude: 48.23512783507458;   longitude: 6.896574932520792;   name: "The point on the left"; }
        ListElement { latitude: 48.23614708436043;   longitude: 6.898623901851295;   name: "The point on the top"; }
        ListElement { latitude: 48.23417574713512;   longitude: 6.898641104398024;   name: "The point on the bottom"; }
    }

    Map {
        id: map
        anchors.fill: parent
        plugin: mapPlugin
        center: QtPositioning.coordinate(48.2351164, 6.8986936)
        zoomLevel: 19

        MapItemView {
            model: myModel

            delegate: MapQuickItem {
                anchorPoint.x: myRect.width / 2
                anchorPoint.y: myRect.height / 2
                width: myRect.width
                height: myRect.height

                coordinate: QtPositioning.coordinate(model.latitude, model.longitude)

                sourceItem: Rectangle {
                    id: myRect

                    readonly property int radius: 10

                    width: radius * 2
                    height: radius * 2
                    color: "transparent"

                    Canvas {
                        id: myCanvas
                        anchors.fill: parent

                        property alias textVisible: myPopup.visible

                        onPaint: {
                            var width = myRect.width;
                            var height = myRect.height;
                            var centreX = width / 2;
                            var centreY = height / 2;
                            var ctx = getContext("2d");
                            ctx.reset();
                            ctx.fillStyle = "blue";
                            ctx.globalAlpha = 1;
                            ctx.beginPath();
                            ctx.moveTo(centreX, centreY);
                            ctx.arc(centreX, centreY, myRect.radius, 0, Math.PI * 2, false);
                            ctx.closePath();
                            ctx.fill();
                        }

                        MouseArea {
                            x: 0; y: 0;
                            width: myRect.radius * 2
                            height: myRect.radius * 2
                            acceptedButtons: Qt.LeftButton
                            hoverEnabled: true
                            onEntered: { myCanvas.textVisible = true }
                            onExited: { myCanvas.textVisible = false }
                        }
                    }

                    Popup {
                        id: myPopup
                        x: myRect.width / 2 - width / 2
                        y: myRect.height / 2 + 20
                        visible: false
                        Label { text: model.name; horizontalAlignment: Text.AlignHCenter; }
                    }
                }
            }
        }
    }
}

非常感谢任何帮助。

您可以检查 (popup width+popup x) 是否超出屏幕宽度,然后更改 x,y 以调整弹出位置。您可以查看 Popup 组件的以下修改代码。根据您的标记位置调整 X 和 Y。

          Popup {
                    id: myPopup
                    x: {
                        if((mapItem.x+myPopup.width) >= root.width)
                           return -(myRect.width/2)-(width)
                        else if((mapItem.x-myPopup.width) < 0)
                           return (myRect.width)
                        else
                          return myRect.width / 2 - width / 2
                     }
                    y: {
                        if((mapItem.y+myPopup.height) >= root.height)
                            return  -(myRect.height/2)-(height)
                        else if((mapItem.y-myPopup.height) < 0)
                            return (height)
                        else
                            return myRect.height / 2 - height / 2
                     }
                    visible: false
                    Label { text: model.name;anchors.centerIn: parent;horizontalAlignment: Text.AlignHCenter; }
                }

找了一段时间,终于找到这两个函数:mapToItem and mapFromItem

所以,我首先需要将当前项目点映射到map项目坐标系。然后,我必须检查该点是否在地图视口内。 如果不是,我必须调整坐标,然后将点映射回当前项目坐标系。当接近底部和右边界时,弹出窗口的宽度和高度似乎会减小,所以我不得不使用 contentHeight、contentWidth 和 padding 属性来获得真正的弹出窗口大小。

而且我必须将弹出窗口 x 和 y 初始化为一个不同于零的值,以允许鼠标事件传递到蓝点上。

这是工作代码,供可能需要的人使用。

import QtQuick 2.11
import QtQuick.Controls 2.4
import QtLocation 5.11
import QtPositioning 5.11
import QtQuick.Window 2.11

Window {
    id: root; width: 800; height: 600;

    Plugin { id: mapPlugin; name: "osm"; }

    ListModel {
        id: myModel
        ListElement { latitude: 48.2351164;          longitude: 6.8986936;           name: "The point on the center"; }
        ListElement { latitude: 48.235111272600186;  longitude: 6.9007217756551995;  name: "The point on the right"; }
        ListElement { latitude: 48.23512783507458;   longitude: 6.896574932520792;   name: "The point on the left"; }
        ListElement { latitude: 48.23614708436043;   longitude: 6.898623901851295;   name: "The point on the top"; }
        ListElement { latitude: 48.23417574713512;   longitude: 6.898641104398024;   name: "The point on the bottom"; }
    }

    Map {
        id: map
        anchors.fill: parent
        plugin: mapPlugin
        center: QtPositioning.coordinate(48.2351164, 6.8986936)
        zoomLevel: 19

        MapItemView {
            model: myModel

            delegate: MapQuickItem {
                anchorPoint.x: myRect.width / 2
                anchorPoint.y: myRect.height / 2
                width: myRect.width
                height: myRect.height

                coordinate: QtPositioning.coordinate(model.latitude, model.longitude)

                sourceItem: Rectangle {
                    id: myRect

                    readonly property int radius: 10

                    width: radius * 2
                    height: radius * 2
                    color: "transparent"

                    Canvas {
                        id: myCanvas
                        anchors.fill: parent

                        property alias textVisible: myPopup.visible

                        onPaint: {
                            var width = myRect.width;
                            var height = myRect.height;
                            var centreX = width / 2;
                            var centreY = height / 2;
                            var ctx = getContext("2d");
                            ctx.reset();
                            ctx.fillStyle = "blue";
                            ctx.globalAlpha = 1;
                            ctx.beginPath();
                            ctx.moveTo(centreX, centreY);
                            ctx.arc(centreX, centreY, myRect.radius, 0, Math.PI * 2, false);
                            ctx.closePath();
                            ctx.fill();
                        }

                        MouseArea {
                            x: 0; y: 0;
                            width: myRect.radius * 2
                            height: myRect.radius * 2
                            acceptedButtons: Qt.LeftButton
                            hoverEnabled: true

                            onPositionChanged: {
                                myCanvas.textVisible = true;

                                // absolute position in map coordinate system
                                var absPos = mapToItem(map, mouse.x, mouse.y);

                                // margin between mouse pointer and the popup
                                var cursorMargin = 10;
                                // extra margin for right and bottom side
                                var bottomRightSideExtraMargin = 10;

                                // add the cursor margin to the position
                                var absPopupX = absPos.x + cursorMargin;
                                var absPopupY = absPos.y + cursorMargin;

                                // adjust if the popup is out of view on the bottom or right sides
                                if (absPos.x + myPopup.contentWidth + myPopup.leftPadding + myRect.radius * 2 + bottomRightSideExtraMargin > root.width) {
                                    absPopupX = root.width - (myPopup.contentWidth + myPopup.leftPadding + cursorMargin + bottomRightSideExtraMargin);
                                }
                                if (absPos.y + myPopup.contentHeight + myPopup.topPadding + myRect.radius * 2 + bottomRightSideExtraMargin > root.height) {
                                    absPopupY = root.height - (myPopup.contentHeight + myPopup.topPadding + cursorMargin + bottomRightSideExtraMargin);
                                }

                                // convert back to the current item coordinate system
                                var popupPos = mapFromItem(map, absPopupX, absPopupY);
                                myPopup.x = popupPos.x;
                                myPopup.y = popupPos.y;
                            }

                            onExited: {
                                myCanvas.textVisible = false;
                            }
                        }
                    }

                    Popup {
                        id: myPopup
                        // original offset to allow mouse hover
                        x: 20; y: 20;
                        visible: false
                        Label { text: model.name; horizontalAlignment: Text.AlignHCenter; }
                    }
                }
            }
        }
    }
}