QCustomplot使用分享(五) 布局


一、歷史對比

  關於QCPLayoutElement這個元素的講解之前,我想先對1.3.2release版本和2.0.0beta版本的該元素做以簡單的對比介紹,首先,1.3.2release版本時,鼠標單擊時,如果按下的位置是一個布局元素,那么QCustomPlot首先會把這個事件回調給該被點擊的元素,並且mouse系列的方法都是這樣傳遞給QCPLayoutElement對象,該布局元素的聲明會像這樣QPointer<QCPLayoutElement> mMouseEventElement;但是到了2.0.0beta版本時,QCustomPlot源碼做出了很大的調整,不僅僅是QCPLayoutElement布局元素可以接收鼠標事件,凡事繼承自QCPLayerable類的元素都可以支持鼠標事件,因為mouse一系列的方法被移到了QCPLayerable類中。下面我分別貼出這兩個版本時的mousePressEvent處理方法

    1.3.2release版本鼠標按下處理方式

 1 void QCustomPlot::mousePressEvent(QMouseEvent *event)
 2 {
 3     emit mousePress(event);
 4     mMousePressPos = event->pos(); // need this to determine in releaseEvent whether it was a click (no position change between press and release)
 5 
 6     // call event of affected layout element:
 7     mMouseEventElement = layoutElementAt(event->pos());//后去當前選中布局元素 並調用其相關接口
 8     if (mMouseEventElement)
 9         mMouseEventElement->mousePressEvent(event);
10 
11     QWidget::mousePressEvent(event);
12 }

    2.0.0beta版本鼠標按下處理方式

 1 void QCustomPlot::mousePressEvent(QMouseEvent *event)
 2 {
 3     emit mousePress(event);
 4     // save some state to tell in releaseEvent whether it was a click:
 5     mMouseHasMoved = false;
 6     mMousePressPos = event->pos();
 7 
 8     if (mSelectionRect && mSelectionRectMode != QCP::srmNone)//優先處理鼠標繪制矩形事件
 9     {
10         if (mSelectionRectMode != QCP::srmZoom || qobject_cast<QCPAxisRect*>(axisRectAt(mMousePressPos))) // in zoom mode only activate selection rect if on an axis rect
11             mSelectionRect->startSelection(event);
12     }
13     else
14     {
15         // no selection rect interaction, so forward event to layerable under the cursor:
16         QList<QVariant> details;
17         QList<QCPLayerable*> candidates = layerableListAt(mMousePressPos, false, &details);//根據鼠標位置獲取當前點選的元素
18         for (int i = 0; i < candidates.size(); ++i)
19         {
20             event->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list
21             candidates.at(i)->mousePressEvent(event, details.at(i));
22             if (event->isAccepted())//如果有候選者處理了鼠標事件,那么事件循環結束
23             {
24                 mMouseEventLayerable = candidates.at(i);
25                 mMouseEventLayerableDetails = details.at(i);
26                 break;
27             }
28         }
29     }
30 
31     event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
32 }

    對比上述鼠標按下時兩個版本的代碼,beta版本(指2.0.0版本)比發布版本(1.3.2)明顯多了一些代碼,具體在兩個方面體現:1、多了一個矩形選擇區域;2、鼠標按下時獲取當前候選元素,返回值類型為QCPLayerable。

    如圖2是我設置了矩形選擇區域畫筆顏色為紅色之后,進行的測試,可以畫出一個紅色的矩形框,測試代碼:selectionRect()->setPen(QPen(Qt::red));

圖1 繪制矩形

二、布局元素成員

    從第一小節看過來我們已經知道了mouse系列的方法已經從QCPLayoutElement移到了QCPLayerable類中,加之上述對鼠標按下事件的理解,這里我就不在說明鼠標事件的處理邏輯了,其實還是比較簡單的,總之鼠標事件是從QCustomPlot類中觸發,然后通過指針回調到各個QCPLayerable對象中,直到有一個對象處理了這個事件。

    如圖2所示,是QCPLayoutElement類的繼承關系圖,從QCPLayoutElement派生出來的直接子類一共有5個,下面分別做以介紹

  • QCPAbstractLegendItem:圖例項,包含於QCPLegend,具體可以參看QCustomplot使用分享(二) 源碼解讀圖1
  • QCPAxisRect:坐標軸矩形,一般默認包含4個坐標軸,前邊的文章也有簡單的介紹到
  • QCPColorScale:顏色表,配合QCPColorMap使用
  • QCPLayout:布局抽象類,直接子類包括:QCPLayoutGrid和QCPLaoutInset
  • QCPTextElement:文本,可以支持鼠標事件

    以上類我是按照QCustomPlot提供的類圖類進行分析的,但是本節我將不對坐標軸、文本元素、圖例項和顏色表進行分析,僅僅只對QCPLayout類加以說明

