詳解QwtLegend(1)

目錄

QwtLegend的繼承關係圖

QwtLegend簡介

QwtAbstractLegend簡介

QwtAbstractLegend的源碼

實戰代碼(詳解insertLegend( new QwtLegend() );)

(1)爲什麼它是在右上側,不是在左上側呢?

(2)它怎麼直接就在界面上了,在qt中不是得先對其佈局嗎?

(3)還有最重要的是右上側顯示的bar0,bar1,bar2以及對應的icon從哪來的,代碼中沒有設置啊


QwtLegend的繼承關係圖

要充分的瞭解一個類,繼承關係是很重要的,子類中有很多接口是來源於基類,包括一些可重寫的virtual函數,我們使用別人的庫,增加一些新功能時一般需要重寫庫的類。所以:首先先貼個QwtLegend的繼承關係圖。

QwtLegend簡介:

  • 官方文檔:The legend widget. The QwtLegend widget is a tabular arrangement of legend items. Legend items might be any type of widget, but in general they will be a QwtLegendLabel.
  • 中文翻譯:QwtLegend widget是由列表中的legend items組成的。Legend items可以是任何類型的widget,但一般是一個QwtLegendLabel。(QwtLegendLabel類似於Qt的QLabel),具體介紹可看博客鏈接:

QwtAbstractLegend簡介:

  • 官方文檔:Abstract base class for legend widgets。Legends, that need to be under control of the QwtPlot layout system need to be derived from QwtAbstractLegend. Other type of legends can be implemented by connecting to the QwtPlot::legendDataChanged() signal. But as these legends are unknown to the plot layout system the layout code( on screen and for QwtPlotRenderer ) need to be organized in application code.
  • 中文翻譯:QwtAbstractLegend是legend widgets的抽象基類。legends繼承於QwtAbstractLegend,並且在QwtPlot的佈局系統中。所有類型的legends,除了QwtAbstractLegend,需要連接 QwtPlot::legendDataChanged() 這個信號就可以進入legends對象的槽中,去執行它的代碼。但是這些legends 對於QwtPlot的佈局是未知的,所以需要你在應用程序中寫佈局代碼,使legend佈局到QwtPlot中。

QwtAbstractLegend的源碼:

  • 方法都是virtual,需要子類繼承重寫。
class QWT_EXPORT QwtAbstractLegend : public QFrame
{
    Q_OBJECT

public:
    explicit QwtAbstractLegend( QWidget *parent = NULL );
    virtual ~QwtAbstractLegend();

    /*!
      Render the legend into a given rectangle.

      \param painter Painter
      \param rect Bounding rectangle
      \param fillBackground When true, fill rect with the widget background 

      \sa renderLegend() is used by QwtPlotRenderer
    */
    virtual void renderLegend( QPainter *painter, 
        const QRectF &rect, bool fillBackground ) const = 0;

    //! \return True, when no plot item is inserted
    virtual bool isEmpty() const = 0;

    virtual int scrollExtent( Qt::Orientation ) const;

public Q_SLOTS:

    /*!
      \brief Update the entries for a plot item

      \param itemInfo Info about an item
      \param data List of legend entry attributes for the  item
     */
    virtual void updateLegend( const QVariant &itemInfo, 
        const QList<QwtLegendData> &data ) = 0;
};

 

緊接着介紹QwtLegend:

  • class PrivateData簡介:
class QScrollBar;

class QWT_EXPORT QwtLegend : public QwtAbstractLegend
{
    Q_OBJECT

public:
    explicit QwtLegend( QWidget *parent = NULL );
    virtual ~QwtLegend();

    //此處省略
    ............
    ............
    ............

private:

    class PrivateData;
    PrivateData *d_data;
};

看到QwtLegend的private成員變量中有一個class PrivateData了吧,qwt的庫中凡是帶有class PrivateData的類,該類的數據成員全在privateData中。這種寫法的解釋可以看鏈接:

  • class LegendView的簡介
class QwtLegend::PrivateData
{
public:
    PrivateData():
        itemMode( QwtLegendData::ReadOnly ),
        view( NULL )
    {
    }

    QwtLegendData::Mode itemMode;
    QwtLegendMap itemMap;

    class LegendView;
    LegendView *view;
};

class QwtLegend::PrivateData::LegendView: public QScrollArea
{
public:
    LegendView( QWidget *parent ):
        QScrollArea( parent )
    {
        contentsWidget = new QWidget( this );
        contentsWidget->setObjectName( "QwtLegendViewContents" );

        setWidget( contentsWidget );
        setWidgetResizable( false );

        viewport()->setObjectName( "QwtLegendViewport" );

        // QScrollArea::setWidget internally sets autoFillBackground to true
        // But we don't want a background.
        contentsWidget->setAutoFillBackground( false );
        viewport()->setAutoFillBackground( false );
    }

     //此處省略
     ..............
     ..............
}

 由上面片段代碼可以看出QwtLegend的私有成員LegendView中放了一個widget。

再看下面這段代碼將LegendView佈局到了QwtLegend中。

