Qt開発者必見!Item.mapToGlobal()のエラーと解決策

2025-06-06

QtプログラミングにおけるItem.mapToGlobal()は、特定のItem(例えば、QMLのItemやC++のQQuickItem、あるいはより一般的なQWidget)のローカル座標を、グローバルスクリーン座標に変換するためのメソッドです。

簡単に言うと、以下のことを行います。

  1. ローカル座標とは? Item自身の左上を(0, 0)とした場合の、そのItem内部の座標のことです。例えば、幅100、高さ100のItemの真ん中の座標は、ローカル座標では(50, 50)となります。

  2. グローバルスクリーン座標とは? ディスプレイ画面全体の左上を(0, 0)とした場合の座標のことです。複数のディスプレイがある場合は、それらを合わせた仮想的なデスクトップ全体の左上を基準とします。

  3. mapToGlobal()の役割 Item.mapToGlobal(point)を呼び出すと、引数point(ローカル座標)で指定された点が、画面上のどの位置にあるのか(グローバルスクリーン座標)を返します。

具体例



Itemがまだ表示されていない、またはレイアウト計算が完了していない

エラー/症状
mapToGlobal()が予期しない(0,0)や不正確な値を返す。特に、アプリケーションの起動時や、動的にUI要素を追加/表示した直後に発生しやすい。

原因
mapToGlobal()は、Itemが実際に画面上のどこに描画されるかが決まった後にのみ、正確な座標を返すことができます。まだItemがレイアウトシステムによって配置されていなかったり、描画イベントが処理されていなかったりすると、正しいグローバル座標が得られません。

トラブルシューティング

  • QApplication::processEvents() (C++)
    デバッグ目的や、特殊な状況で強制的にイベントを処理させたい場合に一時的に使用できますが、通常のアプリケーションフローで多用すべきではありません。
  • Component.onCompleted (QML)
    QMLでは、コンポーネントが完全にロードされ、初期プロパティが設定された後にComponent.onCompletedシグナルが発せられます。このハンドラ内でmapToGlobal()を使用するのが一般的です。
  • 遅延実行
    QTimer::singleShot() (C++) や Qt.callLater() (QML) を使用して、UIが完全にロードされ、表示された後にmapToGlobal()を呼び出すようにします。
    // QML
    Component.onCompleted: {
        // わずかな遅延を設けて呼び出す
        Qt.callLater(function() {
            var globalPos = myItem.mapToGlobal(Qt.point(0, 0));
            console.log("Global X:", globalPos.x, "Global Y:", globalPos.y);
        });
    }
    
    // C++ (QWidgetの場合)
    // QWidget::showEvent() や QEvent::Paint で呼び出す、あるいは
    // QTimer::singleShot(0, this, [this]() {
    //     QPoint globalPos = myWidget->mapToGlobal(QPoint(0, 0));
    //     qDebug() << "Global Pos:" << globalPos;
    // });
    // のようにイベントループの次回のサイクルで実行させる。
    

エラー/症状
得られるグローバル座標が大幅にずれている、あるいは全く関係のない場所を指している。

原因
mapToGlobal()ローカル座標をグローバル座標に変換します。しかし、Itempos()プロパティやgeometry().topLeft()などを直接mapToGlobal()に渡してしまい、意図しない結果になることがあります。

  • QML Item.x, Item.y: 親Itemからの相対座標です。
  • QWidget::geometry().topLeft(): 親ウィジェットからの相対座標です。
  • QWidget::pos(): 親ウィジェットからの相対座標です。

これらのプロパティは、すでに親Itemの座標系における位置を示しています。mapToGlobal()は、そのItem自身の座標系を基準とするため、これらを直接渡すと結果がずれます。



QML での Item.mapToGlobal() 使用例

QML では、Item およびその派生クラス(Rectangle, Text など)が mapToGlobal() メソッドを持っています。

例1:アイテムの左上隅のグローバル座標を取得する

この例では、myRectangle の左上隅(ローカル座標 (0,0))が画面上のどこに位置するかを表示します。

// main.qml
import QtQuick
import QtQuick.Window

Window {
    width: 640
    height: 480
    visible: true
    title: "mapToGlobal Example QML"

    Rectangle {
        id: myRectangle
        x: 100 // 親Windowからの相対X座標
        y: 80  // 親Windowからの相対Y座標
        width: 150
        height: 100
        color: "lightblue"
        border.color: "blue"
        border.width: 2

        Text {
            anchors.centerIn: parent
            text: "Click Me!"
            color: "darkblue"
            font.bold: true
        }

        MouseArea {
            anchors.fill: parent
            onClicked: {
                // myRectangleのローカル座標 (0,0) をグローバル座標に変換
                var globalTopLeft = myRectangle.mapToGlobal(Qt.point(0, 0));
                console.log("myRectangle の左上 (0,0) のグローバル座標:");
                console.log("X:", globalTopLeft.x, "Y:", globalTopLeft.y);

                // myRectangleの中心 (ローカル座標 width/2, height/2) をグローバル座標に変換
                var globalCenter = myRectangle.mapToGlobal(Qt.point(myRectangle.width / 2, myRectangle.height / 2));
                console.log("myRectangle の中心のグローバル座標:");
                console.log("X:", globalCenter.x, "Y:", globalCenter.y);

                // クリックされた位置 (mouse.x, mouse.y はmyRectangleのローカル座標) をグローバル座標に変換
                var clickedGlobalPos = myRectangle.mapToGlobal(Qt.point(mouse.x, mouse.y));
                console.log("クリックされた位置のグローバル座標 (MouseArea):");
                console.log("X:", clickedGlobalPos.x, "Y:", clickedGlobalPos.y);

                // QMLのWindowのx, yプロパティはグローバル座標なので、それを参照
                console.log("Windowのグローバル座標:");
                console.log("Window X:", Window.x, "Window Y:", Window.y);
            }
        }
    }
}

解説

  • Window.xWindow.y は、アプリケーションウィンドウ自体のグローバル座標(画面左上からの位置)を示します。
  • MouseAreamouse.xmouse.y は、MouseArea が属する Item(この場合は myRectangle)のローカル座標系におけるクリック位置です。そのため、myRectangle.mapToGlobal(Qt.point(mouse.x, mouse.y)) を使うと、クリックされた正確な画面上の位置が得られます。
  • myRectangle.mapToGlobal(Qt.point(0, 0)) は、myRectangle 自体の左上隅(ローカル座標の原点)が、画面全体のどこに位置するかを返します。
  • myRectangle.xmyRectangle.y は、親である Window のコンテンツ領域の左上からの相対座標です。

C++ では、QWidget クラスおよび QQuickItem クラス(QML の Item に対応する C++ クラス)が mapToGlobal() メソッドを提供します。

例1:QWidget での使用例

この例では、メインウィンドウ内のボタンの左上隅のグローバル座標を取得し、それを表示します。

// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPushButton>
#include <QLabel>
#include <QPoint>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();

private:
    QPushButton *pushButton;
    QLabel *globalPosLabel;
};

#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>
#include <QScreen>
#include <QGuiApplication>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setWindowTitle("mapToGlobal Example C++");
    resize(400, 300);

    QWidget *centralWidget = new QWidget(this);
    setCentralWidget(centralWidget);

    QVBoxLayout *layout = new QVBoxLayout(centralWidget);

    pushButton = new QPushButton("Get Button Global Position", centralWidget);
    layout->addWidget(pushButton);
    connect(pushButton, &QPushButton::clicked, this, &MainWindow::on_pushButton_clicked);

    globalPosLabel = new QLabel("Global Pos: (Click Button)", centralWidget);
    layout->addWidget(globalPosLabel);

    // レイアウトを適用
    centralWidget->setLayout(layout);
}

MainWindow::~MainWindow()
{
}

void MainWindow::on_pushButton_clicked()
{
    // pushButtonのローカル座標 (0,0) をグローバル座標に変換
    // QWidget::mapToGlobal() は QPoint を引数に取り QPoint を返します。
    QPoint globalTopLeft = pushButton->mapToGlobal(QPoint(0, 0));

    // メインウィンドウのグローバル座標を取得
    QPoint mainWindowGlobalPos = this->mapToGlobal(QPoint(0, 0));

    // マウスクリック時のカーソル位置は既にグローバル座標
    QPoint cursorGlobalPos = QCursor::pos();

    qDebug() << "Button Global Top-Left: " << globalTopLeft;
    qDebug() << "MainWindow Global Top-Left: " << mainWindowGlobalPos;
    qDebug() << "Cursor Global Position: " << cursorGlobalPos;

    globalPosLabel->setText(QString("Button Global Pos: (%1, %2)").arg(globalTopLeft.x()).arg(globalTopLeft.y()));

    // 補足: QScreen を使ってスクリーン情報を取得することもできます
    QScreen *screen = QGuiApplication::screenAt(globalTopLeft); // ボタンがあるスクリーンを取得
    if (screen) {
        qDebug() << "Screen Geometry: " << screen->geometry();
        qDebug() << "Screen Available Geometry: " << screen->availableGeometry();
    }
}
// main.cpp
#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