圖2 QCPLayoutElement類圖

三、QCPLayout

    從圖2可以看出QCPLayout有2個直接子類和1個間接子類,QCPLegend就是圖例類了,他可以包含多個QCPAbstractLegend項,構成一個真正的的圖例。接下來我們將進入到其直接子類的介紹,也是我們本片文章的重點需要講解到的。

1、QCPLayoutGrid

    不管是beta版本還是發布版本,對於這個類的事件回調一直沒有變化,都是類似如下代碼

1 mPlotLayout->update(QCPLayoutElement::upPreparation);//margin計算和布局之前的准備
2 mPlotLayout->update(QCPLayoutElement::upMargins);//計算margin
3 mPlotLayout->update(QCPLayoutElement::upLayout);//更新布局

    這3次調用update方法,只是參數不同而已,看起來有些可怕,其實不然,好了,現在就跟我們一步一步分析這個調用過程吧

a、回調到抽象類QCPLayout的update方法中,因為phase第一次傳遞的是QCPLayoutElement::upPreparation,因此第3行的代碼(原因看b小節)和第6行的if都不能執行

 1 void QCPLayout::update(UpdatePhase phase)
 2 {
 3     QCPLayoutElement::update(phase);
 4 
 5     // set child element rects according to layout:
 6     if (phase == upLayout)
 7         updateLayout();
 8 
 9     // propagate update call to child elements:
10     const int elCount = elementCount();//返回當前布局元素下總共有多少個子類 一般情況下至少有一個QCPAxisRect
11     for (int i = 0; i < elCount; ++i)
12     {
13         if (QCPLayoutElement *el = elementAt(i))
14             el->update(phase);
15     }
16 }

b、當update方法中傳遞QCPLayoutElement::upMargins參數時,第3行的QCPLayoutElement::update方法就可以被執行了,先看看如下代碼,是不是有點兒明白了,在QCustomPlot類中雖然3次調用了update方法,但其實他們真正的計算是被分開了的,QCPLayoutElement::update處理margin計算,QCPLayout::update中的if負責布局更新,並把事件傳遞給所有的子類布局元素

 1 void QCPLayoutElement::update(UpdatePhase phase)
 2 {
 3     if (phase == upMargins)//只有margin計算才能進來
 4     {
 5         if (mAutoMargins != QCP::msNone)
 6         {
 7             // set the margins of this layout element according to automatic margin calculation, either directly or via a margin group:
 8             QMargins newMargins = mMargins;
 9             QList<QCP::MarginSide> allMarginSides = QList<QCP::MarginSide>() << QCP::msLeft << QCP::msRight << QCP::msTop << QCP::msBottom;
10             foreach(QCP::MarginSide side, allMarginSides)
11             {
12                 if (mAutoMargins.testFlag(side)) // this side's margin shall be calculated automatically
13                 {
14                     if (mMarginGroups.contains(side))
15                         QCP::setMarginValue(newMargins, side, mMarginGroups[side]->commonMargin(side)); // this side is part of a margin group, so get the margin value from that group
16                     else
17                         QCP::setMarginValue(newMargins, side, calculateAutoMargin(side)); // this side is not part of a group, so calculate the value directly
18                     // apply minimum margin restrictions:
19                     if (QCP::getMarginValue(newMargins, side) < QCP::getMarginValue(mMinimumMargins, side))
20                         QCP::setMarginValue(newMargins, side, QCP::getMarginValue(mMinimumMargins, side));
21                 }
22             }
23             setMargins(newMargins);
24         }
25     }
26 }

c、QCPLayoutGrid是一個遞歸包含的類,正是因為這樣我們才可以實現在一個QCustomPlot窗口包含多個圖表,不僅僅是多個圖表,而且圖表可能包含在不同的坐標軸上,如圖3所示,在一個QCustomPlot窗口中默認有一個QCPLayoutGrid,而QCPLayoutGrid可以添加了多個QCPLayoutElement元素。仔細揣摩一下圖2的類圖,你就會發現這樣的設計真是不簡單,可以實現一個比較復雜的搭配,本節就只簡單的描述下其類關系,如果需要實現復雜的示例,還是同學自己去努力啦。不過在本系列博客完結之后我將會把自己二次開發的一個demo分享給大家,現在還請大家耐心等待。