QwtLegend::QwtLegend( QWidget *parent ):
    QwtAbstractLegend( parent )
{
    setFrameStyle( NoFrame );

    d_data = new QwtLegend::PrivateData;

    d_data->view = new QwtLegend::PrivateData::LegendView( this );
    d_data->view->setObjectName( "QwtLegendView" );
    d_data->view->setFrameStyle( NoFrame );

    QwtDynGridLayout *gridLayout = new QwtDynGridLayout(
        d_data->view->contentsWidget );
    gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop );

    d_data->view->contentsWidget->installEventFilter( this );

    QVBoxLayout *layout = new QVBoxLayout( this );
    layout->setContentsMargins( 0, 0, 0, 0 );
    layout->addWidget( d_data->view );
}

綜上所述:QwtLegend就只是放了一個繼承自QScrollarea的legendView,然後在legendView中放了一個widget。

實戰代碼(詳解insertLegend( new QwtLegend() );)

QwtLegend的簡介完畢。我們來看看實戰代碼。例子是barchart。

BarChart::BarChart( QWidget *parent ):
    QwtPlot( parent )
{
    ...
    ...

    insertLegend( new QwtLegend() );

    ...
    ...
}

在該例中:與QwtLegend相關的只有insertLegend( new QwtLegend() );這句代碼。

我們把這句話註釋掉再運行,可以得出結論這句話的作用是顯示右上側的legend。這個時候我想大家應該會提出這麼三個問題。

(1)爲什麼它是在右上側,不是在左上側呢?

(2)它怎麼直接就在界面上了,在qt中不是得先對其佈局嗎?

(3)還有最重要的是右上側顯示的bar0,bar1,bar2以及對應的icon從哪來的,代碼中沒有設置啊,有什麼類似下面這段代碼的嗎?

QwtLegend legend;

legend.setValue(...);

legend.setIcon(...);

沒有,我們倒是看到了下面這段代碼

    QList<QwtText> titles;
    for ( int i = 0; i < numBars; i++ )
    {
        QString title("Bar %1");
        titles += title.arg( i );
    }
    d_barChartItem->setBarTitles( titles );
    d_barChartItem->setLegendIconSize( QSize( 10, 14 ) );

但d_barChartItem是QwtPlotMultiBarChart的對象,不是QwtLegend對象啊。

我想有經驗的同志們應該想到了,

第一個是默認設置在右上側,源碼如下:

在qwt_plot.h中:

void insertLegend( QwtAbstractLegend *, 
        LegendPosition = QwtPlot::RightLegend, double ratio = -1.0 );

LegendPosition = QwtPlot::RightLegend 看到了吧,默認在右側。

如果你想改動到左上側,可以寫爲insertLegend(legend,QwtPlot::LeftLegend);

順便提下,在QwtLegend的簡介中,有一句是需要連接 QwtPlot::legendDataChanged() 這個信號就可以進入legends對象的槽中,去執行它的代碼。上面那個信號槽就是。

第二個問題的答案是:它也是用佈局的方式,只是把佈局隱藏到內部執行了。

在qwt_plot.cpp中:

void QwtPlot::insertLegend( QwtAbstractLegend *legend,
    QwtPlot::LegendPosition pos, double ratio )
{
    d_data->layout->setLegendPosition( pos, ratio );
    
    ......
    ......
}
void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos, double ratio )
{
    if ( ratio > 1.0 )
        ratio = 1.0;

    switch ( pos )
    {
        case QwtPlot::TopLegend:
        case QwtPlot::BottomLegend:
            if ( ratio <= 0.0 )
                ratio = 0.33;
            d_data->legendRatio = ratio;
            d_data->legendPos = pos;
            break;
        case QwtPlot::LeftLegend:
        case QwtPlot::RightLegend:
            if ( ratio <= 0.0 )
                ratio = 0.5;
            d_data->legendRatio = ratio;
            d_data->legendPos = pos;
            break;
        default:
            break;
    }
}

對於第三個問題,其實隱藏的是比較深的。

確實是下面的兩行代碼繪製了右上角的QwtLegend。

    d_barChartItem->setBarTitles( titles );
    d_barChartItem->setLegendIconSize( QSize( 10, 14 ) );

具體解釋下:

  • setBarTitles()
void QwtPlotMultiBarChart::setBarTitles( const QList<QwtText> &titles )
{
    d_data->barTitles = titles;
    itemChanged();
}

           d_data 指的是QwtPlotMultiBarChart的私有成員指針,具體介紹可看QwtLegend的介紹中。

           第一步存儲數據,第二部的itemChanged()最終執行的是qt的replot()函數,大家可以跟着斷點進去看,最終是重繪。

  • setLegendIconSize()

       下面代碼中有關於QwtLegendData,可看我的博客關於QwtLegendData的簡介。

void QwtPlotItem::setLegendIconSize( const QSize &size )
{
    if ( d_data->legendIconSize != size )
    {
        //存儲圖標數據
        d_data->legendIconSize = size;
        legendChanged();
    }
}