解説

  • QScreen クラスを使用すると、マルチディスプレイ環境における各スクリーンの情報(サイズ、利用可能領域など)を取得できます。
  • QCursor::pos() は、現在のカーソル位置を既にグローバル座標で返します。
  • this->mapToGlobal(QPoint(0, 0)) は、MainWindow(トップレベルウィジェット)のクライアント領域の左上隅のグローバル座標を返します。
  • pushButton->mapToGlobal(QPoint(0, 0)) は、pushButton のクライアント領域の左上隅(ローカル座標 (0,0))のグローバル座標を返します。

例2:QQuickItem (QMLバックエンド) での使用例

C++ から QML の Item のグローバル座標を取得したい場合、QQuickViewQQmlApplicationEngine を介して QML のルートオブジェクトや特定の QQuickItem インスタンスにアクセスし、その mapToGlobal() メソッドを呼び出します。

この例はQMLとC++の連携が必要なため、QML側にidを付けてC++からアクセスできるようにし、QML側でmapToGlobal()を実行させるのが一般的です。C++側から直接QQuickItemのインスタンスを取得して呼び出すこともできます。

// main.cpp (QMLとC++の連携例)
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickItem>
#include <QDebug>
#include <QTimer>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    if (engine.rootObjects().isEmpty())
        return -1;

    // QMLのルートオブジェクトを取得
    QObject *rootObject = engine.rootObjects().first();

    // QMLのmyRectangle Itemにアクセス
    QQuickItem *myRectangle = rootObject->findChild<QQuickItem*>("myRectangle");

    if (myRectangle) {
        // UIが完全にロードされ、表示された後に座標を取得するためにQTimerを使用
        // これはQMLのComponent.onCompletedに相当する。
        QTimer::singleShot(500, myRectangle, [myRectangle]() {
            QPointF globalTopLeft = myRectangle->mapToGlobal(QPointF(0, 0));
            qDebug() << "C++から見た myRectangle のグローバル左上: " << globalTopLeft;

            QPointF globalCenter = myRectangle->mapToGlobal(QPointF(myRectangle->width() / 2, myRectangle->height() / 2));
            qDebug() << "C++から見た myRectangle のグローバル中心: " << globalCenter;
        });
    }

    return app.exec();
}
// qml.qrc に追加:
// <RCC>
//     <qresource prefix="/">
//         <file>main.qml</file>
//     </qresource>
// </RCC>

// main.qml (前述のQML例と同じ内容)
import QtQuick
import QtQuick.Window

Window {
    width: 640
    height: 480
    visible: true
    title: "mapToGlobal Example QML"

    Rectangle {
        id: myRectangle // C++からアクセスできるようにidを設定
        x: 100
        y: 80
        width: 150
        height: 100
        color: "lightblue"
        border.color: "blue"
        border.width: 2

        Text {
            anchors.centerIn: parent
            text: "Click Me!"
            color: "darkblue"
            font.bold: true
        }

        MouseArea {
            anchors.fill: parent
            onClicked: {
                var globalTopLeft = myRectangle.mapToGlobal(Qt.point(0, 0));
                console.log("QMLから見た myRectangle の左上 (0,0) のグローバル座標:");
                console.log("X:", globalTopLeft.x, "Y:", globalTopLeft.y);

                var clickedGlobalPos = myRectangle.mapToGlobal(Qt.point(mouse.x, mouse.y));
                console.log("QMLから見たクリックされた位置のグローバル座標:");
                console.log("X:", clickedGlobalPos.x, "Y:", clickedGlobalPos.y);
            }
        }
    }
}
  • QTimer::singleShot を使用しているのは、QML UIの初期化とレイアウト計算が完了する前にmapToGlobal()を呼び出すと、不正確な値が返される可能性があるためです。実際のアプリケーションでは、QMLからのシグナル受信後にC++で処理を行うなど、より堅牢な方法を使用することが推奨されます。
  • findChild<QQuickItem*>("myRectangle") を使って、QML で id: myRectangle と指定された Rectangle オブジェクトの C++ インスタンスを取得しています。
  • QML で定義された Item は、C++ の QQuickItem クラスのインスタンスとして扱われます。


厳密に「代替」と言えるメソッドは少ないですが、座標変換のロジックを手動で構築する、または異なる目的でグローバル座標にアクセスする方法を説明します。

親Itemの座標を積み上げてグローバル座標を計算する(手動計算)

これは mapToGlobal() が内部的に行っていることと似ていますが、自分で計算するアプローチです。主にデバッグ目的や、mapToGlobal() がなぜその値を返すのかを理解するのに役立ちます。

原理
あるItemのグローバル座標は、そのItemの親Itemのグローバル座標に、そのItem自身の親からの相対座標(x, y)を加算することで計算できます。これを最上位のウィンドウまで繰り返します。

QMLでの例

import QtQuick
import QtQuick.Window

Window {
    width: 640
    height: 480
    visible: true
    title: "Manual Global Position"

    Rectangle {
        id: parentRect
        x: 50
        y: 50
        width: 300
        height: 200
        color: "lightgreen"
        border.color: "green"
        border.width: 2

        Rectangle {
            id: childRect
            x: 20  // parentRectからの相対X
            y: 30  // parentRectからの相対Y
            width: 100
            height: 80
            color: "lightblue"
            border.color: "blue"
            border.width: 2

            Text {
                anchors.centerIn: parent
                text: "Click Me!"
                color: "darkblue"
                font.bold: true
            }

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    // mapToGlobal() を使用した正しいグローバル座標
                    var actualGlobalPos = childRect.mapToGlobal(Qt.point(0, 0));
                    console.log("--- mapToGlobal()による正確な値 ---");
                    console.log("childRectのグローバル左上: X:", actualGlobalPos.x, "Y:", actualGlobalPos.y);

                    // 手動計算によるグローバル座標
                    // childRectの親(parentRect)のWindowからの相対位置
                    var childXInParent = childRect.x;
                    var childYInParent = childRect.y;

                    // parentRectのWindowからの相対位置
                    var parentXInWindow = parentRect.x;
                    var parentYInWindow = parentRect.y;

                    // Windowのグローバル位置(トップレベルなのでWindow.x, Window.yがそのままグローバル)
                    var windowGlobalX = Window.x;
                    var windowGlobalY = Window.y;

                    // 合計してグローバル座標を計算
                    var manualGlobalX = windowGlobalX + parentXInWindow + childXInParent;
                    var manualGlobalY = windowGlobalY + parentYInWindow + childYInParent;

                    console.log("--- 手動計算による値 ---");
                    console.log("childRectのグローバル左上 (手動): X:", manualGlobalX, "Y:", manualGlobalY);

                    // クリック位置のグローバル座標を手動で計算
                    var clickedManualGlobalX = windowGlobalX + parentXInWindow + childXInParent + mouse.x;
                    var clickedManualGlobalY = windowGlobalY + parentYInWindow + childYInParent + mouse.y;
                    console.log("クリックされた位置のグローバル座標 (手動): X:", clickedManualGlobalX, "Y:", clickedManualGlobalY);
                }
            }
        }
    }
}

C++ での例 (QWidget の場合)

QWidget には pos() (親からの相対位置) と windowHandle()->x(), windowHandle()->y() (トップレベルウィンドウのグローバル位置) があるため、それらを組み合わせて計算できます。

// あるウィジェット (childWidget) のグローバル座標を計算したい場合
// childWidget は parentWidget の子であり、parentWidget はトップレベルの MainWindow の子とする

// 1. childWidgetの親からの相対位置
QPoint childPosInParent = childWidget->pos();

// 2. parentWidgetのMainWindowからの相対位置
QPoint parentPosInWindow = parentWidget->pos();

// 3. MainWindowのグローバル位置 (WindowHandle があれば)
// Qt 5.x 以降のQWindowまたはトップレベルQWidgetの場合
QPoint windowGlobalPos = this->mapToGlobal(QPoint(0,0)); // あるいは this->windowHandle()->position()
// QWidgetの場合は単に pos() がトップレベルでのグローバル位置を示すことがあるが、
// mapToGlobal(QPoint(0,0)) が最も信頼できる。

// 4. これらを合計
QPoint manualGlobalPos = windowGlobalPos + parentPosInWindow + childPosInParent;

qDebug() << "Manual Global Pos:" << manualGlobalPos;
qDebug() << "mapToGlobal Pos:" << childWidget->mapToGlobal(QPoint(0,0));

利点

  • 特定のデバッグシナリオで、座標計算のステップを追うことができる。
  • mapToGlobal() が内部的にどのように機能するかを理解するのに役立つ。
  • コードの複雑化
    ネストされた要素が多い場合、手動計算は非常に煩雑になる。
  • エラーの可能性
    スケーリング、DPI設定、フレームのサイズ、ウィンドウデコレーションなど、Qtが自動的に処理する複雑な要因を考慮に入れるのが非常に難しい。
  • 非推奨
    mapToGlobal() の方がはるかに安全で堅牢です。