圖3 多坐標軸示例

2、QCPLayoutInset

    相對於QCPLayoutGrid來說,該類是一個一維的數組,也是一個遞歸的包含,源碼中用該類是在QCPAxisRect中用到了,負責保存outrect的大小,仔細看QCPAxisRect的源碼就會發現該類有兩個get接口是像如下代碼這樣寫的。

1 QRect rect() const { return mRect; }
2 QRect outerRect() const { return mOuterRect; }

    關於這個類的更新信息我就不多說了,看的時候沒有過多的關注,呵呵。。。有這部分需求的小伙伴就可能需要自己去看源碼了,個人覺得東西應該也不多,主要是我們當時的業務沒有涉及到這塊,所以不需要進行修改

四、布局示例

    效果如圖4所示

圖4 多坐標軸矩形

    圖4中有兩個坐標軸矩形,上邊的是默認存在的,底下的是新增坐標軸矩形,新增代碼如下(下述代碼的參數決定是新增還是隱藏已有)

 1 QCPBars * TimeSharingTrendPlot::ShowVOL(bool visible)
 2 {
 3     Q_D(TimeSharingTrendPlot);
 4     if (visible)
 5     {
 6         if (d->m_eQuota.testFlag(QCP::QT_VOL) == false)
 7         {
 8             d->m_eQuota |= QCP::QT_VOL;
 9 
10             QCPAxisRect *VolAxisRect = new QCPAxisRect(d_ptr->m_pPlot);
11             connect(d_ptr->m_pPlot->xAxis, static_cast<void (QCPAxis:: *)(const QCPRange &)>(&QCPAxis::rangeChanged)
12                 , VolAxisRect->axis(QCPAxis::atBottom), static_cast<void (QCPAxis:: *)(const QCPRange &)>(&QCPAxis::setRange));
13             connect(VolAxisRect->axis(QCPAxis::atBottom), static_cast<void (QCPAxis:: *)(const QCPRange &)>(&QCPAxis::rangeChanged)
14                 , d_ptr->m_pPlot->xAxis, static_cast<void (QCPAxis:: *)(const QCPRange &)>(&QCPAxis::setRange));
15             VolAxisRect->setMaximumSize(QSize(QWIDGETSIZE_MAX, 100));
16             VolAxisRect->axis(QCPAxis::atBottom)->setLayer("axes");
17             VolAxisRect->axis(QCPAxis::atBottom)->grid()->setLayer("grid");
18             VolAxisRect->setAutoMargins(QCP::msLeft | QCP::msRight | QCP::msBottom);
19             VolAxisRect->setMargins(QMargins(0, 0, 0, 0));
20             d_ptr->m_pPlot->plotLayout()->addElement(1, 0, VolAxisRect);
21 
22             VolAxisRect->setMarginGroup(QCP::msLeft | QCP::msRight, d->m_pMarginGroup);
23 
24             EnableFixedTicker(QCPAxis::atLeft | QCPAxis::atRight);
25 
26             //拉取分時圖下VOL指標數據
27             Q_D(TimeSharingTrendPlot);
28             QCPBars * bars = AddBars(VolAxisRect->axis(QCPAxis::atBottom), VolAxisRect->axis(QCPAxis::atLeft));
29 
30             return bars;
31         }
32         else
33         {
34         }
35     }
36     else
37     {
38         d->m_eQuota &= ~QCP::QT_VOL;
39         d->m_pPlot->plotLayout()->remove(d_ptr->m_pPlot->plotLayout()->element(1, 0));
40     }
41 
42     d->m_pPlot->plotLayout()->simplify();
43 
44     return nullptr;
45 }

五、相關文章

  QCustomplot使用分享(一) 能做什么事

  QCustomplot使用分享(二) 源碼解讀

  QCustomplot使用分享(三) 圖

  QCustomplot使用分享(四) QCPAbstractItem

 

如果您覺得文章不錯,不妨給個 打賞,寫作不易,感謝各位的支持。您的支持是我最大的動力,謝謝!!! 

 

  


很重要--轉載聲明

  1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用鏈接的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords
  2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM