详解QwtLegend(1)

目录

QwtLegend的继承关系图

QwtLegend简介

QwtAbstractLegend简介

QwtAbstractLegend的源码

实战代码(详解insertLegend( new QwtLegend() );)

(1)为什么它是在右上侧,不是在左上侧呢?

(2)它怎么直接就在界面上了,在qt中不是得先对其布局吗?

(3)还有最重要的是右上侧显示的bar0,bar1,bar2以及对应的icon从哪来的,代码中没有设置啊


QwtLegend的继承关系图

要充分的了解一个类,继承关系是很重要的,子类中有很多接口是来源于基类,包括一些可重写的virtual函数,我们使用别人的库,增加一些新功能时一般需要重写库的类。所以:首先先贴个QwtLegend的继承关系图。

详解QwtLegend(1)_第1张图片

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 &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 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 &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 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 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 list;
    list += data;

    return list;
}

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



void QwtLegend::updateLegend( const QVariant &itemInfo, 
    const QList &data )
{
    //获取所有的右上角的widget,如果有的话,按照barchart例子来将总共有三个widget。
    QList 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( 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这个类具有滚动条的属性。

详解QwtLegend(1)_第2张图片

 

将insertLegend(legend);改为

QwtLegend *legend = new QwtLegend();

legend->setMaxColumns(3);

效果图为: 

详解QwtLegend(1)_第3张图片

你可能感兴趣的:(Qwt)