QPointF mapFrom() 詳細解説: Qt 初心者向けガイド
もう少し詳しく見ていきましょう。
関数の定義
QPointF QWidget::mapFrom(const QWidget *other, const QPointF &pos) const
または
QPoint QWidget::mapFrom(const QWidget *other, const QPoint &pos) const
見ての通り、QPointF
版と QPoint
版の2つのオーバーロードがあります。どちらも基本的な機能は同じです。
引数
pos
: 変換したい座標です。other
ウィジェットの座標系における点を指定します。other
: 変換元の座標系を持つウィジェットへのポインタです。この引数がnullptr
の場合、座標はスクリーン(グローバル)座標系からウィジェットのローカル座標系へ変換されます。
戻り値
QPointF
またはQPoint
:pos
で指定された座標を、この関数を呼び出したウィジェットのローカル座標系に変換した結果の座標です。
「ローカル座標系」とは?
ウィジェットには、自身を基準としたローカルな座標系があります。この座標系の原点 (0, 0) は、ウィジェットの左上隅です。ウィジェット内のすべての描画や子ウィジェットの配置は、このローカル座標系に基づいて行われます。
「他の座標系」とは?
- 他の兄弟ウィジェットの座標系
同じ親を持つ兄弟ウィジェット同士の座標系を変換することも可能です。 - スクリーン(グローバル)座標系
これは、画面全体を基準とした座標系です。画面の左上隅が (0, 0) となります。mapFrom(nullptr, ...)
を使うと、スクリーン上の点を特定のウィジェットのローカル座標系に変換できます。 - 親ウィジェットの座標系
ウィジェットが別のウィジェットの子である場合、親ウィジェットにも自身のローカル座標系があります。mapFrom(parentWidget(), ...)
を使うと、子ウィジェット上の点を親ウィジェットの座標系に変換できます。
変換元のウィジェット (other) が nullptr である場合の間違い
- トラブルシューティング
other
に渡すウィジェットポインタが有効であることを確認してください。ポインタが初期化されているか、削除されていないかなどをチェックします。- スクリーン座標からの変換を意図している場合は、明示的に
nullptr
を渡すか、より意図が明確なmapFromGlobal()
関数を使用することを検討してください。
- エラー
other
にnullptr
を渡すと、スクリーン座標系からの変換を意図していると解釈されます。しかし、意図せずnullptr
が渡ってしまった場合、期待しない座標に変換される可能性があります。
変換元の座標 (pos) が誤った座標系である場合
- トラブルシューティング
pos
に渡す座標が、other
ウィジェットの座標系における正しい点であることを確認してください。- 座標がどのウィジェットのどの座標系に属しているのかを常に意識するようにしましょう。必要であれば、変換元の座標を取得する際に、適切な
mapTo...()
関数を使用して、目的の座標系に変換してからmapFrom()
に渡すことを検討してください。
- エラー
pos
に渡す座標が、other
ウィジェットのローカル座標系における点ではない場合、変換結果は意味のないものになります。例えば、widgetA
のローカル座標にある点を、widgetB
のmapFrom(widgetA, ...)
に渡してしまうといったケースです。
ウィジェットの親子関係が期待通りでない場合
- トラブルシューティング
parentWidget()
が意図した親ウィジェットを返しているか確認してください。ウィジェットの階層構造をデバッガなどで確認すると良いでしょう。- 親ウィジェットが設定されていない場合や、途中で変更されている場合に注意が必要です。
- エラー
mapFrom(parentWidget(), ...)
を使用する際に、ウィジェットの親子関係が想定と異なっていると、期待する座標変換が行われません。
ウィジェットのジオメトリ(位置やサイズ)が変更されたタイミングでの誤り
- トラブルシューティング
- 座標変換を行うタイミングが、関連するウィジェットのジオメトリが確定した後であることを確認してください。
- シグナルとスロットの仕組みを利用して、ジオメトリが変更された後に座標変換を行うようにすると、より安全です。
- エラー
ウィジェットの位置やサイズが動的に変更されるようなアニメーションやリサイズ処理の中でmapFrom()
を使用する場合、タイミングによっては期待しない変換結果になることがあります。
浮動小数点数の扱いに関する注意
- トラブルシューティング
- 必要に応じて、
qRound()
などの関数を使って浮動小数点数を整数に丸めてから使用することを検討してください。ただし、丸め処理による誤差も考慮する必要があります。
- 必要に応じて、
- エラー
QPointF
は浮動小数点数を扱うため、わずかな誤差が生じる可能性があります。整数座標を期待する処理でQPointF
の結果をそのまま使うと、意図しない動作を引き起こすことがあります。
可視性 (isVisible()) の影響
- トラブルシューティング
- 座標変換の対象となるウィジェットが可視状態にあるかどうかを意識して、その後の処理を行うようにしてください。
- 注意点
mapFrom()
自体がエラーを引き起こすわけではありませんが、不可視のウィジェットに関する座標変換は、視覚的に意味のない結果となる可能性があります。
- ステップ実行
デバッガでコードをステップ実行し、mapFrom()
の前後で変数の値がどのように変化するかを確認します。 - グラフィックデバッガの利用
Qt Creator に付属しているグラフィックデバッガを使用すると、ウィジェットの階層構造やジオメトリ、座標などを視覚的に確認でき、問題の特定に役立ちます。 - qDebug() の活用
変換前後の座標値をqDebug()
で出力して確認することで、何が起こっているのかを把握しやすくなります。
例1: マウスイベントにおける座標変換 (スクリーン座標からローカル座標へ)
この例では、ウィジェット上でマウスボタンが押されたときに、そのクリック位置のスクリーン座標をウィジェット自身のローカル座標に変換します。これは、マウスイベントが発生した位置をウィジェット内部で処理する際によく使われるパターンです。
#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <QMouseEvent>
#include <QDebug>
class MyWidget : public QWidget
{
public:
MyWidget(QWidget *parent = nullptr) : QWidget(parent)
{
setWindowTitle("mapFrom() の例 1");
setGeometry(100, 100, 300, 200);
}
protected:
void mousePressEvent(QMouseEvent *event) override
{
// マウスボタンが押されたスクリーン座標を取得
QPoint globalPos = event->globalPos();
qDebug() << "グローバル座標 (スクリーン):" << globalPos;
// スクリーン座標をこのウィジェットのローカル座標に変換
QPoint localPos = mapFromGlobal(globalPos); // mapFrom(nullptr, globalPos) と同じ
qDebug() << "ローカル座標 (MyWidget 内):" << localPos;
// ここでローカル座標を使った処理を行う (例: 図形の描画など)
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyWidget w;
w.show();
return a.exec();
}
このコードを実行し、表示されたウィンドウ内をクリックすると、コンソールにクリック位置のスクリーン座標と、MyWidget
のローカル座標系における対応する座標が出力されます。
例2: 異なるウィジェット間の座標変換 (子ウィジェットから親ウィジェットへ)
この例では、親ウィジェット (ParentWidget
) と子ウィジェット (ChildWidget
) があります。子ウィジェット上の特定の点を、親ウィジェットのローカル座標系に変換します。
#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <QLabel>
#include <QPointF>
#include <QDebug>
#include <QVBoxLayout>
class ChildWidget : public QLabel
{
public:
ChildWidget(const QString &text, QWidget *parent = nullptr) : QLabel(text, parent)
{
setStyleSheet("background-color: lightblue; border: 1px solid blue;");
setGeometry(50, 50, 100, 50);
}
QPointF getLocalCenter() const
{
return rect().center();
}
};
class ParentWidget : public QWidget
{
public:
ParentWidget(QWidget *parent = nullptr) : QWidget(parent)
{
setWindowTitle("mapFrom() の例 2");
setGeometry(50, 50, 200, 150);
setStyleSheet("background-color: lightgray;");
QVBoxLayout *layout = new QVBoxLayout(this);
child = new ChildWidget("子ウィジェット", this);
layout->addWidget(child);
}
private:
ChildWidget *child;
protected:
void mousePressEvent(QMouseEvent *event) override
{
// 子ウィジェットのローカル座標系における中心点を取得
QPointF childCenterLocal = child->getLocalCenter();
qDebug() << "子ウィジェットのローカル中心座標:" << childCenterLocal;
// 子ウィジェットのローカル座標を親ウィジェットのローカル座標に変換
QPointF parentCenterLocal = mapFrom(child, childCenterLocal);
qDebug() << "親ウィジェットにおける対応する座標:" << parentCenterLocal;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ParentWidget w;
w.show();
return a.exec();
}
このコードを実行し、ParentWidget
のウィンドウ内をクリックすると、ChildWidget
の中心点のローカル座標と、それが ParentWidget
のローカル座標系でどこに位置するかがコンソールに出力されます。
例3: 異なる兄弟ウィジェット間の座標変換
この例では、同じ親ウィジェットを持つ2つの兄弟ウィジェット (SiblingWidget1
と SiblingWidget2
) があります。SiblingWidget1
上の点を、SiblingWidget2
のローカル座標系に変換します。
#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <QLabel>
#include <QPointF>
#include <QDebug>
#include <QHBoxLayout>
class SiblingWidget : public QLabel
{
public:
SiblingWidget(const QString &text, QWidget *parent = nullptr, const QColor &color = Qt::white) : QLabel(text, parent)
{
setStyleSheet(QString("background-color: %1; border: 1px solid black;").arg(color.name()));
setAlignment(Qt::AlignCenter);
}
QPointF getLocalPoint(qreal x, qreal y) const
{
return QPointF(x, y);
}
};
class ContainerWidget : public QWidget
{
public:
ContainerWidget(QWidget *parent = nullptr) : QWidget(parent)
{
setWindowTitle("mapFrom() の例 3");
setGeometry(150, 150, 400, 100);
QHBoxLayout *layout = new QHBoxLayout(this);
sibling1 = new SiblingWidget("兄弟 1", this, Qt::yellow);
sibling1->setGeometry(20, 20, 150, 60);
sibling2 = new SiblingWidget("兄弟 2", this, Qt::green);
sibling2->setGeometry(230, 20, 150, 60);
layout->addWidget(sibling1);
layout->addWidget(sibling2);
}
private:
SiblingWidget *sibling1;
SiblingWidget *sibling2;
protected:
void mousePressEvent(QMouseEvent *event) override
{
// 兄弟 1 のローカル座標系における点を取得
QPointF sibling1LocalPoint = sibling1->getLocalPoint(50, 30);
qDebug() << "兄弟 1 のローカル座標:" << sibling1LocalPoint;
// 兄弟 1 のローカル座標を兄弟 2 のローカル座標系に変換
QPointF sibling2LocalPoint = mapFrom(sibling1, sibling1LocalPoint);
qDebug() << "兄弟 2 における対応する座標:" << sibling2LocalPoint;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ContainerWidget w;
w.show();
return a.exec();
}
このコードを実行し、ContainerWidget
のウィンドウ内をクリックすると、SiblingWidget1
の (50, 30) のローカル座標が、SiblingWidget2
のローカル座標系でどこに位置するかがコンソールに出力されます。
複数の mapTo...() 関数と組み合わせる方法
mapFrom()
の直接的な代替というわけではありませんが、出発点のウィジェットの座標を、目的のウィジェットの親ウィジェットの座標系、あるいはスクリーン座標系に一旦変換し、そこから目的のウィジェットのローカル座標系に間接的に変換する方法です。
- 例: 子ウィジェットの座標を親ウィジェットの孫ウィジェットのローカル座標に変換する
<!-- end list -->
QPointF childPosInParent = childWidget->mapToParent(childLocalPos);
QPointF grandchildPos = grandchildWidget->mapFromParent(childPosInParent);
この例では、mapFrom(childWidget, childLocalPos)
と同じ最終的な結果を得るために、mapToParent()
と mapFromParent()
を組み合わせています。
同様に、スクリーン座標を経由することも可能です。
QPoint globalPos = sourceWidget->mapToGlobal(sourceLocalPos.toPoint()); // QPoint に変換が必要な場合あり
QPointF destinationLocalPos = destinationWidget->mapFromGlobal(globalPos);
この方法は、座標変換の経路をより明示的に制御したい場合に有効です。
ジオメトリ関数 (geometry(), pos(), mapToGlobal()) を利用して自力で計算する方法
ウィジェットの位置やサイズに関する情報を取得し、それらを基に座標変換を自分自身で計算することも可能です。
- 例: あるウィジェット上の点を別のウィジェットのローカル座標系に変換する
QPointF sourceGlobalPos = sourceWidget->mapToGlobal(sourceLocalPos.toPoint());
QPoint destinationGlobalOrigin = destinationWidget->mapToGlobal(QPoint(0, 0));
QPointF destinationLocalPos = sourceGlobalPos - destinationGlobalOrigin;
この例では、出発点のグローバル座標と目的地のグローバル原点を計算し、その差を求めることで、mapFrom()
と同様の結果を得ています。
この方法は、より低レベルな制御が可能になりますが、ウィジェットの親子関係や位置関係が複雑になると計算も複雑になる可能性があります。
QTransform クラスを利用した座標変換
より複雑な2D変換(回転、スケール、せん断など)を行う必要がある場合は、QTransform
クラスを利用できます。QTransform
オブジェクトを作成し、必要な変換行列を設定した後、map()
関数を使って点を変換します。
QTransform transform;
// 例えば、移動変換を設定 (mapFrom() の基本的な機能に近い)
transform.translate(destinationWidget->x() - sourceWidget->x(), destinationWidget->y() - sourceWidget->y());
QPointF destinationLocalPos = transform.map(sourceLocalPos);
QTransform
はより強力なツールですが、単純な座標系変換にはややオーバースペックかもしれません。ただし、複数の変換を組み合わせる必要がある場合には非常に有効です。
イベント処理における座標の直接的な利用
特にマウスイベントなどのイベント処理においては、イベントオブジェクトが提供する座標 (event->pos()
, event->globalPos()
) を直接利用することが多く、必ずしも mapFrom()
を明示的に呼び出す必要がない場合があります。
例えば、ウィジェット内でクリックされたローカル座標は event->pos()
で直接取得できますし、スクリーン座標は event->globalPos()
で取得できます。これらの座標を必要に応じて mapTo...()
関数で別の座標系に変換することで、mapFrom()
の代替となる処理を行うことができます。
- イベント処理内で直接座標を利用できる場合
無理にmapFrom()
を使う必要はありません。 - 低レベルな制御が必要な場合
ジオメトリ関数を利用して自力で計算する方法を検討できますが、複雑になりやすい点に注意が必要です。 - 変換経路を明示的に制御したい場合
複数のmapTo...()
関数を組み合わせる方法が有効です。 - より複雑な座標変換が必要な場合
回転やスケールなどの変換を伴う場合はQTransform
が適しています。