Qt 開発者向け:mapTo() を使いこなすためのヒントとテクニック

2025-05-27

この関数には、主に以下の2つのオーバーロードがあります。

  1. QPointF QWidget::mapTo(const QWidget *other, const QPointF &pos) const:

    • このバージョンは、指定された点 pos を、この関数を呼び出したウィジェットの座標系から、引数 other で指定した別のウィジェットの座標系へと変換します。
    • othernullptr (または 0)の場合、pos はスクリーンのグローバル座標系へと変換されます。
  2. QPoint QWidget::mapTo(const QWidget *other, const QPoint &pos) const:

    • こちらは整数型の QPoint を引数に取り、変換後の点を QPoint 型で返します。基本的な機能は上記の QPointF 版と同じです。

具体的にどのような場合に使うのか?

例えば、以下のような状況で mapTo() 関数が役立ちます。

  • ウィンドウ間の連携: あるウィンドウ内の要素の位置に基づいて、別のウィンドウに何かを表示したり、処理を行ったりする場合。
  • イベント処理: マウスイベントが発生した位置を、親ウィジェットや別の関連するウィジェットの座標系で扱いたい場合。
  • カスタムペイント: あるウィジェット上に描画された要素の位置を、別のウィジェットに重ねて表示する際に、座標を一致させる。
  • ドラッグ&ドロップ処理: ドラッグ開始時のマウスカーソル位置を、ドロップ先のウィジェットの座標系に変換して、ドロップされた位置を正確に把握する。


#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
#include <QDebug>

class WidgetA : public QWidget {
public:
    WidgetA(QWidget *parent = nullptr) : QWidget(parent) {
        labelA = new QLabel("Widget A", this);
        labelA->move(20, 20);
        setGeometry(100, 100, 200, 100);
    }

protected:
    void mousePressEvent(QMouseEvent *event) override {
        // Widget A のローカル座標系でのクリック位置
        QPoint localPos = event->pos();
        qDebug() << "Widget A のローカル座標:" << localPos;

        // Widget B の座標系への変換
        if (widgetB) {
            QPointF mappedToB = mapTo(widgetB, QPointF(localPos));
            qDebug() << "Widget B の座標:" << mappedToB.toPoint();
        }

        // スクリーン座標系への変換
        QPointF mappedToScreen = mapTo(nullptr, QPointF(localPos));
        qDebug() << "スクリーン座標:" << mappedToScreen.toPoint();
    }

public:
    QLabel *labelA;
    QWidget *widgetB = nullptr;
};

class WidgetB : public QWidget {
public:
    WidgetB(QWidget *parent = nullptr) : QWidget(parent) {
        labelB = new QLabel("Widget B", this);
        labelB->move(30, 30);
        setGeometry(350, 150, 200, 100);
    }

private:
    QLabel *labelB;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    WidgetA wA;
    WidgetB wB;
    wA.widgetB = &wB; // Widget A が Widget B を参照できるようにする
    wA.show();
    wB.show();
    return a.exec();
}

この例では、WidgetA がクリックされたときに、そのクリック位置のローカル座標、WidgetB の座標系に変換した座標、そしてスクリーン座標系に変換した座標をコンソールに出力します。



変換先のウィジェットが nullptr である場合

  • トラブルシューティング
    • 変換先のウィジェットが有効なポインタであることを確認してください。
    • もしスクリーン座標への変換が意図的ならば問題ありませんが、特定のウィジェットの座標系への変換を期待している場合は、そのウィジェットのポインタが正しく取得できているか確認してください。
  • エラー
    変換先のウィジェット (other 引数) に nullptr を渡すと、mapTo() は呼び出し元のウィジェットのローカル座標系からスクリーン(グローバル)座標系への変換を試みます。意図せずスクリーン座標になってしまうことがあります。

変換元の点がウィジェットの範囲外である場合

  • トラブルシューティング
    • 変換元の点が、変換元のウィジェットの論理的な範囲内にあることを確認してください。例えば、マウスイベントの位置であれば、イベントが発生したウィジェットのジオメトリ内にあるはずです。
    • 必要であれば、変換前に点の位置を検証する処理を追加してください。
  • エラー
    変換元の点 (pos 引数) が、mapTo() を呼び出したウィジェットの範囲外にある場合でも、関数は座標変換を実行します。しかし、その結果の座標が意味のある位置を指しているとは限りません。

座標系の混同

  • トラブルシューティング
    • mapTo() を呼び出すウィジェットの現在の座標系を明確に理解してください。通常、ウィジェット内で扱われる座標はローカル座標です。
    • 変換先のウィジェットの座標系が何であるかを理解してください。
    • 必要に応じて、QWidget::mapFrom() 関数(あるウィジェットの座標系から別のウィジェットの座標系へ変換する逆の操作)や、QWidget::pos(), QWidget::geometry(), QWidget::mapToGlobal(), QWidget::mapFromGlobal() などの関連関数と組み合わせて、座標系の理解を深めてください。
  • エラー
    Qtにはローカル座標系、親ウィジェットの座標系、スクリーン座標系など、複数の座標系が存在します。どの座標系で点を扱っているのかを正しく理解していないと、mapTo() の結果を誤って解釈してしまうことがあります。

タイミングの問題

  • トラブルシューティング
    • 座標変換を行う処理が、関連するウィジェットのジオメトリが正しく設定された後に行われていることを確認してください。
    • レイアウトマネージャーを使用している場合は、レイアウトが完了した後のタイミングで座標変換を行うようにしてください(例えば、QWidget::showEvent() ハンドラ内など)。
  • エラー
    ウィジェットのジオメトリ(位置やサイズ)は動的に変化することがあります。mapTo() を呼び出すタイミングによっては、期待するジオメトリがまだ設定されていなかったり、既に変更されていたりする可能性があります。

浮動小数点数の扱い

  • トラブルシューティング
    • 精度が重要な場合は、QPointF のまま処理を行うことを検討してください。
    • QPoint に変換する場合は、必要に応じて適切な丸め処理(例えば、qRound(), qCeil(), qFloor() など)を明示的に行ってください。
  • エラー
    QPointF は浮動小数点数を扱うため、わずかな誤差が生じる可能性があります。mapTo() の結果を整数型の QPoint に変換する際に、意図しない丸め処理が行われることがあります。

親子関係の変化

  • トラブルシューティング
    • ウィジェットの親子関係が変更されるタイミングと、座標変換を行うタイミングを考慮してください。
    • 必要であれば、親子関係の変更後に再度座標変換を行うようにしてください。
  • エラー
    ウィジェットの親子関係が動的に変化すると、ローカル座標系の基準点が変わり、mapTo() の結果に影響を与える可能性があります。
  • 簡単なテストコードを作成して、特定の状況下での mapTo() の挙動を確認してみるのも有効です。
  • ウィジェットのジオメトリ (geometry(), pos(), size()) を出力して、座標系の基準点や範囲を確認してください。
  • qDebug() を使って、変換前後の座標の値を出力し、意図した変換が行われているか確認してください。


例1: あるウィジェット上のクリック位置を別のウィジェットの座標系に変換する

これは、前回の説明でも触れた基本的な例です。一方のウィジェットで発生したマウスイベントの位置を、別のウィジェット上で処理するために座標を変換します。

#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
#include <QDebug>

class SourceWidget : public QWidget {
public:
    SourceWidget(QWidget *parent = nullptr) : QWidget(parent) {
        label = new QLabel("クリックしてください", this);
        label->move(20, 20);
        setGeometry(100, 100, 200, 100);
    }

protected:
    void mousePressEvent(QMouseEvent *event) override {
        // SourceWidget のローカル座標系でのクリック位置
        QPointF localPos = event->pos();
        qDebug() << "SourceWidget のローカル座標:" << localPos;

        // TargetWidget の座標系への変換
        if (targetWidget) {
            QPointF mappedPos = mapTo(targetWidget, localPos);
            qDebug() << "TargetWidget の座標:" << mappedPos.toPoint();
            targetWidget->showClickPosition(mappedPos);
        }
    }

public:
    QLabel *label;
    QWidget *targetWidget = nullptr;
};

class TargetWidget : public QWidget {
public:
    TargetWidget(QWidget *parent = nullptr) : QWidget(parent) {
        positionLabel = new QLabel(this);
        positionLabel->move(20, 20);
        setGeometry(350, 150, 200, 100);
    }

    void showClickPosition(const QPointF &pos) {
        positionLabel->setText(QString("クリック位置 (Target): (%1, %2)").arg(pos.x()).arg(pos.y()));
    }

private:
    QLabel *positionLabel;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    SourceWidget source;
    TargetWidget target;
    source.targetWidget = &target;
    source.show();
    target.show();
    return a.exec();
}

この例では、SourceWidget がクリックされると、そのクリック位置が TargetWidget の座標系に変換され、TargetWidget 上のラベルに表示されます。



QWidget::mapFrom()

  • 使いどころ
    • 他のウィジェットやスクリーン上の点を、現在のウィジェットの内部で扱いたい場合に便利です。
    • 例えば、グローバルマウス座標を現在のウィジェットのローカル座標に変換して、ウィジェット内での相対的な位置を把握する、といった用途があります。
  • 機能
    mapTo() があるウィジェットの座標系から別のウィジェット(またはスクリーン)の座標系へ点を変換するのに対し、mapFrom() はその逆を行います。つまり、指定された点(別のウィジェットまたはスクリーンの座標系)を、この関数を呼び出したウィジェットのローカル座標系へと変換します。

<!-- end list -->

#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
#include <QDebug>

class WidgetA : public QWidget {
public:
    WidgetA(QWidget *parent = nullptr) : QWidget(parent) {
        labelA = new QLabel("Widget A", this);
        labelA->move(20, 20);
        setGeometry(100, 100, 200, 100);
    }

protected:
    void mousePressEvent(QMouseEvent *event) override {
        // マウスカーソルのグローバル座標を取得
        QPoint globalPos = event->globalPos();
        qDebug() << "グローバル座標:" << globalPos;

        // Widget A のローカル座標系への変換
        QPoint localPos = mapFromGlobal(globalPos);
        qDebug() << "Widget A のローカル座標 (mapFromGlobal):" << localPos;

        // Widget B の座標系から Widget A の座標系への変換
        if (widgetB) {
            QPointF pointInB(10, 10); // Widget B の座標系における点
            QPointF mappedToA = mapFrom(widgetB, pointInB);
            qDebug() << "Widget B の (10, 10) を Widget A に変換:" << mappedToA.toPoint();
        }
    }

public:
    QLabel *labelA;
    QWidget *widgetB = nullptr;
};

class WidgetB : public QWidget {
public:
    WidgetB(QWidget *parent = nullptr) : QWidget(parent) {
        setGeometry(350, 150, 150, 80);
    }
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    WidgetA wA;
    WidgetB wB;
    wA.widgetB = &wB;
    wA.show();
    wB.show();
    return a.exec();
}