QGraphicsView图形视图框架使用(三)位移变换和图元定位
文章目錄
- 位移變換
- 圖片渲染
- 圖元定位
- 場景聚焦
當視圖框架中的圖元比較多且位置比較散亂的時候,為了操作某個特定的圖元,我們需要對圖元進行位移變換和定位,而從更加方便的操作管理圖元。這里就介紹一下圖元的位移變換和定位。
位移變換
在圖形視圖框架中我們可以通過QTransform和setTransform(const QTransform &matrix, bool combine = false),對來圖元進行位移變換,包括移動、旋轉、縮放等等。有的時候,我們在變換一些圖形的時候,希望視圖中一部分保持不變。這時候,我們就需要讓某些圖元忽略整個視圖的位移變換。下面介紹一下忽略位移變化的實現方式:
繪制固定線寬的直線
我們可以通過 QPen::setCosmetic(true)開啟固定線寬功能,這樣繪制出來的直線在位移變化的過程中就可以保持固定寬度了。參考示例如下:
顯示效果如下:
繪制位置保持不變的文字
這里以一個兩端是圓形的啞鈴狀圖元為例進行說明,在默認狀態下所有視圖中的圖元都會受位移變換的影響,對應的示例如下:
顯示效果如下:
為了查看方便,我們并不希望圖形中的文字跟隨著視圖的位移變化發生變化,我們可以通過下面的配置讓圖形中的文字保持不變。
顯示效果如下:
雖然圖形中的文字沒有發生位移變化,但也出現了一個非常詭異的問題。那就是文字不再顯示在圖形中央了。這是因為setPos()使用的是父對象的坐標系,而boundingRect()使用的是當前對象的坐標系,兩個坐標系本來是重合的,但是設置了對應屬性并發生位移變化之后,兩個坐標系不再重合才引起了該問題。
解決方案一
ItemIgnoresTransformations不會影響Item自身的位移變化。我們可以通過下面的位移變化方式來移動圖元,也就是用setTransform()替代setPos()。setTransform()依據自身的坐標系作為基準進行移動,不會受父對象的坐標系變化影響。
顯示效果如下:
解決方案二
方案二是通過添加一個輔助的矩形圖元來進行定位的。矩形圖元和文本圖元的boundingRect()保持一致。然后對應的文本內容只要顯示在輔助定位的圖元左上角就可以了。其實方案二和方案一的本質相同,都是不再直接使用父對象圖元的坐標系,改用自身坐標系了。
顯示效果如下:
輔助矩形圖元,在實際使用的時候是不需要顯示的,我們可以過下面的配置隱藏矩形外邊框的顯示。
圖片渲染
在開發一些類似于2D游戲的應用的時候,我們有時候需要將整個Scene場景內容進行圖片保存。QGraphicsView實現圖片保存還是比較簡單的,只需要將真個場景的內容渲染到圖片容器中即可,對應的實現如下:
QGraphicsView view(&scene); view.show(); QRect rect = scene.sceneRect().toAlignedRect(); QImage image(rect.size(), QImage::Format_ARGB32); //背景設置為透明 image.fill(Qt::transparent); //將渲染的繪圖保存到本地 QPainter painter(&image); scene.render(&painter); image.save("picture.png");圖元定位
在QGraphicsView應用中,我們有時候需要通過圖元的坐標來對圖元進行定位。圖元視圖中也提供了對應的接口,如果我們只想要最頂層的圖元可以調用itemAt()接口,對應的實現如下:
//如果存在返回對象,沒有的話返回空 //@1坐標點 //@2View位移變化矩陣,如果圖形中包含忽略transform的圖元的時候,需要提供該矩陣。 QGraphicsItem *QGraphicsScene::itemAt(const QPointF &position, const QTransform &deviceTransform) const很多時候視圖框架中位于某個坐標點的圖元,可能有很多,如果我們需要所有圖元的話,那么就得用到items()接口了,QGraphicsScene中存在很多同名的重載函數,可以根據自己的需要選擇使用,對應的接口定義如下:
//return: 返回圖元對象的列表 //@1:坐標點 //@2:選擇模式 //@3:排序方式 Qt::DescendingOrder降序排列 最上層的圖元顯示在列表的首位 //Qt::AscendingOrder增序排列,排列方式相反,最上層的圖元顯示在列表的末尾 //@4View位移變化矩陣,如果圖形中包含忽略transform的圖元的時候,需要提供該矩陣。 QList<QGraphicsItem *> QGraphicsScene::items(const QPointF &pos, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape, Qt::SortOrder order = Qt::DescendingOrder, const QTransform &deviceTransform = QTransform()) constitems()參數2的選擇模式有四種,如下表所示:
| Qt::ContainsItemBoundingRect | 被選擇的圖元的外邊界矩形,必須完全在選擇范圍內,才會被選中 |
| Qt::IntersectsItemBoundingRect | 與選擇范圍有交集,但并不完全包含的圖元,也會被選中。 |
| Qt::ContainsItemShape | 被選擇的圖元的外邊界矩形,必須完全在選擇范圍內,才會被選中。此種模式下,邊界計算更加精確,但是也更耗費計算能力。 |
| Qt::IntersectsItemShape | 與Qt::IntersectsItemBoundingRect類似,但是邊界計算精度更高。 |
場景聚焦
在一些特殊的場景下,我們需要顯示Scene的一部分區域,或者某個特定的圖元,這時候視圖View的焦點就聚焦到了Scene的一部分。我們可以通過控制滾動條來實現對應的功能,但是控制精度有限。圖形視圖框架提供了對應的接口,通過對應的接口我們可以很輕松的實現場景聚焦。對應的接口如下。
centerOn()可以通過滾動View中場景,使某個坐標或者某個Item顯示在視圖中央。如果某個Item離邊界特別近,或者在邊界之外,centerOn()只會讓該元素可見,并不會將其移動到視圖中央。
//聚焦到某個坐標點 QGraphicsView::centerOn(const QPointF &pos) //聚焦到某個Item QGraphicsView::centerOn(const QGraphicsItem *item) //注意:點坐標是float類型,而scrollbar步長是int類型的,所以center是一個近似值ensureVisible()功能和centerOn()功能類似,通過滾動View中的內容使某個Rect或者Item可見,同時還可以指定該范圍與邊界的最小margin。如果聚焦的范圍超出了邊界,那么View視圖會定位到距離這個范圍最近的位置。
//讓某個矩形范圍可見 QGraphicsView::ensureVisible(const QRectF &rect, int xmargin = 50, int ymargin = 50) //讓某個Item可見 QGraphicsView::ensureVisible(const QGraphicsItem *item, int xmargin = 50, int ymargin = 50)fitInView()功能是通過對視圖View進行縮放和移動,讓某個固定范圍或者Item,完全顯示在View當中,使用fitInView()的時候要確保指定的范圍必須包含在Scene中。fitInView()無法保證指定的范圍完整的顯示在View中。
通常在實現resizeEvent()的時候調用fitInView()來確保視圖View尺寸變化的時候,對應的場景內容能自動的填充滿對應的的范圍。但有一點需要注意,如果在位移變換的過程中啟動了滾動條的自動狀態,在ResizeEvent()調用fitInView(),會出現遞歸調用,從而引發崩潰。可以將滾動條狀態設置為Always On 或者 Always Off來避免該問題。
//聚焦某個范圍 QGraphicsView::fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) //聚焦某個控件 QGraphicsView::fitInView(const QGraphicsItem *item, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio)下面以一個例子來說明一下幾種場景聚焦的接口的用法,例子中通過按鍵事件來聚焦不同的圖元:
//graphicsview.h #ifndef GRAPHICSVIEW_H #define GRAPHICSVIEW_H#include <QWidget> #include <QGraphicsScene>namespace Ui { class GraphicsView; }class GraphicsView : public QWidget {Q_OBJECTpublic:explicit GraphicsView(QWidget *parent = 0);~GraphicsView(); public:bool eventFilter(QObject* watched, QEvent* event) override;private://場景QGraphicsScene m_scene;//矩形圖元QGraphicsRectItem* m_rect_item;//圖片圖元QGraphicsPixmapItem* m_pixmap_item;//橢圓圖元QGraphicsEllipseItem* m_ellipse_item;Ui::GraphicsView *ui; };#endif // GRAPHICSVIEW_H //graphicsview.cpp #include "graphicsview.h" #include "ui_graphicsview.h" #include <QGraphicsItem> #include <QKeyEvent> #include <QDebug>GraphicsView::GraphicsView(QWidget *parent) :QWidget(parent),ui(new Ui::GraphicsView) {ui->setupUi(this);this->setWindowTitle("GraphicsView");//在Scene中心添加一個十字坐標m_scene.addLine(-1000, 0, 1000, 0);m_scene.addLine(0, -1000, 0, 1000);//添加矩形圖元m_rect_item = m_scene.addRect(-50,-50,150,150);m_rect_item->setBrush(QBrush(QColor("#4D9CF8")));m_rect_item->setFlag(QGraphicsItem::ItemIsSelectable,true);m_rect_item->setFlag(QGraphicsItem::ItemIsMovable,true);//添加橢圓圖元m_ellipse_item = m_scene.addEllipse(QRectF(QPointF(150,100),QPointF(250,50)),QPen("#0000FF"),QBrush(QColor("#4D00F8")));m_ellipse_item->setZValue(10);m_ellipse_item->setFlag(QGraphicsItem::ItemIsSelectable,true);m_ellipse_item->setFlag(QGraphicsItem::ItemIsMovable,true);//繪制圖片m_pixmap_item = m_scene.addPixmap(QPixmap(":/demo.png"));m_pixmap_item->setPos(-300,-50);m_pixmap_item->setZValue(20);ui->graphicsView->setScene(&m_scene);m_ellipse_item->setFlag(QGraphicsItem::ItemIsSelectable,true);m_ellipse_item->setFlag(QGraphicsItem::ItemIsMovable,true);//抗鋸齒效果ui->graphicsView->setRenderHint(QPainter::Antialiasing);installEventFilter(this);//添加fillModeui->fit_mode_combo->addItems(QStringList() << "IgnoreAspectRation" << "KeepAspectRatio" << "KeepAspectRationByExpanding");ui->fit_mode_combo->setItemData(0,Qt::IgnoreAspectRatio);ui->fit_mode_combo->setItemData(1,Qt::KeepAspectRatio);ui->fit_mode_combo->setItemData(2,Qt::KeepAspectRatioByExpanding);ui->fit_mode_combo->setFocusPolicy(Qt:: NoFocus);}GraphicsView::~GraphicsView() {delete ui; }bool GraphicsView::eventFilter(QObject *watched, QEvent *event) {if (event->type() == QEvent::KeyPress){QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);Qt::KeyboardModifiers modifier = keyEvent->modifiers();int change_mode = ui->fit_mode_combo->currentData().toInt();qDebug() << change_mode;//center()聚焦圖元if(keyEvent->key() == Qt::Key_R && modifier == Qt::NoModifier){ui->graphicsView->centerOn(m_rect_item);}else if(keyEvent->key() == Qt::Key_P && modifier == Qt::NoModifier){ui->graphicsView->centerOn(m_pixmap_item);}else if(keyEvent->key() == Qt::Key_E && modifier == Qt::NoModifier){ui->graphicsView->centerOn(m_ellipse_item);}//ensureVisible()聚焦圖元else if(keyEvent->key() == Qt::Key_R && modifier == Qt::ControlModifier){ui->graphicsView->ensureVisible(m_rect_item,50,50);}else if(keyEvent->key() == Qt::Key_P && modifier == Qt::ControlModifier){ui->graphicsView->ensureVisible(m_pixmap_item,100,100);}else if(keyEvent->key() == Qt::Key_E && modifier == Qt::ControlModifier){ui->graphicsView->ensureVisible(m_ellipse_item,20,20);}//fitInView()聚焦圖元else if(keyEvent->key() == Qt::Key_S && modifier == Qt::NoModifier){ui->graphicsView->fitInView(m_ellipse_item,Qt::AspectRatioMode(change_mode));}}return QObject::eventFilter(watched, event); }使用CenterOn聚焦圖元的效果如下所示:
使用ensureVisible()聚焦圖元的效果如下所示:
fitInView()有四種縮放策略,分別如下:
| Qt::IgnoreAspectRatio | 自由縮放,不受長寬比限制,盡可能的填充View |
| Qt::KeepAspectRatio | 在保持長寬比的情況下,在View范圍內縮放,使View中顯示的范圍盡可能的大。 |
| Qt::KeepAspectRatioByExpanding | 在保持長寬比的情況下,通過縮放讓處于View范圍之外的面積盡可能的小。 |
官方文檔中各種縮放模式的示例圖如下所示:
在例子中對應的效果如下所示:
總結
以上是生活随笔為你收集整理的QGraphicsView图形视图框架使用(三)位移变换和图元定位的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做一个H5项目的准备工作-1
- 下一篇: 高德地图开发:英文地图的实现方式