Item.mapFromGlobal()
Item.mapFromGlobal()
とは?
Item.mapFromGlobal()
は、QtのGUIプログラミングにおいて、グローバル座標(スクリーン全体を基準とした座標)を、特定のItem(ウィジェットやQMLの要素など)のローカル座標(そのItem自身を基準とした座標)に変換するための関数です。
Qtの多くの要素は、それぞれ独自の座標系を持っています。例えば、親ウィジェットの中に子ウィジェットがある場合、子ウィジェットの(0,0)
は親ウィジェットの左上隅とは異なる位置になります。
このmapFromGlobal()
関数は、特に以下のような場面で役立ちます。
- 座標系の理解
デバッグや座標系の関係を理解する際に、異なる座標系間の変換を確認したい場合。 - 要素の配置
スクリーン上の特定の位置に要素を配置したいが、その要素が親要素の内部に配置される場合など、座標変換が必要な場合。 - マウスイベントの処理
グローバル座標で取得したマウスカーソルの位置(例:QCursor::pos()
)を、特定のウィジェットやQML要素の内部での相対的な位置に変換したい場合。これにより、その要素の境界内でのクリックやドラッグ操作を正確に検出できます。
使用方法
mapFromGlobal()
は、通常、以下のように使用します。
// C++ (QWidgetの場合)
QPoint globalPos = QCursor::pos(); // グローバル座標でマウスカーソルの位置を取得
QPoint localPos = myWidget->mapFromGlobal(globalPos); // myWidgetのローカル座標に変換
// QML (Itemの場合)
// QMLのJavaScript内では、特定のItemのインスタンスから呼び出す
Item {
id: myItem
width: 200
height: 100
MouseArea {
anchors.fill: parent
onClicked: {
// event.x, event.y はmyItemのローカル座標
console.log("Local click position:", event.x, event.y);
// グローバル座標をmyItemのローカル座標に変換する例
var globalMouseX = event.screenX; // グローバルなマウスX座標
var globalMouseY = event.screenY; // グローバルなマウスY座標
var localConvertedPos = myItem.mapFromGlobal(globalMouseX, globalMouseY);
console.log("Converted local position from global:", localConvertedPos.x, localConvertedPos.y);
}
}
}
- 多重モニターとDPIスケール
複数のモニター環境や異なるDPIスケールを持つ環境では、座標の変換に注意が必要です。Qtはこれらの要素を考慮して座標変換を行いますが、場合によっては予期せぬ結果になることもあります。特に、Windows APIなどで直接座標を取得する場合は、Qtの座標系との整合性を確認する必要があります。 - 逆の操作: mapToGlobal()
逆に、Itemのローカル座標をグローバル座標に変換したい場合は、mapToGlobal()
関数を使用します。 - 戻り値
戻り値は、そのItemのローカル座標系における点(QPoint
またはQPointF
)です。 - 引数
mapFromGlobal()
の引数には、変換したいグローバル座標の点(QPoint
またはQPointF
)を指定します。
誤ったItem/Widgetから呼び出している
よくある間違い
mapFromGlobal()
を呼び出すItem
またはQWidget
が、変換したいグローバル座標が関係する対象の要素と異なる場合。
例えば、子ウィジェットのマウスイベントでグローバル座標を得て、それを親ウィジェットのローカル座標に変換したいのに、子ウィジェット自身からmapFromGlobal()
を呼び出してしまうケース。
トラブルシューティング
- デバッグ出力で確認
変換元と変換先のItem
またはQWidget
のgeometry()
やpos()
、size()
などをデバッグ出力し、期待通りの値になっているか確認してください。 - どの要素のローカル座標が必要か明確にする
変換結果が必要なItem
またはQWidget
のインスタンスからmapFromGlobal()
を呼び出しているか確認してください。
// C++の例
// myWidgetが欲しいローカル座標を持つウィジェット
// QCursor::pos() はグローバル座標
QPoint globalCursorPos = QCursor::pos();
QPoint localPos = myWidget->mapFromGlobal(globalCursorPos); // 正しい使い方
// 誤った使い方(thisがmyWidgetの子ウィジェットの場合など)
// QPoint wrongLocalPos = this->mapFromGlobal(globalCursorPos);
レイアウトマネージャーの影響
よくある間違い
Qtのレイアウトマネージャー(QHBoxLayout
, QVBoxLayout
, QGridLayout
など)を使用しているウィジェットに対して、手動でsetGeometry()
やmove()
を呼び出し、その後にmapFromGlobal()
を使用すると、期待通りの座標が得られないことがあります。レイアウトがウィジェットの最終的な位置とサイズを決定するため、手動での設定が上書きされる可能性があります。
トラブルシューティング
- showEvent()やresizeEvent()での処理
ウィジェットの表示後やサイズ変更後に正確な座標が必要な場合は、showEvent()
やresizeEvent()
内でmapFromGlobal()
を呼び出すと、レイアウトが適用された後の正確な座標が得られる可能性が高まります。 - レイアウトと手動でのジオメトリ管理を混同しない
レイアウトを使用している場合は、手動でウィジェットの位置やサイズを設定するのではなく、QSpacerItem
やstretch
、setAlignment()
などを利用してレイアウトに任せるのが原則です。
ビューポート(Viewport)の考慮(QAbstractScrollArea派生クラス)
よくある間違い
QAbstractScrollArea
(QGraphicsView
, QTableView
, QTreeView
, QTextEdit
など)を継承したウィジェットの場合、スクロール可能な内容を表示するための「ビューポート」と呼ばれる内部ウィジェットを持っています。これらのウィジェットで直接mapFromGlobal()
を使うと、スクロールによって表示領域がずれている場合に、期待通りのローカル座標が得られないことがあります。マウスイベントなどはビューポートのローカル座標で発生するため、注意が必要です。
トラブルシューティング
- ビューポートに対してmapFromGlobal()を呼び出す
QAbstractScrollArea
派生クラスの場合、そのウィジェットのviewport()
メソッドでビューポートを取得し、そのビューポートに対してmapFromGlobal()
を呼び出すのが正しい方法です。
// QTableWidget (QAbstractScrollAreaを継承) の場合
QTableWidget* myTable = ...;
QPoint globalPos = QCursor::pos();
// 直接ウィジェットに呼び出すと、スクロール位置を考慮しない場合がある
// QPoint localPosWrong = myTable->mapFromGlobal(globalPos);
// 正しい方法:ビューポートに対して呼び出す
QPoint localPosCorrect = myTable->viewport()->mapFromGlobal(globalPos);
ウィジェットがまだ表示されていない、またはレイアウトが適用されていない
よくある間違い
ウィジェットがまだ画面に表示されていない(show()
が呼ばれていない)、またはレイアウトが完全に計算・適用される前にmapFromGlobal()
を呼び出すと、正しい座標が得られないことがあります。ウィジェットのgeometry()
がまだ初期値のままだったり、親からの相対位置が確定していなかったりするためです。
トラブルシューティング
- 非同期処理の考慮
複雑なUIでは、QTimer::singleShot()
などを使って、わずかな時間後に座標計算をディレイさせることで、UIの準備が整うのを待つこともできます。 - QApplication::processEvents()を使う(推奨されないことが多い)
レイアウトの計算を強制的に行うためにQApplication::processEvents()
を呼び出す方法もありますが、これはUIのフリーズやデッドロックの原因になる可能性があるため、特別な理由がない限り避けるべきです。 - showEvent()内で処理する
ウィジェットが初めて表示される際に座標が必要な場合は、そのウィジェットのshowEvent()
をオーバーライドして、その中でmapFromGlobal()
を呼び出すようにします。
よくある間違い
複数のモニターを使用している環境や、高DPI(Retinaディスプレイなど)環境で、OSレベルの座標系とQtの座標系との整合性が取れていない場合。Qtは通常DPIスケールを自動的に処理しますが、OSのAPIとQtのAPIを混在させている場合などに問題が発生することがあります。
- Qtのバージョン
古いQtのバージョンではDPI対応が不十分な場合があるため、最新のQtバージョンを使用することも検討してください。 - テスト環境の確認
開発環境と異なるDPI設定やマルチモニター環境でテストを行い、期待通りの動作をするか確認します。 - QtのAPIを統一的に使用する
グローバル座標を取得する際も、QtのQCursor::pos()
やQMouseEvent::globalPos()
を使用し、OS固有のAPIを可能な限り避けるようにします。
QMLでの Item.mapFromGlobal() 例
QMLでは、UI要素はすべてItem
型またはその派生型です。マウスイベントなどで得られるグローバル座標を、特定のItem
のローカル座標に変換する際にmapFromGlobal()
がよく使われます。
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 600
height: 400
visible: true
title: "Item.mapFromGlobal() Example"
// 親要素となるRectangle
Rectangle {
id: parentRect
x: 50
y: 50
width: 300
height: 200
color: "lightgray"
border.color: "blue"
border.width: 2
Text {
anchors.centerIn: parent
text: "Parent Rectangle"
font.pixelSize: 20
}
// 子要素となるRectangle
Rectangle {
id: childRect
x: 50
y: 50
width: 150
height: 100
color: "lightblue"
border.color: "green"
border.width: 2
Text {
anchors.centerIn: parent
text: "Child Rectangle"
font.pixelSize: 16
}
// この子Rectangle内でのマウスイベントを処理
MouseArea {
anchors.fill: parent
onClicked: {
// グローバル座標でのクリック位置
var globalX = mouse.screenX;
var globalY = mouse.screenY;
// childRectのローカル座標に変換
var localPos = childRect.mapFromGlobal(globalX, globalY);
console.log("---------------------------------------");
console.log("Clicked on Child Rectangle:");
console.log("Global position (screenX, screenY):", globalX, ",", globalY);
console.log("ChildRect's local position (x, y):", mouse.x, ",", mouse.y); // mouse.x/y はイベント発生元のローカル座標
console.log("Converted local position using mapFromGlobal():", localPos.x, ",", localPos.y);
// mapToGlobal() の逆変換も確認
var globalPosConvertedBack = childRect.mapToGlobal(localPos.x, localPos.y);
console.log("Converted back to global using mapToGlobal():", globalPosConvertedBack.x, ",", globalPosConvertedBack.y);
console.log("---------------------------------------");
}
}
}
}
}
解説
parentRect
とchildRect
という2つのRectangle
があります。childRect
はparentRect
の子要素です。childRect
内にMouseArea
が配置されており、クリックイベントを捕捉します。onClicked
シグナルハンドラ内で、クリックイベントのグローバル座標 (mouse.screenX
,mouse.screenY
) を取得します。childRect.mapFromGlobal(globalX, globalY)
を呼び出すことで、取得したグローバル座標がchildRect
の内部のどこに位置するか を計算し、localPos
に格納します。mouse.x
とmouse.y
は、クリックイベントが発生したMouseArea
(この場合はchildRect
と同じサイズ)のローカル座標なので、mapFromGlobal()
で変換した結果と一致することが期待されます。mapToGlobal()
で逆変換を行い、結果が一致するかどうかも確認しています。
このコードを実行し、childRect
の異なる位置をクリックすると、コンソール出力で座標の変化を確認できます。
C++のQWidget
を使ったアプリケーションでも同様にmapFromGlobal()
を使用します。主に、マウスイベントやコンテキストメニューの表示位置の決定などで利用されます。
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include <QMouseEvent>
#include <QDebug>
#include <QApplication> // QCursor::pos() に必要
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event) override;
};
#endif // MYWIDGET_H
mywidget.cpp
#include "mywidget.h"
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
{
setWindowTitle("QWidget::mapFromGlobal() Example");
setGeometry(100, 100, 400, 300); // ウィジェットの初期位置とサイズ
setStyleSheet("background-color: lightblue; border: 2px solid green;");
}
void MyWidget::mousePressEvent(QMouseEvent *event)
{
// グローバル座標でのクリック位置
QPoint globalPos = event->globalPos(); // QMouseEventからグローバル位置を取得
// または QCursor::pos(); // 現在のマウスカーソルのグローバル位置
// このウィジェット(MyWidget)のローカル座標に変換
QPoint localPos = mapFromGlobal(globalPos);
qDebug() << "---------------------------------------";
qDebug() << "Clicked on MyWidget:";
qDebug() << "Global position:" << globalPos;
qDebug() << "MyWidget's local position (from event):" << event->pos(); // event->pos() はイベント発生元のローカル座標
qDebug() << "Converted local position using mapFromGlobal():" << localPos;
// mapToGlobal() の逆変換も確認
QPoint globalPosConvertedBack = mapToGlobal(localPos);
qDebug() << "Converted back to global using mapToGlobal():" << globalPosConvertedBack;
qDebug() << "---------------------------------------";
// 親クラスのイベントハンドラを呼び出す (通常は必要)
QWidget::mousePressEvent(event);
}
main.cpp
#include <QApplication>
#include "mywidget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyWidget w;
w.show();
return a.exec();
}
MyWidget
クラスはQWidget
を継承しています。mousePressEvent
をオーバーライドし、ウィジェットがクリックされたときに呼び出されるようにします。- イベントオブジェクト
event
から、クリックされた位置のグローバル座標event->globalPos()
を取得します。 mapFromGlobal(globalPos)
を呼び出すことで、取得したグローバル座標がMyWidget
の内部のどこに位置するか を計算し、localPos
に格納します。event->pos()
はクリックイベントが発生したMyWidget
のローカル座標なので、mapFromGlobal()
で変換した結果と一致することが期待されます。mapToGlobal()
で逆変換を行い、結果が一致するかどうかも確認しています。
Qtにおける Item.mapFromGlobal()
(QML) や QWidget::mapFromGlobal()
(C++) は、グローバル座標を特定の要素のローカル座標に変換するための最も直接的で推奨される方法です。しかし、状況によっては、あるいは特定の目的のために、代替のアプローチを検討する必要がある場合があります。
代替方法の考え方
mapFromGlobal()
の主な目的は、ある要素の座標系から別の要素の座標系への変換です。代替方法を考える際には、以下の点に注目します。
- 座標計算を自力で行う
各要素のx/y
(QML)やpos()
(C++)、width/height
(QML)やsize()
(C++)といったジオメトリ情報を使用して、手動でオフセットを計算する。 - イベントオブジェクトのローカル座標を利用する
マウスイベントなど、特定の要素のローカル座標をすでに持っている場合に、それを直接利用する。 - より上位の共通座標系を利用する
例えば、ウィンドウのローカル座標系を一時的な共通座標系として利用する。
各要素のジオメトリ情報を使った手動計算
これはmapFromGlobal()
がない場合に、最も基本的な代替となる方法です。親要素と子要素のx
, y
座標とサイズを使って、オフセットを計算します。
概念
グローバル座標 (GX,GY) を、特定の子要素 (TargetX,TargetY,TargetWidth,TargetHeight) のローカル座標 (LX,LY) に変換するには、グローバルな位置から親要素のオフセット、そしてターゲット要素自身のオフセットを順に引いていく必要があります。
LX=GX−TargetGlobalX LY=GY−TargetGlobalY
ここで、TargetGlobalX
と TargetGlobalY
はターゲット要素の左上隅のグローバル座標です。これは、ウィンドウの左上からのオフセットと、親要素からのオフセットをすべて足し合わせることで計算できます。
QMLでの例
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 600
height: 400
visible: true
title: "Manual Coordinate Conversion Example"
Rectangle {
id: parentRect
x: 50; y: 50; width: 300; height: 200
color: "lightgray"
border.color: "blue"
Rectangle {
id: childRect
x: 50; y: 50; width: 150; height: 100
color: "lightblue"
border.color: "green"
MouseArea {
anchors.fill: parent
onClicked: {
var globalX = mouse.screenX;
var globalY = mouse.screenY;
// Manual calculation
// childRectのグローバルX座標 = parentRectのグローバルX + childRectのparentRectからのX
// QMLではItem.x/y は親に対する相対座標
var childGlobalX = parentRect.x + childRect.x + window.x; // window.x/y はスクリーンに対するWindowの位置
var childGlobalY = parentRect.y + childRect.y + window.y;
var localX = globalX - childGlobalX;
var localY = globalY - childGlobalY;
console.log("---------------------------------------");
console.log("Clicked on Child Rectangle (Manual Calculation):");
console.log("Global position (screenX, screenY):", globalX, ",", globalY);
console.log("Calculated local position:", localX, ",", localY);
console.log("Using mapFromGlobal():", childRect.mapFromGlobal(globalX, globalY).x, ",", childRect.mapFromGlobal(globalX, globalY).y);
console.log("---------------------------------------");
}
}
}
}
}
C++ (QWidget) での例
// QWidget::pos() は親に対する相対座標
// QWidget::mapToGlobal(QPoint(0,0)) がそのウィジェットの左上隅のグローバル座標
QPoint globalCursorPos = QCursor::pos(); // グローバルカーソル位置
// this (MyWidget) のローカル座標に変換したいと仮定
// this ウィジェットのグローバル位置を取得
QPoint widgetGlobalPos = mapToGlobal(QPoint(0,0)); // このウィジェットの左上隅のグローバル座標
int localX = globalCursorPos.x() - widgetGlobalPos.x();
int localY = globalCursorPos.y() - widgetGlobalPos.y();
qDebug() << "Calculated local pos:" << QPoint(localX, localY);
qDebug() << "Using mapFromGlobal():" << mapFromGlobal(globalCursorPos);
利点
- 非常に単純な階層構造の場合に、概念的に理解しやすい。
mapFromGlobal()
の内部ロジックをより深く理解できる。
欠点
- DPIスケールやマルチスクリーン環境での複雑性
これらの環境では、手動計算が非常に難しくなり、Qtの提供する関数の方がはるかに信頼性が高いです。 - スクロールエリアの考慮不足
QAbstractScrollArea
の派生クラス(QGraphicsView
など)では、スクロール位置を考慮する必要があり、手動計算がさらに複雑になります。 - レイアウトマネージャーとの不整合
レイアウトマネージャーがウィジェットの位置とサイズを動的に決定する場合、手動で計算したジオメトリが古くなる可能性があります。 - 非常に煩雑
階層が深くなるほど、+x
,+y
の計算が複雑になり、エラーを起こしやすくなります。
イベントオブジェクトのローカル座標を直接利用する
最も一般的なシナリオの一つは、マウスイベントやドラッグ&ドロップイベントなどの「入力イベント」における座標変換です。これらのイベントオブジェクトは、通常、イベントが発生した自身のソース(Item
またはQWidget
)に対するローカル座標をすでに持っています。
QMLでの例
MouseArea
の onClicked
や onPressed
シグナルハンドラ内で提供される mouse
オブジェクトには、すでにローカル座標 (mouse.x
, mouse.y
) が含まれています。
MouseArea {
id: myMouseArea
// ...
onClicked: {
// mouse.x と mouse.y は、myMouseAreaに対するローカル座標
console.log("Local click position:", mouse.x, ",", mouse.y);
// この場合、mapFromGlobal(mouse.screenX, mouse.screenY) の結果とほぼ同じになります。
// (QMLではMouseAreaがItemの子要素であることが多いため)
var convertedFromGlobal = myMouseArea.mapFromGlobal(mouse.screenX, mouse.screenY);
console.log("Converted from global using mapFromGlobal():", convertedFromGlobal.x, ",", convertedFromGlobal.y);
}
}
C++ (QWidget) での例
QMouseEvent
の pos()
メソッドは、イベントを受け取ったウィジェットのローカル座標を返します。
void MyWidget::mousePressEvent(QMouseEvent *event)
{
// event->pos() は、MyWidgetのローカル座標
QPoint localPos = event->pos();
qDebug() << "Local click position (from event):" << localPos;
// event->globalPos() は、スクリーン全体のグローバル座標
QPoint globalPos = event->globalPos();
// mapFromGlobal() と比較
QPoint mappedPos = mapFromGlobal(globalPos);
qDebug() << "Converted from global using mapFromGlobal():" << mappedPos;
}
利点
- 多くの入力イベントシナリオでは、
mapFromGlobal()
を明示的に呼び出す必要がない。 - 最も直接的で、コードが簡潔。
欠点
- イベントが特定の要素に対して発生した場合にしか使えない。任意のグローバル座標を変換したい場合には不向き。
複数の要素のローカル座標を比較したいが、直接mapFromGlobal()
やmapToItem()
/mapToWidget()
を使うのが複雑な場合、一時的に共通の親(通常は最上位のウィンドウ)の座標系に変換してから、再度目的のローカル座標系に変換するというアプローチも考えられます。
概念
- 要素Aのローカル座標 → ウィンドウのローカル座標
- 要素Bのローカル座標 → ウィンドウのローカル座標
- ウィンドウのローカル座標での計算や比較
- 結果のウィンドウローカル座標 → 要素Cのローカル座標
これは、mapFromGlobal()
の直接的な代替というよりは、mapFromItem()
/ mapToItem()
(QML) や mapFrom()
/ mapTo()
(C++) の使用例に近いです。これらの関数は、異なるウィジェット/アイテム間の座標変換を行います。
QMLでの例 (Item.mapToItem / Item.mapFromItem)
// itemAのローカル座標をitemBのローカル座標に変換したい場合
var itemALocalPoint = Qt.point(10, 10);
var itemBLocalPoint = itemB.mapFromItem(itemA, itemALocalPoint.x, itemALocalPoint.y);
C++ (QWidget) での例 (QWidget::mapTo / QWidget::mapFrom)
// QWidgetAのローカル座標をQWidgetBのローカル座標に変換したい場合
QPoint widgetALocalPoint(10, 10);
QPoint widgetBLocalPoint = widgetB->mapFrom(widgetA, widgetALocalPoint);
利点
mapFromGlobal()
の代わりに、より限定されたスコープでの変換を行う場合に適している。- 特定の要素間の座標変換が明確になる。
欠点
- グローバル座標が最初に入力として与えられるシナリオでは、回り道になる。
これらの代替方法は概念的な理解や特定のニッチなケースで役立つかもしれませんが、ほとんどのユースケースにおいて、Item.mapFromGlobal()
(QML) および QWidget::mapFromGlobal()
(C++) が、グローバル座標から特定の要素のローカル座標への変換に最も適切で堅牢な方法です。
Qtのフレームワークは、これらの座標変換関数を、ウィジェットの階層、レイアウト、スクロール、DPIスケール、マルチスクリーン環境などをすべて考慮して最適化しています。手動でこれらの複雑性を管理しようとすると、バグの温床となり、コードの可読性と保守性が著しく低下します。