void QwtPlotItem::legendChanged()
{
    if ( testItemAttribute( QwtPlotItem::Legend ) && d_data->plot )
        d_data->plot->updateLegend( this );
}


void QwtPlot::updateLegend( const QwtPlotItem *plotItem )
{
    if ( plotItem == NULL )
        return;

    QList<QwtLegendData> legendData;

    if ( plotItem->testItemAttribute( QwtPlotItem::Legend ) )
        //獲取QwtPlotMultiBarChart對象的LegendData數據
        legendData = plotItem->legendData();

    const QVariant itemInfo = itemToInfo( const_cast< QwtPlotItem *>( plotItem) );
    Q_EMIT legendDataChanged( itemInfo, legendData );
}


QList<QwtLegendData> QwtPlotItem::legendData() const
{
    QwtLegendData data;

    QwtText label = title();
    label.setRenderFlags( label.renderFlags() & Qt::AlignLeft );
            
    QVariant titleValue;
    qVariantSetValue( titleValue, label );
    data.setValue( QwtLegendData::TitleRole, titleValue );
        
    const QwtGraphic graphic = legendIcon( 0, legendIconSize() );
    if ( !graphic.isNull() )
    {   
        QVariant iconValue;
        qVariantSetValue( iconValue, graphic );
        data.setValue( QwtLegendData::IconRole, iconValue );
    }   
        
    QList<QwtLegendData> list;
    list += data;

    return list;
}

void QwtPlot::insertLegend( QwtAbstractLegend *legend,
    QwtPlot::LegendPosition pos, double ratio )
{
    .......
    .......
    connect( this, 
                SIGNAL( legendDataChanged( 
                    const QVariant &, const QList<QwtLegendData> & ) ),
                d_data->legend, 
                SLOT( updateLegend( 
                    const QVariant &, const QList<QwtLegendData> & ) ) 
            );
    .......
    .......
}



void QwtLegend::updateLegend( const QVariant &itemInfo, 
    const QList<QwtLegendData> &data )
{
    //獲取所有的右上角的widget,如果有的話,按照barchart例子來將總共有三個widget。
    QList<QWidget *> widgetList = legendWidgets( itemInfo );

    if ( widgetList.size() != data.size() )
    {
        //可以看QwtLegend的簡介,獲取到的contentsLayout是QwtLegend中legendView的layout。
        QLayout *contentsLayout = d_data->view->contentsWidget->layout();

        while ( widgetList.size() > data.size() )
        {
            QWidget *w = widgetList.takeLast();

            contentsLayout->removeWidget( w );

            // updates might be triggered by signals from the legend widget
            // itself. So we better don't delete it here.

            w->hide();
            w->deleteLater();
        }

        for ( int i = widgetList.size(); i < data.size(); i++ )
        {
            //創建右上角的widget,總共三個
            QWidget *widget = createWidget( data[i] );

            if ( contentsLayout )
                //添加widget到佈局中
                contentsLayout->addWidget( widget );
            
            //讓其可見
            if ( isVisible() )
            {
                // QLayout does a delayed show, with the effect, that
                // the size hint will be wrong, when applications
                // call replot() right after changing the list
                // of plot items. So we better do the show now.

                widget->setVisible( true );
            }

            widgetList += widget;
        }

        if ( widgetList.isEmpty() )
        {
            d_data->itemMap.remove( itemInfo );
        }
        else
        {
            d_data->itemMap.insert( itemInfo, widgetList );
        }

        //設置幾個widget的tab鍵順序,按tab鍵的時候會有順序。
        updateTabOrder();
    }
    
    for ( int i = 0; i < data.size(); i++ )
        updateWidget( widgetList[i], data[i] );
}

//設置右上角QLegend中label的數據和模式
void QwtLegend::updateWidget( QWidget *widget, const QwtLegendData &data )
{
    QwtLegendLabel *label = qobject_cast<QwtLegendLabel *>( widget );
    if ( label )
    {
        label->setData( data );
        if ( !data.value( QwtLegendData::ModeRole ).isValid() )
        {
            // use the default mode, when there is no specific
            // hint from the legend data

            label->setItemMode( defaultItemMode() );
        }
    }
}

總而言之,setLegendIconSize()的意思是存儲icon數據,並繪製了QwtLegend。

QwtLegend的基本屬性

將insertLegend(legend,QwtPlot::LeftLegend)

改爲:

QwtLegend *legend = new QwtLegend();

QWidget* widget = legend->contentsWidget();

widget->setBackgroundRole(QPalette::Background);

widget->setStyleSheet("background-color:#37474F");

經測試可以看出QwtLegend所佔的位置是右側一大塊,而不是隻有顯示右上側的那麼點。這也正符合了qt的佈局之後的樣子。

還有就是QwtLegend中有legendView繼承自QScrollarea。右側的國家如果夠多就會超出QwtPlot,就會有滾動條出現。

QScrollarea這個類具有滾動條的屬性。

 

將insertLegend(legend);改爲

QwtLegend *legend = new QwtLegend();

legend->setMaxColumns(3);

效果圖爲: 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章